⭐️Vue
Vue 2
Vue 的特性
-  数据驱动视图  
-  双向数据绑定 

-  MVVM - MVVM 指的是 Model、View 和 ViewModel,它把每个 HTML 页面都拆分成了这三个部分 
    - Model 表示当前页面渲染时所依赖的数据源。
- View 表示当前页面所渲染的 DOM 结构。
- ViewModel 表示 vue 的实例,它是 MVVM 的核心。
 
 
- MVVM 指的是 Model、View 和 ViewModel,它把每个 HTML 页面都拆分成了这三个部分 
    
-  数据代理 -  更加方便的操作data中的数据 
-  基本原理 -  通过Object.defineProperty()把data对象中所有属性添加到vm上。 
-  为每一个添加到vm上的属性,都指定一个getter/setter。 
-  在getter/setter内部去操作(读/写)data中对应的属性。 
 
-  
 
-  
Vue监视数据的原理:
    1. vue会监视data中所有层次的数据。
    2. 如何监测对象中的数据?
	通过setter实现监视,且要在new Vue时就传入要监测的数据。
        (1).对象中后追加的属性,Vue默认不做响应式处理
        (2).如需给后添加的属性做响应式,请使用如下API:
        Vue.set(target,propertyName/index,value) 或 
        vm.$set(target,propertyName/index,value)
	3. 如何监测数组中的数据?
		通过包裹数组更新元素的方法实现,本质就是做了两件事:
            (1).调用原生对应的方法对数组进行更新。
            (2).重新解析模板,进而更新页面。
	4.在Vue修改数组中的某个元素一定要用如下方法:
		1 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
		2 Vue.set() 或 vm.$set()
				
		特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
Vue 的基本使用
-  使用步骤 - 1 导入 vue.js 的script 脚本文件
- 2 在页面中声明一个将被 Vue 所控制的 DOM 区域
- 3 创建 vm 实例对象
 <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 --> <div id="app">{{ username }}</div> <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --> <script src="./lib/vue-2.6.12.js"></script> <!-- 2. 创建 Vue 的实例对象 --> <script> // 创建 Vue 的实例对象 const vm = new Vue({ // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器 el: '#app', // data 对象就是要渲染到页面上的数据 data: { username: 'zhangsan' } }) </script>

指令
按照不同用途可分为:
1 内容渲染指令
-  v-text- 示例:<p v-text="gender">性别:</p>
- ❗️v-text 指令会覆盖元素内默认的值
 
- 示例:
-  ⭐️ {{ }}- <p>性别:{{ gender }}</p>
- 不会覆盖元素内默认的值
 
-  v-html-  把包含 HTML 标签的字符串渲染为页面的 HTML 元素 
-  <div v-html="info"></div>
-  ❗️v-html有安全性问题 -  (1).在网站上动态渲染任意HTML是非常危险的,容易导致 XSS 攻击。 
-  (2).一定要在可信的内容上使用v-html,不要用在用户提交的内容上!(和eval()有点像) 
 
-  
 
-  
-  v-cloak- 1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
- 2.使用css(display:'none')配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
 
-  v-once- 1.v-once所在节点在初次动态渲染后,就视为静态内容了。
- 2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
 
-  v-pre- 1.跳过其所在节点的编译过程。
- 2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
 
2 属性绑定
-  v-bind: -  v-bind: 指令可以简写为 :
-  <input type="text" :placeholder="tips">
-  <img :src="photo" alt="">
-  <div title="'box'+index"></div>
-  动态绑定 class / style / checked -  :checked="isChecked"- isChekced是 Boolean值
 
-  字符串: - 适用于:类名不确定,要动态获取。
- :class="classes"- classes是一个计算属性
 
 
-  对象语法: -  要绑定多个样式,个数确定,名字也确定,但不确定用不用 
-  :class="{active:isActive}"
-  :class="{active:isActive,line:isLine}"
-  class="title":class="{active:isActive,line:isLine}"
 
-  
-  数组语法: - 要绑定多个样式,个数不确定,名字也不确定。
- :style='[styleobj,overridingStyles]'
 
 
-  
 使用 JavaScript 表达式 的运算  
-  
3 事件绑定
-  v-on :简写形式:@ - eg: @click=‘addCount’ @keyup=‘count += 1’
 
-  需要在 methods 节点中进行声明 
  
-  事件参数对象 -  $(event) 指原生的事件参数对象 event 
-  绑定事件并 传参<button @click="add($event, 1)">+N</button>
 
-  
-  事件修饰符 事件修饰符 说明 .prevent 阻止默认行为(eg:阻止 a 链接的跳转、阻止表单的提交等) .stop 阻止事件冒泡 .capture 以捕获模式触发当前的事件处理函数 .once 绑定的事件只触发一次 .self 只有在 event.target 是当前元素自身时触发事件处理函数 - <a href="http://www.baidu.com" @click.prevent="show">跳转到百度首页</a>
 
-  按键修饰符 - <input type="text" @keyup.esc="clearInput" @keyup.enter="commitAjax">
- .enter
- .delete
- .esc
- .space
- .up- .down- .left- .right
- tab换行 (必须配合 keydown去使用)
 
-  可以执行少量代码 count++
-  可以写函数传参(可以获取事件对象,没写参数默认有,写参数要 (a,$event)- 给调用函数+个()eg:@change="handelClick()"
- 如果只有一个 e 不加 ()
- e.target.value/checkded
 
- 给调用函数+个()eg:
4 双向绑定
-  v-model -  实现 表单与数据的双向绑定
-  用于获取 表单(输入类)的数据
-  场景 - 表单(value / checked)、全选(状态在 computed )反选(状态在 data )
 
-  收集表单数据: 若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。 若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。 若:<input type="checkbox"/> 1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值) 2.配置input的value属性: (1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值) (2)v-model的初始值是数组,那么收集的的就是value组成的数组(多选)
-  v-model 修饰符 修饰符 作用 示例 .number 自动把用户输入的值转为数值类型 <input type="number" v-model.number="age">.trim 自动过滤用户输入 的首尾空白字符 <input v-model.number="msg">.lazy 在“change”时而非“input”时更新 <input v-model.number="msg">
 
-  
5 条件渲染
-  条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏 -  v-if-  <p v-if="flag">这是被 v-if 控制的元素</p>
-  会动态地创建或移除 DOM 元素 - v-else、- v-else-if=“ ”
- 必须相邻
 
-  只能在与 v-if 平级的时候使用 优秀 良好 一般 差 
-  移除时 dom 不存在 
 
-  
-  v-show
-  <p v-show="flag">这是被 v-show 控制的元素</p>- 会动态为元素添加或移除 style=“display: none;” 样式
- 移除时 dom 存在
 
 v-if v-show 动态地创建或移除 DOM 元素 动态为元素添加或移除 style="display: none;"样式 支持多条件显示 不支持多条件显示 有更高的开销 有更高的初始渲染开销 使用场景:切换频率较低、判断条件较多的场景 使用场景:非常频繁地切换 
-  
6 列表渲染
-  v-for - 基于一个数组来循环渲染一个列表结构
- 需要使用 item in items 形式的特殊语法
 <tr v-for="(item, index) in list" :key="item.id"> <td>{{ index }}</td> <td>{{ item.id }}</td> <td>{{ item.name }}</td> </tr>- 1 用于 循环 数组 (item,index) in array
- 2 用于循环 对象 (item,key) in obj
- 3 用于循环 数字 item in num
- 使用 key 维护列表的状态 
    - Vue 复用已存在的DOM元素提升渲染性能 但导致有状态的列表无法被正确更新
- key 注意事项 
      - key 的值只能是字符串或数字类型
- key 的值必须 具有唯一性
- 建议把数据项的 id 属性值作为 key 的值
- 使用 index 的值当做 key 的值没有意义( index 不具唯一性)
- 建议使用 v-for 指令时要指定 key 的值(既提升性能,又防止列表状态紊乱)
 
 
- label 的 for 属性 
    - :for="'cb' + item.id"input里面- :id="'cb' + item.id"
 
 
过滤器
-  过滤器放在 js 表达式的尾部 由“管道符”进行调用 - 插值表达式 
    - <p>message 的值是:{{ message | capi }}</p>
- 不能给属性用,直接“ ”
 
- v-bind 属性绑定 
    - <div v-bind:id="rawId | formatId"></div>
 
 
- 插值表达式 
    
-  定义过滤器 -  在创建 vue 实例期间,可以在 filters 节点中定义过滤器 filters: { // 注意:过滤器函数形参中的 val,永远都是“管道符”前面的那个值 capi(val) { // 字符串有 charAt 方法,这个方法接收索引值,表示从字符串中把索引对应的字符,获取出来 // val.charAt(0) const first = val.charAt(0).toUpperCase() // 字符串的 slice 方法,可以截取字符串,从指定索引往后截取 const other = val.slice(1) // 强调:过滤器中,一定要有一个返回值 return first + other } }
-  私有过滤器 
-  全局过滤器 // 使用 Vue.filter() 定义全局过滤器 Vue.filter('capi', function (str) { const first = str.charAt(0).toUpperCase() const other = str.slice(1) return first + other + '~~~' })
 
-  
-  连续调用多个过滤器 - <p>message 的值是:{{ message | capi | maxLength }}</p>
 
-  过滤器传参 -  本质是 js 函数 <p>{{ message | filterA(arg1,arg2)}</p> Vue.filter('filterA',(mesg,arg1,arg2)=>{})
 
-  
-  兼容性 - 仅在 Vue1、2中受支持
- vue3 不支持 
    - 官方建议使用 计算属性 或 方法 代替过滤器功能
- 参考
 
 
侦听器
当被监视的属性变化时, 回调函数自动调用, 进行相关操作
- 1 在 watch 节点进行声明
const vm = new Vue({
  el: '#app',
  data: {
    username: 'admin'
  },
  // 所有的侦听器,都应该被定义到 watch 节点下
  watch: {
    // 侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为方法名即可
      
    // 新值在前,旧值在后
    username(newVal) {
      if (newVal === '') return
      // 1. 调用 jQuery 中的 Ajax 发起请求,判断 newVal 是否被占用!!!
      $.get('https://www.escook.cn/api/finduser/' + newVal, function (result) {
        console.log(result)
      })
    }
  }
})
-  2 使用 watch 检测用户名是否可用 watch: { // 监听 username 值的变化 async username(newVal) { if (newVal === '') return // 使用 axios 发起请求,判断用户名是否可用 const { data: res } = await axios.get('https://www.escook.cn/api/finduser/' + newVal) } }
-  3 immediate选项 watch: { // 让被监听的对象指向一个 配置对象 username: { // handler 是固定写法,表示当 username 的值变化时,自动调用 handler 处理函数 handler: async function (newVal) { if (newVal === '') return const { data: res } = await axios.get('https://www.escook.cn/api/finduser/' + newVal) console.log(res) }, // 表示页面初次渲染好之后,就立即触发当前的 watch 侦听器 immediate: true } }
-  4 deep 选项 - 在上面的基础上加一个 deep:true
 
- 在上面的基础上加一个 
-  5 监听对象单个属性的变化 watch:{ info: { handler(newVal) { console.log(newVal) }, deep:true, } // 如果要侦听的是 子属性 的变换,则必须包裹一层单引号 'info.username'(newVal) { console.log(newVal) } // 配置对象 'info.username':{ async handler(newVal){ const { data:res } = await axios.get('https://www.escook.cn/api/finduser/' + newVal.username) console.log(res) } } }
监视的属性必须存在,才能进行监视!!
-  应用场景 -  本地存储 subjectList() { // 要侦听的属性 localStorage.setItem('scoreMsg', JSON.stringify(this.subjectList)) }
-  当监听 对象数组 时,数组的长度变化时,不用 deep 也可以,但是监听不到对象内部的 变化 
-  数据变化时,发起 ajax 请求 
 
-  
计算属性
-  个动态计算出来的属性值可以被模板结构或 methods 方法使用。 <div id="app"> <!-- 专门用户呈现颜色的 div 盒子 --> <!-- 在属性身上,: 代表 v-bind: 属性绑定 --> <!-- :style 代表动态绑定一个样式对象,它的值是一个 { } 样式对象 --> <!-- 当前的样式对象中,只包含 backgroundColor 背景颜色 --> <div class="box" :style="{ backgroundColor: rgb }"> {{ rgb }} </div> <button @click="show">按钮</button> </div> <script> // 创建 Vue 实例,得到 ViewModel var vm = new Vue({ el: '#app', data: { // 红色 r: 0, // 绿色 g: 0, // 蓝色 b: 0 }, methods: { // 点击按钮,在终端显示最新的颜色 show() { console.log(this.rgb) } }, // 所有的计算属性,都要定义到 computed 节点之下 // 计算属性在定义的时候,要定义成“方法格式” computed: { // rgb 作为一个计算属性,被定义成了方法格式, // 最终,在这个方法中,要返回一个生成好的 rgb(x,x,x) 的字符串 rgb() { return `rgb(${this.r}, ${this.g}, ${this.b})` } } }); console.log(vm) </script>- 所有的计算属性,都要定义到 computed 节点之下
- 计算属性在定义的时候,要定义成“方法格式”
 
-  计算属性的特点 - 1 虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性
- 2 计算属性会缓存计算的结果,只有计算属性依赖的数据变化时,才会重新进行运算
 
-  好处: - 代码复用
- data变化,计算属性也变化
 
-  应用场景 - 反选 
    - return this.list.every(item => item.checked === true)
 
- total 
    - return this.subjectList.reduce((pre, current) => (pre += current.score), 0)
 
 
- 反选 
    
全选:v-model="allChecked" 反选:computed
computed: {
    allChecked: {
      get() {
        return this.list.every(item => item.checked === true)
      },
      set(allChecked) {
        this.checked = !allChecked
        this.list.forEach(item => (item.checked = allChecked))
      }
    }
  }
计算属性:
    1.定义:要用的属性不存在,要通过`已有属性`计算得来。
    
    2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
    3.get函数什么时候执行?
        (1).初次读取时会执行一次。
        (2).当依赖的数据发生改变时会被再次调用。
    4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
    
    5.备注:
        1.计算属性最终会出现在vm上,直接读取使用即可。
        2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
计算属性 vs 侦听器
1 计算属性侧重于监听多个值的变化,最终计算并返回一个新值
2 侦听器侧重于监听单个数据的变化,最终执行特定的业务处理,不需要有任何返回值
3 computed能完成的功能,watch都可以完成。watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
axios
-  调用 axios 方法得到的返回值是 Promise对象 
-  const result = axios({ method:'GET', url:'', // URL 中的查询参数 GET // params:{}, // 请求体参数 POST // data:{} }) result.then(res=>{ console.log(res.data) }) $('#btnPost').on('click',async ()=>{ const { data:res } = await axios({ method:'POST', url:'', data:{ name:'zs', age:20 } }) // 返回的是数据对象 可以解构单独拿出 data 把 data 重命名为 res })

如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await
await 只能在被 async “修饰”的方法中
用 jQuery 可以发起 $.ajax() $.get() $.post() 请求
axios:axios() axios.get() axios.post() axios.delete() axios.put() axios.patch()
axios.get()
$('#btnPost').on('click',async ()=>{
    const { data:res } = await axios.get('url',{params:{id:1}})
})
axios.post()
$('#btnPost').on('click',async ()=>{
    const { data:res } = await axios.post('url',{name:'zs',gender:'女'}) // axios.post()里面的请求体直接写数据对象
})
组件中发起axios 请求,不用每个组件都要导入 axios,在 main.js 导入,变成 Vue内置的成员
// main.js
import axios from 'axios'
// 配置请求根路径
axios.defaults.baseURL = 'http://www.itcbc.com:3006'
// 把 axios 挂载到 Vue.prototype上,供每个组件的实例直接使用
// 缺点: 不利于接口的 复用
Vue.propotype.$http = axios
// 组件中
methods:{
    async getBooks(){
        const {data : res} = await this.$http.get('http://www.itcbc.com:3006/api/getbooks')
    }
}
$mount方法
const vm = new Vue({
  data: {
    username: 'admin'
  }
})
vm.$mount('#app')
vue/cli
下载: npm i -g @vue/cli 查找安装: vue -V
-  在指定目录的 终端下 创建指定名称的项目 -  vue create demo-first
-  项目名称不能有空格、中文、大写字母 
  
-  创建冻结 按 ctrl + c 
-  创建成功 cd 项目名称npm run serve不要关掉终端
 
-  
-  vue 项目中 src 目录的构成 
-  assets 放:图片、css 样式等静态资源 
-  components :放封装好的 组件 
-  main.js :是项目的入口文件,整个项目的运行,要先执行 main.js 

- app.vue:是项目的根组件(render渲染的组件就是根组件)
-  vue 项目运行流程 - 通过 main.js 把 App.vue 渲染到 HTML 页面
 
单页面应用程序
vue组件
组件化开发
组件的后缀名是 .vue
一个重要的内置关系
- VueComponent.prototype.proto === Vue.prototype
为什么要有这个关系
- 让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
- this.$refs.xx(ref=xx).属性/方法()
定义组件
组成部分:
-  1 template -> 组件的模板结构(必须包含) - <template> 当前组件的 DOM 结构,需要被定义到 template 标签的内部 </template>
- template 中只能包含唯一的根节点
 
-  2 script -> 组件的 JavaScript 行为 
使用组件
- 1 使用 import 语法导入需要的组件 
  - @ 表示定位到 src
- import MyCount from 'xxx'
 
- 2 使用 components 节点注册组件
- 3 以标签的形式使用刚才注册的组件
组件间的父子关系
- 组件封装之后,彼此之间是相互独立的,不存在父子关系
- 在使用组件的时候,根据彼此的嵌套关系构成 父子、兄弟关系
私有组件
- 使用 components 注册的是 私有组件
- 在组件 A 的 components 节点下,注册了组件 F。 则组件 F 只能用在组件 A 中;不能被用在组件 C 中。
- component:{ MyCount }
- ⭐️component:{ 'my-count', MyCount }
注册全局组件
- 在 vue 项目的 main.js 入口文件中,通过 Vue.component() 方法,可以注册全局组件。 
  - 1 import
- 2 Vue.component('MyCount',MyCount)
 
- 1 
组件的 name 会显示在 devtools 上
组件的 props
exports default{
	props:['自定义属性A','自定义属性B','...'],
    data(){
        return {}
    }
}
-  props 是只读的 -  修改:把 props 的值 转存 到 data 中,data 中的数据都是可读写的 
-  不要直接在 子组件里修改 props 的值 (会报错,父组件没跟着变) props:['init'], data(){ return{ count:this.init } }
 
-  
-  props 的 default 默认值 -  props:{ init: { default: 0 }}
-  用户没传 init 的值时,default 的值生效 
-  对象或数组默认值必须从一个工厂函数获取 default:function() { return {message:'hello'} }
 
-  
-  type 值类型 - eg:type: Number
- :init="9"v-bind: 加上 js 的数字
- 写在 props 里面
- props:{name:String}
 
- eg:
-  required 必填项 - required: true
 
使用:<my-count :init="9"></my-count>
组件之间的样式冲突问题
-  根本原因 - 1 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
- 2 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素
 
-  解决 -  1 为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域 <style> .container[data-v-0001]{ border: 1px solid red;} </style>
-  2 style 的 scoped 属性 - 防止组件之间的样式冲突问题
- <style scoped> </style>
- 则当前组件的样式对其子组件是不生效的
 
-  3 /deep/ 样式穿透 - 让某些样式对子组件生效
 
 
-  
-  当使用第三方组件库的时候,如果有修改第三方组件默认样式的需求,需要用到 /deep/  
组件的生命周期
生命周期
生命周期函数
-  组件生命周期函数的分类 -  组件创建阶段、组件运行阶段 
  
-  生命周期图示 
  
 
-  
-  beforeCreate() - 创建阶段的第一个生命周期函数
- 组件的 props/data/methods 尚未被创建,都处于 不可用 状态
 
-  ⭐️created() - 组件的 props/data/methods 已创建好,都处于 可用 状态,但是组件的模板结构未生成
- 在里面调用 methods 方法,请求服务器的数据,并且,把请求到的数据,转存到 data 中,供 template 使用
- 有些bus.$on()写在 created 里面
 
-  beforeMount - 浏览器还没当前组件的 DOM结构
 
-  ⭐️mounted - 已渲染 HTML,第一次取到 DOM 结构
- 启动定时器、- 绑定自定义事件、- 订阅消息等【初始化操作】
 
-  beforeUpdate - 将要 根据变化、更新后的数据,重新渲染组件的模板结构
 
-  ⭐️updated - 已根据最新的数据,完成了组件 DOM 结构 的重新渲染
- 当数据变化之后,为了能够操作到最新的 DOM 结构,必须把代码写到 updated 生命周期函数中
- 但是一般不在这里做什么,因为一个属性变化就会触发 updated
 
-  beforeDestroy - 尚未销毁组件,还处于 正常工作状态
- 清除定时器、- 解绑自定义事件、- 取消订阅消息等【收尾工作】
 
-  destroyed - 组件已被销毁,DOM 结构已被完全移除
- 销毁后自定义事件会失效,但原生DOM事件依然有效。
- 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
  
 
⭐️组件之间的数据共享
1 父组件向子组件共享数据
- 需要使用自定义属性

- 也就是 使用自定义属性的方法
2 子组件向父组件共享数据
- 使用自定义事件(以传参的形式 去共享数据)
- 父:函数 
  - 函数接收数据
 
- 子:props:[‘函数名称’] 
  - 调用函数,传递参数数据
 

3 兄弟组件之间的数据共享
-  在 vue2 中,兄弟组件之间的数据共享的方案是 EventBus 
-  EventBus 的使用步骤 - 1 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
- 2 在数据发送方,调用 bus.**$emit(**'事件名称', 要发送的数据)方法触发自定义事件
- 3 在数据接收方,调用 bus.**$on**('事件名称', 事件处理函数)方法注册一个自定义事件
- bus.$off('xxx')解绑
 

4 ⭐️全局事件总线:任意组件间通信
-  main.js- 在vue 实例beforeCreate(){ Vue.prototype.$bus = this}
 
- 在vue 实例
-  发送方 - this.$bus.$emit('deleteTodo', 参数)
 
-  接收方 mounted() { this.$bus.$on('checkTodo', this.checkTodo) this.$bus.$on('deleteTodo', this.deleteTodo) }, beforeDestroy() { this.$bus.$off('checkTodo') this.$bus.$off('deleteTodo') }
5 消息订阅与发布 实现任意组件间通讯
使用步骤:
-  安装pubsub: npm i pubsub-js
-  引入: import pubsub from 'pubsub-js'
-  接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。 methods(){ demo(data){......} =》或者写在回调函数里 } ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 (msgName,data)=>{...} 写箭头函数 } beforeDestroy(){ pubsub.unsunscribe(this.pid)}
-  提供数据: pubsub.publish('xxx',数据)
-  最好在beforeDestroy钩子中,用 PubSub.unsubscribe(pid)去取消订阅。
ref 引用
- 使用 ref 引用 DOM 元素

- 使用 ref 引用组件实例

-  ref=' '写在 组件使用标签上
-  引用到组件的实例后,就可以调用组件上的 methods 方法 
-  控制文本框和按钮的按需切换  
-  让文本框自动获得焦点  
-  this.$nextTick(cb) 方法 
@ 从 src 源目录从外往里找
❗️一个重要的内置关系:
VueComponent.prototype.__proto__ === Vue.prototype
让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
动态组件
动态组件的渲染
data(){ // 函数 避免组件被复用时,数据存在引用关系。
    return {comName:'Left'}
}
<componet :is="comName"></componet>
<button @click="comName = 'Left'">切换 Left 组件</button>
<button @click="comName = 'Right'">切换 Right 组件</button>
使用 内置的 keep-alive 保持状态
<keep-alive>
	<component :is="comName"></component>
</keep-alive>
keep-alive 对应的生命周期函数
- 当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。
- 当组件被激活时,会自动触发组件的 activated 生命周期函数。 
  - 当组件第一次被创建是,即会触发created 也会触发 actived
- 当组件被激活,只会触发 actived,不再触发 created,因为组件没有被重新创建
 
keep-alive 的 include 属性
- 与 include 相对的是 exclude(指定哪些组件不需要被缓存)只能二选一
<keep-alive include="MyLeft,MyRight">
    <component :is="comName"></component>
</keep-alive>
组件的name属性:
export default{
    name:'MyRight'
}
对比:
- name 
  - 1 调试的时候 出现的 组件名称
- 2 与 <keep-alive>结合 指定被缓存与不被缓存
 
- 注册名称 
  - 应用场景:以标签的形式,把注册好的组件,渲染和使用到页面结构之中
 
mixin(混入)
独立的 js 文件
使用:
1 定义混合
export const xxx = {
    data(){...}
	methods(){...}
	...
}
export const yyy = {
data(){...}
methods(){...}
...
}
2 使用混合
- 全局混入 
  - 在 main.js 中 Vue.mixin(xxx)
 
- 在 main.js 中 
- 局部混入 // App.vue or 某一组件中 
  - import {xxx,yyy} from '/mixin'
- mixins:[xxx,yyy]
 
插件
独立的 js 文件
// 1 定义插件 plugins.js
export default {
    install(Vue,x,y,z)
    // 1. 添加全局过滤器
    Vue.filter('mySlice',function(value){
        return value.sclice(0,4)
    })
    
    // 2. 添加全局指令
    Vue.directive('fbind',{bind(el,binding){ el.value = binding.value},
	insert(el,binding){el.focus()},update(el,binding){el.value = 		binding.value}
	})
    // 3. 配置全局混入(合)
    Vue.mixin({data(){ return {x:100,y:200 }}})
    // 4. 添加实例方法
    Vue.prototype.$myMethod = function () {...}
    Vue.prototype.$myProperty = xxxx
                                           
    // 5. 添加全局组件
    Vue.component('myButton',{})                                       
}
// 2 引入插件 main.js
import myBtn from '@/plugin/plugins.js'
// 3 使用自定义插件
<my-button/>
插槽

基础用法:
在 <template> 中 通过<slot></slot>,为用户预留内容占位符
<my-com-1> <p>用户自定义的内容 </p> </my-com-1>
- 默认情况下,在使用组件时,提供的内容会被填充到名字为 default的插槽中
没有预留插槽的内容会被丢弃
后备内容
具名插槽
<slot name="header"></slot>
如果省略了 slot 的 name 属性就是 <slot name="default"></slot>
为具名插槽提供内容
v-slot:header 只能加给 <template>  只起到包裹作用
<my-com-2>
    <template v-slot:header>
        <h1>ittle<h1>
    </template>
	<template v-slot:default>
        <h1>ittle<h1>
    </template>
</my-com-2>        
- 具名插槽的简写形式 
  - v-slot:=>- #
- 外面没有 template包裹只能写 slot="slotName"
 
作用域插槽
组件(用 slot传)传数据给插槽使用者(用 template接收)
插槽使用者接收
- 匿名插槽 
  - <template scope="xx{games}">
- 只能有一个
 
- 具名插槽 
  - <tempalte #header="{msg,user}">
 
<slot v-for="item in list" :user="item" msg="hello"></slot>
使用作用域插槽
<template #header="scope"> {{scope}} // 使用作用域插槽的数据 </template> 直接接受一个对象 scope
解构插槽 Prop
<template #author="{msg,user}">
  <h3>一行</h3>
  <p>{{msg}}</p>
  <p>{{user.name}}</p>
</template>
自定义指令
私有自定义指令
1 在 directives 节点下声明私有自定义指令。
directives:{
    color:{
        bind(el){
            // el 是绑定了此指令的、原生的 DOM 对象
            el.style.color = 'red'
        }
    }
}
2 使用指令
<h1 v-color> APP 组件 </h1>
为自定义指令 动态绑定参数值
data(){
    return {
        color:'red'
    }
}
<h1 v-color="color">App 组件</h1>
通过 binding 获取指令的参数值
directives:{
    color:{
        bind(el,binding){
            // 通过 binging 对象的 .value 属性,获取动态的参数值
            // 标签上写 v-color="'red'"/ v-color="color" 都可
            el.style.color = binding.value
        }
    }
}
update 函数
directives:{
    color:{
        // 当指令第一次被绑定到元素时被调用(必写)
        bind(el,binding){
            // 通过 binging 对象的 .value 属性,获取动态的参数值
            el.style.color = binding.value
        },
        // 每次 DOM 更新时被调用
        update(el,binding){
            el.style.color = binding.value
        }
    }
}
函数简写
directives:{
    color(el,binding){ // 'big-number'(el,binding)
            el.style.color = binding.value
        }
    }
}
全局自定义指令
Vue.directive('color',function(el,binding){
    el.style.color = binding.value
})
//定义全局指令
Vue.directive('fbind',{
    //指令与元素成功绑定时(一上来)
    bind(element,binding){
        element.value = binding.value
    },
    //指令所在元素被插入页面时
    inserted(element,binding){
        element.focus()
    },
    //指令所在的模板被重新解析时
    update(element,binding){
        element.value = binding.value
    }
})
new Vue({
    el:'#root',
    data:{
        name:'尚硅谷',
        n:1
    },
    directives:{
        //big 函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
        /* 'big-number'(element,binding){
            // console.log('big')
            element.innerText = binding.value * 10
        }, */
        big(element,binding){
            console.log('big',this) //注意此处的 this 是 window
            // console.log('big')
            element.innerText = binding.value * 10
        },
        fbind:{
            //指令与元素成功绑定时(一上来)
            bind(element,binding){
                // 此处的 this 为 window
                element.value = binding.value
            },
            //指令所在元素被插入页面时
            inserted(element,binding){
                element.focus()
            },
            //指令所在的模板被重新解析时
            update(element,binding){
                element.value = binding.value
            }
        }
    }
})
参数:
el:是绑定了此指令的、原生的 DOM 对象
bing: 指令核心对象,描述指令全部信息属性。通过 bing 对象的 .value 属性,获取动态的参数值 {value} = bing
name:指令名
value:指令的绑定值
oldValue:指令绑定的前一个值,仅在 update和 componentUpdated钩子中可用。
expression:绑定值的字符串形式。
arg:传给指令的参数
modifers:modifiers:一个包含修饰符的对象。
vnode  虚拟节点
oldVnode:上一个虚拟节点(更新钩子函数中才有用)
ESLint
ctrl + F 可以 查找规则
路由1
前端路由的概念与原理
SPA 与前端路由
前端路由
前端路由:key是路径,value是组件。
锚链接 #
- 都属于Hash地址
- <a href="#b1">b1</a>- <div id="b1"></div>
前端路由的工作方式
① 用户点击了页面上的路由链接
② 导致了 URL 地址栏中的 Hash 值发生了变化
③ 前端路由监听了到 Hash 地址的变化
④ 前端路由把当前 Hash 地址对应的组件渲染都浏览器中
实现简易的前端路由
1 通过 <component> 标签,结合 comName 动态渲染组件
<component :is="comName"></component>
export default{
    name:'App',
    data(){
        return{
            comName:'Home'
        }
	}
}
2 在 App.vue 组件中,为 链接添加对应的 hash 值:
<a href="#/home">Home</a>
<a href="#/movie">Home</a>
<a href="#/about">Home</a>
3 在 created 生命周期函数中,监听浏览器地址栏中 hash 地址的变化,动态切换要展示的组件的名称:
created(){
    window.onhashchange = () =>{
        switch(location.hash){
            case '#home': // 点击了首页链接
                this.comName = 'Home'
                break
            case '#movie':
                this.comName = 'Moive'
                break 
            case '#about':
                this.comName = 'About'
                break    
             
        }
    }
}
vue-router
vue-router 安装和配置的步骤
① 安装 vue-router 包
- npm i vue-router@3.5.2 -S
② 创建路由模块
-  在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码: import Vue from 'Vue' import VueRouter from 'vue-router' // 调用 Vue.use() 函数,把 VueRouter 安装为 Vue 插件 Vue.use(VueRouter) const router = new VueRouter() export default router
③ 导入并挂载路由模块
-  在 src/main.js 入口文件中,导入并挂载路由模块。 import Vue from 'vue' import App from './App.vue' // 1 导入路由模块 // 在进行模块化导入时,如果给定的是文件夹,则默认导入这个文件夹,名字叫做 index.js 的文件 import router from '@/router' new Vue({ render:h => h(App), // 2 挂载路由模块 router:router }).$mount('#app')
④ 声明路由链接和占位符
- 在 src/App.vue 组件中,使用 vue-router 提供的 <router-link>和<router-view>声明路由链接和占位符:
 <template>
      <div class='app-container'>
          <h1> App 组件 </h1>
  
  		// 定义路由链接
  		<router-link to="/home">首页</router-link>
  		<router-link to="/movie">电影</router-link>
  		<router-link to="/about">关于</router-link>
  
  		<hr>
           // 定义路由的占位符  
          <router-view></router-view>
  </template>
声明路由的匹配规则
-  在 src/router/index.js 路由模块中,通过 routes 数组声明路由的匹配规则。 // 导入需要使用路由切换展示的组件 import Home from '@/components/Home.vue' import Home from '@/components/Movie.vue' import Home from '@/components/About.vue' // 创建路由的实例对象 const router = new VueRouter({ routers:[ // 在 routes 数组中,声明路由的匹配规则 // 路由规则 // path 表示要匹配的 hash 地址;component表示要展示的路由组件 {path:'/home',component:Home}, {path:'/movie',component:Movie}, {path:'/about',component:About} ] })
vue-router 的常见用法
路由重定向
const router = new VueRouter({
    routers:[
        // 当用户访问 / 时,通过 redirect 属性跳转到 /home 对应的路由规则 
        {path:'/',redirect:'/home'}
        {path:'/home',component:Home},
        {path:'/movie',component:Movie},
        {path:'/about',component:About}
    ]
})
嵌套路由
- 点击父级路由链接显示模板内容
1 声明子路由链接和子路由占位符
-  在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符。 <template> <div class="about-container"> <h3>About 组件</h3> // 在关于页面中,声明两个子路由链接 <router-link to="/about/tab1">tab1</router-link> <router-link to="/about/tab2">tab2</router-link> <hr/> <router-view></router-view> </template>
2 通过 children 属性声明子路由规则
在 src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性声明子路由规则
import Tab1 from '@/component/tabs/Tab1.vue'
import Tab1 from '@/component/tabs/Tab2.vue'
const router = new VueRouter({
    routers:[
        { // about 页面的路由规则(父级路由规则)
            path:'/about',
            component:About,
            children:[ // 通过children 属性,嵌套声明子级路由规则
                {path:'tab1',component:Tab1}, // 访问 /about/tab1
                {path:'tab2',component:Tab2}
            ]
        }
    ]
})
动态路由匹配
< path:'/movie/:id',component:Movie
$route.params 路由参数对象
在 <template>中使用 <h3>{{this.$route.params.id}}
在 <script> 中写 export default{ name:'Movie' }
路由的query参数
传递参数
<!-- 跳转并携带query参数,to的字符串写法 -->
<router-link :to="`/about/message/detail?id=${item.id}&title=${item.title}`">{{ item.title }}</router-link>
				
<!-- 跳转并携带query参数,to的对象写法 -->
<router-link 
	:to="{
		path:'/home/message/detail',
		query:{
		   id:666,
            title:'你好'
		}
	}"
>跳转</router-link>
接收查询参数:
$route.query.id
$route.query.title
< path:'/movie/:id',component:Movie, props:true>
在 Movie 组件中写 props:[‘id’]
直接使用 props 中接收的路由参数
<h3>{{ id }}</h3>
或者 $route.params.id
注意:
1 ‘/’ 后面的参数叫做 路径参数
- 使用 $route.params.id访问 路径参数
2 ‘?’ 后面叫查询参数
- 使用 $route.query.z访问 查询参数
3 在 this.$route 中 path 只是路径的一部分, fullPath 是完整的地址
4 params 与 query 的区别
- params传参:是在内存中传参,刷新会丢失
- query传参:是在地址栏传参,刷新还在
声明式导航 & 编程式导航
- 普通网页中点击 链接、vue 项目中点击 都属于声明式导航
- 普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航
vue-router 中的 编程式导航 API
① this.$router.push(‘hash 地址’)
-  跳转到指定 hash 地址,并增加一条历史记录,展示对应的组件页面。 
-  export default{ mehtods:{ getoMovie(){ this.$router.push('/movie/1') } } }
② this.$router.replace(‘hash 地址’)
- 跳转到指定的 hash 地址,不会增加历史记录并替换掉当前的历史记录
③ this.$router.go(数值 n)
-  实现导航历史前进、后退 
-  export default{ props:['id'], methods:{ goBack(){ this.$router.go(-1) } } }
-  如果后退的层数超过上限,则原地不动 
-  $router.go 的简化用法 - ① $router.back()
- ② $router.forward()
 
导航守卫
全局前置守卫
const router =new Vue({...})
                       
router.beforeEach(fn)                       
守卫方法的 3 个形参
const router =new Vue({...})
                       
router.beforeEach((to,from,next)=>{
    // to 是将要访问的路由的信息对象
    // from 是将要离开的路由的信息对象
    // next 是一个函数,调用 next() 表示放行,允许这次路由导航
})     
next 函数的 3 种调用方式
 
控制后台主页的访问权限
router.beforeEach((to,from,next))=>{
    if(to.path === '/main'){
        const token = localStorage.getItem('token')
        if(token){
            next() // 访问的是后台主页,且有 token 的值
        }else{
            next('/login')
        }
    }else{
        next() // 访问的不是后台主页,直接发行
    }
}
路由2
- 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
- 前端路由:key是路径,value是组件。
1.基本使用
-  安装vue-router,命令: npm i vue-router
-  应用插件: Vue.use(VueRouter)
-  编写router配置项: // router/index.js //引入VueRouter import VueRouter from 'vue-router' //引入Luyou 组件 import About from '../components/About' import Home from '../components/Home' //创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] }) //暴露router export default router
-  实现切换(active-class可配置高亮样式) <router-link active-class="active" to="/about">About</router-link>
-  指定展示位置 <router-view></router-view>
2.几个注意点
- 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
- 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的$route属性,里面存储着自己的路由信息。
- 整个应用只有一个router,可以通过组件的$router属性获取到。
3.多级路由(多级路由)
-  配置路由规则,使用children配置项: // router/index.js routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 { path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component:Message } ] } ]
-  跳转(要写完整路径): <router-link to="/home/news">News</router-link>
4.路由的query参数
-  传递参数 // Message.vue <!-- 跳转并携带query参数,to的字符串写法 --> <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link> // 页面中不需要 <!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }" >跳转</router-link>
-  接收参数: // Detail.vue $route.query.id $route.query.title
5.命名路由
-  作用:可以简化路由的跳转。 
-  如何使用 -  给路由命名: { path:'/demo', component:Demo, children:[ { path:'test', component:Test, children:[ { name:'hello' //给路由命名 path:'welcome', component:Hello, } ] } ] }
-  简化跳转: <!--简化前,需要写完整的路径 --> <router-link to="/demo/test/welcome">跳转</router-link> <!--简化后,直接通过名字跳转 --> <router-link :to="{name:'hello'}">跳转</router-link> <!--简化写法配合传递参数 --> <router-link :to="{ name:'hello', query:{ id:666, title:'你好' } }" >跳转</router-link>
 
-  
6.路由的params参数
-  配置路由,声明接收params参数 
-  :占位符
-  页面必需的参数,使用内嵌参数 params // router/index.js { path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', //使用占位符声明接收params参数 component:Detail } ] } ] }
-  传递参数 <!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="`/home/message/detail/${{item.id}}/${{item.title}}`">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', //params to的对象写法不能写 path name是路由的名字 params:{ id:666, title:'你好' } }" >跳转</router-link>
-  接收参数: // Detail.vue $route.params.id $route.params.title
7.路由的props配置
 作用:让路由组件更方便的收到参数
//  router/index.js
{
	name:'xiangqing',
	path:'detail/:id',
	component:Detail,
	//第一种写法:props值为对象,该对象中所有的`key-value`的组合最终都会通过props传给Detail组件(传递死数据,不常用)
	// props:{a:900,b:10} // Detail.vue -> props:['a','b']
	//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有`params`参数通过props传给Detail组件(query参数收不到
	// props:true
	
	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件 (推荐)
	props({ query }){ // $route
		return {
			id:route.query.id,
			title:route.query.title
		}
	}
    // 连续解构赋值
    props({ params: { id, title } }) {
      return { id: id, title: title }
    }
}
8.<router-link>的replace属性
 
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
- 如何开启replace模式:<router-link replace>News</router-link>
9.编程式路由导航
-  作用:不借助 <router-link>实现路由跳转,让路由跳转更加灵活
-  具体编码: //$router的两个API this.$router.push({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.replace({ name:'xiangqing', // 可以任意指定name跳转 params:{ id:xxx, title:xxx } }) this.$router.forward() //前进 this.$router.back() //后退 this.$router.go() //可前进也可后退 $route: 路由规则 $router: 路由器
10.缓存路由组件
-  作用:让不展示的路由组件保持挂载,不被销毁。 
-  具体编码: <keep-alive include="News"> <router-view></router-view> </keep-alive>
11.两个新的生命周期钩子
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
- 具体名字: 
  - activated路由组件被激活时触发。
- deactivated路由组件失活时触发。
 
12.路由守卫
-  作用:对路由进行权限控制 
-  分类:全局守卫、独享守卫、组件内守卫 
-  全局守卫: //全局前置守卫:初始化时执行、每次路由切换前执行 // 在需要鉴权的路由规则下: meta:{ isAuth:true } router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则 next() //放行 }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() //放行 } }) //全局后置守卫:初始化时执行、每次路由切换后执行 // 作用:点击改变页面 title // routes: meta:{title:'about'} router.afterEach((to,from)=>{ console.log('afterEach',to,from) if(to.meta.title){ document.title = to.meta.title //修改网页的title }else{ document.title = 'vue_test' } })
-  独享守卫: beforeEnter(to,from,next){ console.log('beforeEnter',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'atguigu'){ next() }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() } }
-  组件内守卫: //进入守卫:通过`路由规则`,进入该组件时被调用 beforeRouteEnter (to, from, next) { }, //离开守卫:通过`路由规则`,离开该组件时被调用 beforeRouteLeave (to, from, next) { next() }
13.路由器的两种工作模式
1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
3. hash模式:
   1. 地址中永远带着#号,不美观 。
   2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
   3. 兼容性较好。
   
4. history模式:
   1. 地址干净,美观 。
   2. 兼容性和hash模式相比略差。
   3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。// nginx
   
// router:
   mode:'hash/history'

路由404
{path:'*',component:NotFound}
声明式导航 - 两个类名
router-link-active: 激活的导航链接   模糊匹配
	to="/my"  可以匹配   /my      /my/a      /my/b    ....
    
router-link-exact-active:  激活的导航链接 精确匹配
	to="/my"  仅可以匹配   /my
Vue封装的过度与动画
-  作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。 
-  写法: -  准备好样式: - 元素进入的样式: 
      - v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
 
- 元素离开的样式: 
      - v-leave:离开的起点
- v-leave-active:离开过程中
- v-leave-to:离开的终点
 
- v 即 <transition>里的name- .hello-enter-active
 
 
- 元素进入的样式: 
      
-  使用 <transition>包裹要过度的元素,并配置name属性:<transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition>
-  备注:若有多个元素需要过度,则需要使用: <transition-group>,且每个元素都要指定key值。
 
-  
vue脚手架配置代理
方法一
 在vue.config.js中添加如下配置:
devServer:{
  proxy:"http://localhost:5000"
}
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
- 8080 请求 5000
方法二
 编写vue.config.js配置具体代理规则:
module.exports = {
	devServer: {
      proxy: {
      '/api1': {// 匹配所有以 '/api1'开头的请求路径
        target: 'http://localhost:5000',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api1': ''}
      },
      '/api2': {// 匹配所有以 '/api2'开头的请求路径
        target: 'http://localhost:5001',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api2': ''}
      }
    }
  }
}
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
⭐️Vue3
ES6模块化与异步编程高级用法
ES6模块化
定义:
-  每个 js 文件都是一个独立的模块 
-  导入其它模块成员使用 import 关键字 
-  向外共享模块成员使用 export 关键字 
基于 node.js 学习 ES6需配置:
- v14.15.1以上版本的 node.js
- 在 package.json 的根节点中添加 “type”:“module”
语法:
① 默认导出与默认导入
- 默认导出: 
  - 语法:export default{ 值类型成员,模块私有方法... }(❗️export default只能使用一次)
 
- 语法:
- 默认导入: 
  - import 接收名称 from '模块标识符'
 
② 按需导出与按需导入
-  按需导入 - import { s1,say } from '模块标识符'
 
-  按需导出 - export let s1 = 'ben';export function...
 
-  注意: ① 每个模块中可以使用多次按需导出 ② 按需导入的成员名称必须和按需导出的名称保持一致 ③ 按需导入时,可以使用 as 关键字进行重命名 ④ 按需导入可以和默认导入一起使用 
③ 直接导入并执行模块中的代码
import '模块标识符'
⭐️Promise
回调地狱
缺点:
- 代码耦合性太强,牵一发而动全身,难以维护
- 大量冗余的代码相互嵌套,代码的可读性变差
解决回调地狱–promise
基本概念
- const p = new Promise()// new实例 代表一个异步操作
- 实例可以通过 原型链 的方式访问到 .then() 方法,p.then()
- p.then(成功的回调函数【必选】,失败的回调函数【可选】) 
  - p.then(result => { }, error => { })
 
基于回调函数按顺序读取文件内容

基于 then-fs 读取文件内容
1 import thenFs from 'then-fs'
2 thenFs.readFile('./1.txt','utf8').then(r1 => {log r1},err1 => {log err1.message})(重复写)
注意:失败回调可选,无法保证文件读取顺序
基于 Promise 按顺序读取文件的内容
import thenFs from 'then-fs'
thenFs.readFile('./1.txt','utf8').then((r1) => {
    return thenFs.readFile('./2.txt','utf8')
}).then((r2) => {
    return thenFs.readFile('./3.txt','utf8')
}).then((r3) => {
    console.log(r3)
}).catch(err => {
    console.log(err.message)
})
通过 .catch 捕获错误
(...).catch(err => {
    console.log(err.message)
})
如果不希望前面的错误导致后续的 .then 无法正常执行,则可以将 .catch 的调用提前
thenFs.readFile('./1.txt','utf8').catch(err => { console.log(err.message) }).then...
- 捕获前面发生的错误,并输出错误的信息
- 由于错误已被及时处理,不影响后面 .then 的正常进行
Promise.all()方法
Promise.race() 方法
基于 Promise 封装读文件的方法
function getFile(fpath){
    return new Promise(function(resolve,reject){
        fs.readFile(fpath,'utf8',(err,dataStr)=>{
            if(err) return reject(err)
            resolve(dataStr)
        })
    })
}
getFile('./1.txt').then(resolve,reject)
async/await
import thenFs from 'then-fs'
async function getAllFile(){
    const r1 = await thenFs.readFile('./1.txt')
    const r2 = await thenFs.readFile('./2.txt')
    const r3 = await thenFs.readFile('./3.txt')
}
getAllFile()
❗️注意
① 如果在 function 中使用了 await,则 function 必须被 async 修饰
② 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行
⭐️EventLoop
JavaScript 是单线程的语言
问题:
- 如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题。
  
同步任务和异步任务
① 同步任务(synchronous)
-  又叫做非耗时任务,指的是在主线程上排队执行的那些任务 
-  只有前一个任务执行完毕,才能执行后一个任务 
② 异步任务(asynchronous)
-  又叫做耗时任务,异步任务由 JavaScript 委托给宿主环境进行执行 
-  当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数 
同步任务和异步任务的执行过程
 
EventLoop 的基本概念
宏任务和微任务
js 主线程先执行完 宏任务 之后再判断有无微任务,没有再执行下一个 宏任务
① 宏任务(macrotask)
-  异步 Ajax 请求、 
-  setTimeout、setInterval、 
-  文件操作 
-  其它宏任务 
② 微任务(microtask)
-  Promise.then、.catch 和 .finally 
-  process.nextTick 
-  其它微任务 
宏任务和微任务的执行顺序
 


 
前端工程化
- 前端工程化的解决方案 
  - webpack(打包构建)
- parcel
 
webpack
- 解决 浏览器不兼容 ES6 语法
Vue的基本使用
Vue全家桶
- vue(核心库)
- vue-router(路由方案)
- vuex(状态管理方案)
- vue 组件库(快速搭建页面 UI 效果的方案)
工具:
vue-cli、vite、vue-devtools、vetur
Vue 特性
-  数据驱动视图 
-  双向数据绑定 
Vue3 新增功能:
- 组合式API、多根节点组件、更好的TypeScript支持
废弃的旧功能:
-  过滤器、不再支持 o n 、 on 、 on、off 和 $once 实例方法等 
-  迁移指南 
过滤器(已剔除)
可以用在:
-  插值表达式 {{msg | capitalize}}
-  v-bind 属性绑定 :id="rawId | formatId"
组件基础
单页面应用程序(SPA)
SPA 特点:
-  将所有的功能局限于一个 web 页面中,仅在该 web 页面初始化时加载相应的资源( HTML、JavaScript 和 CSS)。 
-  一旦页面加载完成了,SPA 不会因为用户的操作而进行页面的重新加载或跳转。而是利用 JavaScript 动态地变换 HTML 的内容,从而实现页面与用户的交互。 
SPA 优点:
- 1 良好的交互体验 
  - 单页应用的内容的改变不需要重新加载整个页面
- 获取数据也是通过 Ajax 异步获取
- 没有页面之间的跳转,不会出现“白屏现象”
 
- 2 良好的前后端工作分离模式 
  - 后端专注于提供 API 接口,更易实现 API 接口的复用
- 前端专注于页面的渲染,更利于前端工程化的发展
 
- 3 减轻服务器的压力 
  - 服务器只提供数据,不负责页面的合成与逻辑的处理,吞吐能力会提高几倍
 
SPA 缺点:
- 1 首屏加载慢 
  - 路由懒加载
- 代码压缩
- CDN 加速
- 网络传输压缩
 
- 2 不利于 SEO 
  - SSR 服务器端渲染
 
两种快速创建工程化的 SPA 项目的方式:
| vite | vue-cli | |
|---|---|---|
| 支持版本 | 仅支持 vue3 | 支持 3.x 和2.x | 
| 基于 webpack? | no | yes | 
| 运行速度 | 快 | 较慢 | 
| 功能完整度 | 小而巧(逐渐完善 | 大而全 | 
| 建议在企业级开发中使用? | 目前不建议 | 建议 | 
组件化开发思想
好处:
-  提高了前端代码的复用性和灵活性 
-  提升了开发效率和后期的可维护性 
vue 是一个完全支持组件化开发的框架。vue 中规定组件的后缀名是 .vue。
vue 组件组成结构
- template -> 组件的模板结构(必选)
- script -> 组件的 JavaScript 行为(可选) 
  - <script> export default { name,data数据、methods方法...} </script>
- name 节点为当前组件定义一个名称(建议每个单词首字母大写 
    - name:'MyApp'
- 在注册组件时:app.component(Swiper.name,Swiper)
 
- data必须是一个函数,不能直接指向一个数据对象 
    - data(){return {}}
 
- methods:{ 事件处理函数 }
- components:{ 'my-search':Search,}
 
- style -> 组件的样式(可选) 
  - <style lang="css"> h1{...}</style>
- lang="css"(默认)还有 less(npm i less -D)、scss
 
在 template 中定义根节点:
-  在 vue2 中,节点内最外层必须由 唯一 的单个根节点包裹 
-  在 vue3 中,中支持定义多个根节点 
组件的基本使用:
注册组件
-  全局注册 - app.component('my-swiper',Swiper)
- 直接在template中以标签形式使用 <my-swiper>
- 使用频率高
 
-  局部注册 -  <script> import Search for '' export default{ components:{ 'my-search':Search, } }
-  只有特点情况下会被用到 
 
-  
组件注册时名称
- kebab-case 命名法(俗称短横线命名法,例如 my-swiper)
- ⭐️PascalCase 命名法(俗称帕斯卡命名法或大驼峰命名法,例如 MySwiper) 
  - 可以转化成 短横线命名法
 
组件之间的样式冲突问题
-  为每个组件分配唯一的自定义属性,在编写样式时,通过属性选择器来控制样式的作用域 <template> <div class="container" data-v-001> <h3 data-v-001>轮播图组件<h3> </div> </template> <style> .container[data-v-001]{ border:1px solid red; } </style>
-  style 节点的 scoped 属性 <style scoped> .container{ border:1px solid red; } </style>
-  /deep/样式穿透 <style lang="less" scoped> /deep/ .title{ color:blue; } </style>注意:/deep/ 是 vue2.x 中实现样式穿透的方案。在 vue3.x 中推荐使用 :deep() 替代 /deep/。 
全局样式:
1 另外写一个 index.css 文件
2 在 main.js 中引入
组件的 props
为了提高组件的复用性,封装组件时要遵守:
- 组件的 DOM 结构、Style 样式要尽量复用
- 组件中要展示的数据,尽量由组件的使用者提供
props 的作用:父组件通过 props 向子组件传递要展示的数据。
props 的好处:提高了组件的复用性。
在组件中声明 props
动态绑定 props 的值
可以使用 v-bind 属性绑定的形式,为组件动态绑定 props 的值
props 的大小写命名
组件中如果使用“camelCase (驼峰命名法)”声明了 props 属性的名称,则有两种方式为其绑定属性的值:
1 pubTime = '1889'
2 pub-time = '1889'(推荐)
Class 与 Style 绑定
动态绑定 HTML 的 class
1 可以通过三元表达式,动态的为元素绑定 class 的类名。
:class = "isItalic ? 'isItalic' : ''"
- 一定要记得带 ''
2 以数组语法绑定 HTML 的 class
:class = "[isItalic ? 'isItalic' : '',isDelete ? 'delete' : '']"
3 以对象语法绑定 HTML 的 class
语法::class="{类名:布尔值}"
:class = "classObj" classObj:{italic:true,delete:false}
4 以对象语法绑定内联的 style
语法::style="样式属性名:样式的值"
- 样式名要用 小驼峰 命名or 引号
:style="{color:active},fontSize:fsize+'px'"
:style= "styleObj"
props 验证
对象类型的 props 节点
① 基础的类型检查
- Srting Number Boolean Array Object Date Function Symbol
② 多个可能的类型
- [String,Number,…]
③ 必填项校验
- required:true
④ 属性默认值
- defult:‘’/0 (注意类型要和 type 的相同)
⑤ 自定义验证函数

计算属性
声明计算属性
① 计算属性必须定义在 computed 节点中
② 计算属性必须是一个 function 函数
③ 计算属性必须有返回值
④ 计算属性必须当做普通属性使用
计算属性 vs 方法
相对于方法来说,计算属性会缓存计算的结果,只有计算属性的依赖项发生变化时,才会重新进行运算。因此计算属性的性能更好
自定义事件
在封装组件时:
① 声明自定义事件
- 在 emits 节点中声明 emits:['change']
② 触发自定义事件
-  methods:{ onBtnClick(){ this.$emit('change') } }
在使用组件时:
③ 监听自定义事件
- @change='getCount'
自定义事件传参
- this.$emit('change',this.count)
自定义事件解绑
- 一个事件解绑: 
  - this.$off('自定义事件名称')
 
- 多个事件解绑 
  - this.$off('xxx','yyy')
- this.$off()
 
组件上的 v-model
步骤:
父 => 子
- 父组件 
  - :number='count'
 
- 子组件 
  - props:['number']
 
子 => 父
- 父组件 
  - v-model:number='count'(不行)
 
- 子组件 
  - props:['number']
- emits:['update:number']
- this.$emit('update:number',this.number+1)
 
或者:
// 父组件
<aa-input v-model="aa"></aa-input>
// 等价于
<aa-input :value="aa" @input="aa=$event.target.value"></aa-input>
(父组件不用自己拿子组件传过来的数据进行赋值了)
// 子组件:
<input :value="aa" @input="onmessage"></aa-input>
props:['value']
methods:{
    onmessage(e){
        this.$emit('input',e.target.value)
    }
}
组件高级
组件的生命周期
| 生命周期函数 | 执行时机 | 所属阶段 | 执行次数 | 应用场景 | 
|---|---|---|---|---|
| beforeCreate | 在内存中开始创建组件之前 | 创建阶段 | 唯一1次 | - | 
| ⭐️created | 组件在内存中创建完毕后 | 创建阶段 | 唯一1次 | ajax 请求数据 | 
| beforeMount | 在把组件初次渲染到页面之前 | 创建阶段 | 唯一1次 | |
| ⭐️mounted | 组件初次在页面中渲染完毕后 | 创建阶段 | 唯一1次 | 操作 DOM | 
| beforeUpdate | 在组件被重新渲染之前 | 运行阶段 | 0 或 多次 | |
| updated | 组件在页面中被重新渲染完毕后 | 运行阶段 | 0 或 多次 | |
| beforeUnmount | 在组件被销毁之前 | 销毁阶段 | 唯一1次 | |
| ⭐️unmounted | 组件被销毁后(页面和内存) | 销毁阶段 | 唯一1次 | 
组件之间的数据共享
1. 组件之间的关系
① 父子关系
② 兄弟关系
③ 后代关系
2. 父子组件之间的数据共享
① 父 -> 子共享数据
- 父 
  - :msg="massage" :user='userinfo'
 
- 子 
  - props:['msg','user']
 
② 子 -> 父共享数据
- 父 
  - :change='getNum'
 
- 子 
  - emits:['change']
- this.$emit('change',this.num)
 
③ 父 <-> 子双向数据同步
- 父 
  - v-model:number='count'
 
- 子 
  - props:['number']
- emits:['update:props的名字']
- this.emits('update:props的名字',this.number+1)
 
3. 兄弟组件之间的数据共享

1 安装 mitt 依赖包
npm install mitt@2.1.0 -S
2 创建公共的 eventBus 模块
import mitt from 'mitt'
const bus = mitt()
export default bus
3 在数据接收方自定义事件
import bus from './eventBus.js'
export default{
    data(){return { count:0 }}
    created(){
        bus.on('countChange',(num)=> this.count = num)
    }
}
4 在数据接发送方触发事件
bus.emit('countChange',this.count)
4. 后代关系组件之间的数据共享
-  爷节点 - 通过 provide 共享数据
- provide(){ return { color: this.color } }
 
-  子孙节点 - 通过 inject 接收数据
- inject:['color']
 
-  爷节点对外共享响应式的数据 -  结合 computed 函数向下共享响应式的数据 
-  import { computed } from 'vue' export default{ data(){ return { color:'red' } }, provide(){ return color:computed(()=> this.color) } }
 
-  
-  子孙节点使用响应式的数据 - inject:['color']
- 必须以.value的形式进行使用- {{ color.value }}
 
 
5. vuex
- 全局数据共享
全局配置 axios
在 main.js 入口文件中,通过 app.config.globalProperties 全局挂载 axios
axios.defaults.baseURL = 'http://api.com'
app.config.globalProperties.$http = axios
ref 引用
标签上写:ref=mybtn
this.$refs.mybtn.style.color = 'red'
引用到组件的实例之后,就可以调用组件上的 methods 方法
this.$refs.mybtn.add()
控制文本框和按钮的按需切换
通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换。
v-if="inputVisible " v-else
让文本框自动获得焦点
this.$nextTick(() => {
    this.$ref.ipt.focus() // 异步
})
this.$nextTick(cb) 方法
动态组件
<component is="要渲染的组件的名称"></component>
使用 keep-alive 保持状态
默认情况下,切换动态组件时无法保持组件的状态。
<keep-alive>
    <component is="要渲染的组件的名称"></component>
</keep-alive>
插槽
// 封装组件
<template>
    <slot></slot>
</template>
// 注册组件
<myCom>
    <p>用户自定义内容</p>
</myCom>
自定义指令
路由
Vuex
何时使用?
多个组件需要共享数据时
原理图:

搭建vuex环境
-  创建文件: src/store/index.js//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex) //准备actions对象——响应组件中用户的动作 const actions = {} //准备mutations对象——修改state中的数据 const mutations = {} //准备state对象——保存具体的数据 const state = {} //创建并暴露store export default new Vuex.Store({ actions, mutations, state })
-  在 main.js中创建vm时传入store配置项...... //引入store import store from './store' ...... //创建vm new Vue({ el:'#app', render: h => h(App), store })
4.基本使用
-  初始化数据、配置 actions、配置mutations,操作文件store/index.js//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //引用Vuex Vue.use(Vuex) const actions = { //响应组件中加的动作 jia(context,value){ // console.log('actions中的jia被调用了',miniStore,value) context.commit('JIA',value) }, } const mutations = { //执行加 JIA(state,value){ // console.log('mutations中的JIA被调用了',state,value) state.sum += value } } //初始化数据 const state = { sum:0 } //创建并暴露store export default new Vuex.Store({ actions, mutations, state, })
-  组件中读取vuex中的数据: $store.state.sum 1 在模板里面写不用加 this {{}}  2 在脚本里面写要加 this 
-  组件中修改vuex中的数据: $store.dispatch('action中的方法名',数据)或$store.commit('mutations中的方法名',数据)
5.getters的使用
-  概念:当state中的数据需要经过 加工后再使用时,可以使用getters加工。
-  在 store.js中追加getters配置const getters = { bigSum(state){ return state.sum * 10 } }
-  组件中读取数据: $store.getters.bigSum
6.四个map方法的使用
-  mapState方法:用于帮助我们映射 state中的数据为计算属性computed: { //借助mapState生成计算属性:sum、school、subject(对象写法) ...mapState({sum:'sum',school:'school',subject:'subject'}), 简化:sum:state => state.sum... //借助mapState生成计算属性:sum、school、subject(数组写法) ...mapState(['sum','school','subject']), },
-  mapGetters方法:用于帮助我们映射 getters中的数据为计算属性computed: { //借助mapGetters生成计算属性:bigSum(对象写法) ...mapGetters({bigSum:'bigSum'}), //借助mapGetters生成计算属性:bigSum(数组写法) ...mapGetters(['bigSum']) },
-  mapActions方法:用于帮助我们生成与 actions对话的方法,即:包含$store.dispatch(xxx)的函数methods:{ //靠mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) //靠mapActions生成:incrementOdd、incrementWait(数组形式) ...mapActions(['jiaOdd','jiaWait']) }
-  mapMutations方法:用于帮助我们生成与 mutations对话的方法,即:包含$store.commit(xxx)的函数methods:{ //靠mapActions生成:increment、decrement(对象形式) ...mapMutations({increment:'JIA',decrement:'JIAN'}), //靠mapMutations生成:JIA、JIAN(对象形式) ...mapMutations(['JIA','JIAN']), }
7.Modules 模块化+命名空间
命名空间 namespaced: true
 
 
computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
    
methods: {
    ...mapActions('moduleA', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
    ])
}
namespaced: true + createNamespacedHelpers
 
 
1 import { createNamespacedHelpers } from 'vuex'
2 const { mapState, mapActions } = createNamespacedHelpers('moduleB')
-  目的:让代码更好维护,让多种数据分类更加明确。 
-  修改 store.jsconst countAbout = { namespaced:true,//开启命名空间 state:{x:1}, mutations: { ... }, actions: { ... }, // context 指向当前模块 getters: { bigSum(state){ return state.sum * 10 } } } const personAbout = { namespaced:true,//开启命名空间 state:{ ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { countAbout, personAbout } })
-  开启命名空间后,组件中读取state数据: //方式一:自己直接读取 this.$store.state.personAbout.list //方式二:借助mapState读取: ...mapState('countAbout',['sum','school','subject']),
-  开启命名空间后,组件中读取getters数据: //方式一:自己直接读取 this.$store.getters['personAbout/firstPersonName'] //方式二:借助mapGetters读取: ...mapGetters('countAbout',['bigSum'])
-  开启命名空间后,组件中调用dispatch //方式一:自己直接dispatch this.$store.dispatch('personAbout/addPersonWang',person) //方式二:借助mapActions: ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
-  开启命名空间后,组件中调用commit //方式一:自己直接commit this.$store.commit('personAbout/ADD_PERSON',person) //方式二:借助mapMutations: ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
路由
- 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
- 前端路由:key是路径,value是组件。
1.基本使用
-  安装vue-router,命令: npm i vue-router
-  应用插件: Vue.use(VueRouter)
-  编写router配置项: //引入VueRouter import VueRouter from 'vue-router' //引入Luyou 组件 import About from '../components/About' import Home from '../components/Home' //创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] }) //暴露router export default router
-  实现切换(active-class可配置高亮样式) <router-link active-class="active" to="/about">About</router-link>
-  指定展示位置 <router-view></router-view>
2.几个注意点
- 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
- 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的$route属性,里面存储着自己的路由信息。
- 整个应用只有一个router,可以通过组件的$router属性获取到。
3.多级路由(多级路由)
-  配置路由规则,使用children配置项: routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 { path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component:Message } ] } ]
-  跳转(要写完整路径): <router-link to="/home/news">News</router-link>
4.路由的query参数
-  传递参数 <!-- 跳转并携带query参数,to的字符串写法 --> <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link> <!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }" >跳转</router-link>
-  接收参数: $route.query.id $route.query.title
5.命名路由
-  作用:可以简化路由的跳转。 
-  如何使用 -  给路由命名: { path:'/demo', component:Demo, children:[ { path:'test', component:Test, children:[ { name:'hello' //给路由命名 path:'welcome', component:Hello, } ] } ] }
-  简化跳转: <!--简化前,需要写完整的路径 --> <router-link to="/demo/test/welcome">跳转</router-link> <!--简化后,直接通过名字跳转 --> <router-link :to="{name:'hello'}">跳转</router-link> <!--简化写法配合传递参数 --> <router-link :to="{ name:'hello', query:{ id:666, title:'你好' } }" >跳转</router-link>
 
-  
6.路由的params参数
-  配置路由,声明接收params参数 { path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', //使用占位符声明接收params参数 component:Detail } ] } ] }
-  传递参数 <!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/666/你好">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:666, title:'你好' } }" >跳转</router-link>
-  接收参数: $route.params.id $route.params.title
7.路由的props配置
 作用:让路由组件更方便的收到参数
{
	name:'xiangqing',
	path:'detail/:id',
	component:Detail,
	//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
	// props:{a:900}
	//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
	// props:true
	
	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
	props(route){
		return {
			id:route.query.id,
			title:route.query.title
		}
	}
}
8.<router-link>的replace属性
 
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
- 如何开启replace模式:<router-link replace .......>News</router-link>
9.编程式路由导航
-  作用:不借助 <router-link>实现路由跳转,让路由跳转更加灵活
-  具体编码: //$router的两个API this.$router.push({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.replace({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.forward() //前进 this.$router.back() //后退 this.$router.go() //可前进也可后退
10.缓存路由组件
-  作用:让不展示的路由组件保持挂载,不被销毁。 
-  具体编码: <keep-alive include="News"> <router-view></router-view> </keep-alive>
-  注意❗️ - 找到最根的组件 加
- include 里面的名字为 组件名
 
11.两个新的生命周期钩子
由于组件没有被销毁,所以。。。
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
- 具体名字: 
  - activated路由组件被激活时触发。- 相当于 mounted 的功能
- 设置定时器
- …
 
- deactivated路由组件失活时触发。- 相当于 beforeDestroy 的功能
- 清除定时器
- …
 
 
12.路由守卫
-  作用:对路由进行权限控制 
-  分类:全局守卫、独享守卫、组件内守卫 
-  全局守卫: //全局前置守卫:初始化时执行、每次路由切换前执行 router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则 next() //放行 }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() //放行 } }) //全局后置守卫:初始化时执行、每次路由切换后执行 router.afterEach((to,from)=>{ console.log('afterEach',to,from) if(to.meta.title){ document.title = to.meta.title //修改网页的title }else{ document.title = 'vue_test' } })
-  独享守卫: beforeEnter(to,from,next){ console.log('beforeEnter',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'atguigu'){ next() }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() } }
-  组件内守卫: //进入守卫:通过路由规则,进入该组件时被调用 beforeRouteEnter (to, from, next) { }, //离开守卫:通过路由规则,离开该组件时被调用 beforeRouteLeave (to, from, next) { }
13.路由器的两种工作模式
1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
3. hash模式:
   1. 地址中永远带着#号,不美观 。
   2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
   3. 兼容性较好。
4. history模式:
   1. 地址干净,美观 。
   2. 兼容性和hash模式相比略差。
   3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
UI组件库
移动端常用 UI 组件库
Vant
Cube UI
Mint UI
PC 端常用 UI 组件库
Element UI
IView UI
案例分析
TodoList1
组件化编码流程:
 (1) 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
 (2) 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
 1) 一个组件在用:放在组件自身即可。
 2) 一些组件在用:放在他们共同的父组件上(状态提升)。eg: 读 or 写
 (3) 实现交互:从绑定事件开始。
props适用于:
 (1) 父组件 ==> 子组件 通信
 (2) 子组件 ==> 父组件 通信(要求父先给子一个函数)
技巧:
 1 使用 reduce 累计
computed: {
	doneTotal() {
      return this.todos.reduce((pre, current) => pre + (current.isDone ? 1 : 0), 0)
    }
}
 2 使用 filter 过滤
clearDone() {
      this.todos = this.todos.filter(todo => !todo.isDone)
    }
 3 巧用 计算 属性
computed: {
    isAll() {
      return this.allNum ? this.allChecked : false
    }
  }
 4 使用 every 判断
allChecked() {
      return this.todos.every(todo => todo.isDone === true)
    }
 5 watch 监视数据 进行存储
todos: localStorage.getItem('todos')
        ? JSON.parse(localStorage.getItem('todos'))
        : [...]
           
watch: {
    todos(newValue) {
      localStorage.setItem('todos', JSON.stringify(newValue))
    }
  }
注意:
使用 v-model 时要切记:v-model 绑定的值不能是props传过来的值,因为props是不可以修改的!
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
⭐️杂记
cmtCount: {
    // 通过数组的形式 , 为当前属性定义多个可能的类型
  type: [Number, String],
  default: 0
}
- 在使用组件时,如果某个属性名是“小驼峰”形式,则绑定属性时,建议改写成“连字符”格式,例如: 
  - cmtCount => cmt-count
 
❗️ 修改配置项一定要重新 run 一下项目
EsLint:
'space-before-function-paren': 0
两个重要的原则:
- 由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
- 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数, 这样this的指向才是vm 或 组件实例对象。
v-model.number.lazy
组件化编码流程(通用)
1 实现静态组件
- 抽取组件,使用组件实现静态页面效果
2 展示动态数据
- 2.1 数据类型、名称?
- 2.2 数据保存在哪个组件?
3 交互——从绑定事件监听开始
nanoid
1 安装 npm i nanoid --force
2 import { nanoid } from 'nanoid'
3 id: nanoid()
bootstrap
1 npm i bootstrap
2 // main.js
import 'bootstrap/dist/css/bootstrap.css'
巧用 boolean
不只是 true or false,还可以用来 筛选
条件
eg: :class="{ active: score > 60}"
Vscode 新建文件
// 终端
ni 'xxx'
表单渲染
循环 带有 label标签的 for 和 id 要唯一
:id="'xx'+item.id" :for="'xx'+item.id"
axios
基本语法:
axios发请求的基本语法:
    axios({
        url:'url',
        method:'get/post/put/delete',
        params:{}, //  包含 query 参数对象,问号后面的参数
        data:{}, // 包含请求体参数的对象
    })
    axios.get(url,{配置}) // { params:{id:1} }
    axios.delete(url, {配置})
    axios.post(url, data数据对象)
    axios.put(url, data数据对象)
    使用axios发ajax请求携带参数:
    params参数: 只能拼在路径中: /admin/product/baseTrademark/delete/1
    query参数: 
      拼在路径中的?后面: /admin/product/baseTrademark?id=1
      通过params配置来指定: axios({params: {id: 1}})
    请求体参数: 
      通过data配置或post()/put()的第二个参数指定
babel
e = to.meta.title //修改网页的title
 }else{
 document.title = ‘vue_test’
 }
 })
4. 独享守卫:
```js
beforeEnter(to,from,next){
	console.log('beforeEnter',to,from)
	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
		if(localStorage.getItem('school') === 'atguigu'){
			next()
		}else{
			alert('暂无权限查看')
			// next({name:'guanyu'})
		}
	}else{
		next()
	}
}
-  组件内守卫: //进入守卫:通过路由规则,进入该组件时被调用 beforeRouteEnter (to, from, next) { }, //离开守卫:通过路由规则,离开该组件时被调用 beforeRouteLeave (to, from, next) { }
13.路由器的两种工作模式
1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
3. hash模式:
   1. 地址中永远带着#号,不美观 。
   2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
   3. 兼容性较好。
4. history模式:
   1. 地址干净,美观 。
   2. 兼容性和hash模式相比略差。
   3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
UI组件库
移动端常用 UI 组件库
Vant
Cube UI
Mint UI
PC 端常用 UI 组件库
Element UI
IView UI
案例分析
TodoList1
组件化编码流程:
 (1) 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
 (2) 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
 1) 一个组件在用:放在组件自身即可。
 2) 一些组件在用:放在他们共同的父组件上(状态提升)。eg: 读 or 写
 (3) 实现交互:从绑定事件开始。
props适用于:
 (1) 父组件 ==> 子组件 通信
 (2) 子组件 ==> 父组件 通信(要求父先给子一个函数)
技巧:
 1 使用 reduce 累计
computed: {
	doneTotal() {
      return this.todos.reduce((pre, current) => pre + (current.isDone ? 1 : 0), 0)
    }
}
 2 使用 filter 过滤
clearDone() {
      this.todos = this.todos.filter(todo => !todo.isDone)
    }
 3 巧用 计算 属性
computed: {
    isAll() {
      return this.allNum ? this.allChecked : false
    }
  }
 4 使用 every 判断
allChecked() {
      return this.todos.every(todo => todo.isDone === true)
    }
 5 watch 监视数据 进行存储
todos: localStorage.getItem('todos')
        ? JSON.parse(localStorage.getItem('todos'))
        : [...]
           
watch: {
    todos(newValue) {
      localStorage.setItem('todos', JSON.stringify(newValue))
    }
  }
注意:
使用 v-model 时要切记:v-model 绑定的值不能是props传过来的值,因为props是不可以修改的!
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
⭐️杂记
cmtCount: {
    // 通过数组的形式 , 为当前属性定义多个可能的类型
  type: [Number, String],
  default: 0
}
- 在使用组件时,如果某个属性名是“小驼峰”形式,则绑定属性时,建议改写成“连字符”格式,例如: 
  - cmtCount => cmt-count
 
❗️ 修改配置项一定要重新 run 一下项目
EsLint:
'space-before-function-paren': 0
两个重要的原则:
- 由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
- 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数, 这样this的指向才是vm 或 组件实例对象。
v-model.number.lazy
组件化编码流程(通用)
1 实现静态组件
- 抽取组件,使用组件实现静态页面效果
2 展示动态数据
- 2.1 数据类型、名称?
- 2.2 数据保存在哪个组件?
3 交互——从绑定事件监听开始
nanoid
1 安装 npm i nanoid --force
2 import { nanoid } from 'nanoid'
3 id: nanoid()
bootstrap
1 npm i bootstrap
2 // main.js
import 'bootstrap/dist/css/bootstrap.css'
巧用 boolean
不只是 true or false,还可以用来 筛选
条件
eg: :class="{ active: score > 60}"
Vscode 新建文件
// 终端
ni 'xxx'
表单渲染
循环 带有 label标签的 for 和 id 要唯一
:id="'xx'+item.id" :for="'xx'+item.id"
axios
基本语法:
axios发请求的基本语法:
    axios({
        url:'url',
        method:'get/post/put/delete',
        params:{}, //  包含 query 参数对象,问号后面的参数
        data:{}, // 包含请求体参数的对象
    })
    axios.get(url,{配置}) // { params:{id:1} }
    axios.delete(url, {配置})
    axios.post(url, data数据对象)
    axios.put(url, data数据对象)
    使用axios发ajax请求携带参数:
    params参数: 只能拼在路径中: /admin/product/baseTrademark/delete/1
    query参数: 
      拼在路径中的?后面: /admin/product/baseTrademark?id=1
      通过params配置来指定: axios({params: {id: 1}})
    请求体参数: 
      通过data配置或post()/put()的第二个参数指定
babel
- 语法降级










