文章目录

  • 1.Vue2基础
    • 1.1.初始Vue
    • 1.2.模板语法
    • 1.3.数据绑定
    • 1.4.MVVM模型
    • 1.5.el和data的两种写法
    • 1.6.回顾defineProperty方法
    • 1.7.数据代理
    • 1.8.Vue中的数据代理
    • 1.9.事件处理
    • 1.10.计算属性
    • 1.11.监视属性(侦听属性)
    • 1.12.绑定样式
    • 1.13.条件渲染
    • 1.14.列表渲染
    • 1.15.列表过滤
    • 1.16.Vue检测数据变化的原理
    • 1.17.收集表单数据
    • 1.18.过滤器
    • 1.19.内置指令
    • 1.20.自定义指令
    • 1.21.生命周期
  • 2.Vue组件化
    • 2.1.创建非单文件组件
    • 2.2.组件的嵌套
    • 2.3.VueComponent构造函数
    • 2.4.重要的内置关系
    • 2.5.创建单文件组件
  • 3.Vue脚手架
    • 3.1.初始化脚手架
    • 3.2.目录说明
    • 3.3.利用脚手架实现2.5案例
    • 3.4.render
    • 3.5脚手架配置
    • 3.6.ref属性
    • 3.7.props配置项
    • 3.8.mixin
    • 3.9.插件
    • 3.10.scoped
    • 3.11.TodoList案例
    • 3.12.组件间数据传递
    • 3.13.WebStorage
    • 3.14.组件的自定义事件
    • 3.15全局事件总线
    • 3.16消息订阅与发布
    • 3.17.$nextTick
    • 3.18.过度和动画
  • 4.Vue中的AJAX
    • 4.1.使用axios
    • 4.2.跨域问题
    • 4.3.vue-resource
    • 4.4.插槽
  • 5.vuex
    • 5.1.vuex概念
    • 5.2.搭建vuex环境
    • 5.3.getters配置项
    • 5.4.四个map方法
    • 5.5.vuex的模块化
  • 6.路由
    • 6.1.路由的基本使用
    • 6.2.嵌套路由
    • 6.3.路由传参
    • 6.4.路由命名
    • 6.5.路由的props配置
    • 6.6.replace属性
    • 6.7.编程式路由导航
    • 6.8.缓存路由组件
    • 6.9.两个新的生命周期钩子
    • 6.10.路由守卫
    • 6.11.路由器的工作模式
  • 7.VueUI组件库
    • 7.1.移动端常用UI组件库
    • 7.2 PC 端常用UI组件库
    • 7.3.ElementUI简单使用
    • 7.4 按需引入
  • 8.Vue3
    • 8.1创建vue3.0工程
    • 8.2.常用Composition API
      • 8.2.1.setup函数
      • 8.2.2.ref函数
      • 8.2.3.reactive函数
      • 8.2.4.Vue3响应式原理
      • 8.2.5.reactive和ref的对比
      • 8.2.6.setup的两个注意点
      • 8.2.7.计算属性
      • 8.2.8监视属性
      • 8.2.9.Vue3生命周期
      • 8.2.10.自定义hook函数
      • 8.2.11.toRef和toRefs函数
    • 8.3.其他Composition API
      • 8.3.1.shallowRef和shallowReactive
      • 8.3.2.readonly和shallowReadonly
      • 8.3.3.toRaw和markRaw
      • 8.3.4.customRef
      • 8.3.5.provide和inject
      • 8.3.6.响应式数据的判断
      • 8.3.7.组合式API的优点
    • 8.4新的组件
      • 8.4.1.Fragment
      • 8.4.2.Teleport
      • 8.4.3.Suspense
    • 8.5其他
      • 8.5.1.全局API的转移
      • 8.5.2.其他改变

1.Vue2基础

Vue官网

1.1.初始Vue

<head><meta charset="UTF-8"><title>初始Vue</title><script type="text/javascript" src="../js/vue.js"></script></head><body><div id="root"><h1>hello {{name}}</h1></div><script type="text/javascript">//阻止Vue在启动时生成生产提示Vue.config.productionTip = false//创建Vue实例 参数是配置对象new Vue({el: '#root', //element用于指定当前Vue实例为哪个容器服务data: {name: 'Vue'}, //存储数据,提供给el指定容器使用})</script></body>

总结:

​ 1.想让Vue工作必须先创建Vue实例,并且传入配置对象

​ 2.容器中的代码依旧符合html规范

​ 3.Vue实例和容器只能一一对应

​ 4.{{xxx}}中的xxx要写js表达式,并且xxx可以自动读取data中的所有属性

1.2.模板语法

<body><div id="root"><h1>你好,{{name}}</h1><hr><a v-bind:href="web.url">点我跳转到{{web.name}}</a> <a :href="web.url.toUpperCase()">点我跳转到{{web.name}}</a></div><script>Vue.config.productionTip = falsenew Vue({el: '#root',data: {name: 'jack',web:{name: '百度',url: 'http://www.baidu.com'}}})</script></body>

总结:

​ Vue模板语法分为两大类:

​ 1.插值语法:{{xxx}} xxx为js表达式

​ 2.指令语法:v-xxx xxx为js表达式

1.3.数据绑定

<body><div id="root">单向数据绑定:<input type="text" v-bind:value="name">双向数据绑定:<input type="text" v-model:value="name">单向数据绑定:<input type="text" :value="name">双向数据绑定:<input type="text" v-model="name"></div><script>Vue.config.productionTip = falsenew Vue({el: '#root',data: {name:'单向绑定'}})</script></body>

总结:

Vue中有两种数据绑定的方式:

​ 1.单向绑定(v-bind):数据只能从data流向页面

​ 2.双向绑定(v-model):数据不仅能从data流向页面也能从页面流向data

​ v-bind:属性=”” —简写→ :属性=””

​ v-model:value=”” —简写→ v-model=””

1.4.MVVM模型


MVVM模型:

​ M(模型Model):对应data中的数据

​ V(试图View):模板

​ VM(视图模型ViewModel):Vue实例对象

总结:

​ data中的所有属性最后都出现在vm实例对象上

​ vm实例上的所有属性及Vue原型上所有属性都可以在Vue模板中直接使用

1.5.el和data的两种写法

el的两种写法

<script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root', data: {}})</script><script>Vue.config.productionTip = falseconst vm = new Vue({data: {}})vm.$mount("#root")</script>

data的两种写法

<script>Vue.config.productionTip = falseconst v = new Vue({el: '#root', data: {name: 'jack'}})</script><script>Vue.config.productionTip = falseconst v = new Vue({el: '#root',data: function(){//简写形式-> data(){...}return {name: 'jack'}}})</script>

总结:

el的两种写法:

​ 1.new Vue的时候配置el属性

​ 2.先创建Vue实例,然后再通过vm.$mount("选择器")指定容器

data的两种写法:

​ 1.对象式

​ 2.函数式

关于data函数式写法的重要原则:

​ 由Vue管理的函数只能写普通函数,不能使用箭头函数

​ 一旦写了箭头函数,this就不再指向Vue实例

1.6.回顾defineProperty方法

<body><script type="text/javascript">let num = 18let person = {name: "张三",sex: "男",// age: num 无法动态的修改age属性的值}//给person对象添加age属性Object.defineProperty(person,"age",{// value: 18,// writable:true,//控制属性是否可以被修改// enumerable: true,//控制属性是否可以枚举// configurable:true,//控制属性是否可以被删除get(){ //当读取person的age属性时自动调用return num;},set(value){ //当修改person的age属性时自动调用num = value}//get()和set()不能与value和writable属性共存})</script></body>

折叠代码

//#region//.....//#endregion

1.7.数据代理

<script type="text/javascript">let obj = {x:100}let obj2 = {y:200}Object.defineProperty(obj2,"x",{get(){return obj.x},set(value){obj.x = value}})</script>

在代理对象上通过Object.defineProperty()添加被代理对象的属性

通过obj2对象可以操作obj对象中的x属性,这就是数据代理

1.8.Vue中的数据代理

<script>Vue.config.productionTip = falselet data = {name: "张三",age: "20"}const vm = new Vue({el: '#root',data})</script>

验证vm中的_data就是配置对象中的data

vm上添加的getter/setter

如何实现数据代理” />
总结:

​ 通过vm对象代理data对象的中属性

​ 原理:

​ 通过Object.defineProperty()把data对象的所有属性添加到vm上

​ (data相当于上例中的obj对象 vm相当于上例中的obj2对象)

​ 为每个添加到vm对象上的属性提供getter 和 setter 方法

​ 在getter/setter方法内部操作data中对应的属性

1.9.事件处理

<body><div id="root"><button v-on:click="showInfo">点我显示信息</button><button @click="showInfo">点我显示信息</button><button @click="showInfo2($event,66)">点我显示信息(传参)</button></div><script>Vue.config.productionTip = falsenew Vue({el: '#root',data: {},methods: {showInfo(event){//默认传递event事件对象console.log(event);//事件对象console.log(this);//此处的this指向vm对象},showInfo2(event,number){console.log(event,number);}}})</script></body>

事件的基本使用:

​ 1.使用v-on:xxx@xxx绑定事件,xxx是事件名

​ 2.事件的回调函数需要配置在methods对象中,最终会出现在vm上

​ 3.methods中配置的函数不要使用箭头函数

​ 4.传递参数时,默认不传递event对象,如果需要传递event对象,必须使用$event传递

事件修饰符

事件修饰符说明
prevent阻止默认事件
stop阻止事件冒泡
once事件只触发一次
capture使用事件的捕获模式
self只有event.target是当前操作元素时才触发事件
passive事件的默认行为立即执行 无需等待回调函数执行完毕
<body><div id="root"><a href="http://www.baidu.com" @click.prevent="show">百度</a><a href="http://www.baidu.com" @click.prevent.once="show">百度</a></div><script>Vue.config.productionTip = falsenew Vue({el: '#root',data: {},methods: {show(e){//阻止默认行为,效果同 @click.prevent//e.preventDefault()alert('abc')}}})</script></body>

基础知识补充

css:overflow: auto; 空间不够显示滚动条事件:
      scroll: 滚动条滚动时触发事件wheel: 鼠标滚轮滚动时触发事件

      键盘事件

      Vue中常用的按键别名

      按键别名
      回车enter
      删除delete
      退出esc
      空格space
      换行tab
      上/下/左/右up / down / left / right

      特殊的键:

      ​ Tab只能配合keydown使用

      ​ 系统修饰键:ctrl、alt、shift、meta(win)

      ​ 配合keyup使用:按下修饰键的同时按下其他键,然后释放其他键,事件才能被触发

      ​ 配合keydown使用:正常触发事件

      <body><div id="root"><input type="text" placeholder="按下键盘显示提示" @keydown.enter="showTip"><input type="text" placeholder="按下键盘显示提示" @keydown.CapsLock="showTip"><input type="text" placeholder="按下键盘显示提示" @keydown.caps-lock="showTip"><input type="text" placeholder="按下Ctrl+Y显示提示" @keyup.ctrl.y="showTip"><input type="text" placeholder="按下x或y显示提示" @keydown.x.y="showTip"></div><script>Vue.config.productionTip = falsenew Vue({el: '#root',data: {},methods: {showTip(event){console.log(event.key,event.keyCode);}}})</script></body>

      在Vue中也可以使用keyCode去指定具体的按键(不推荐)

      <input type="text" placeholder="按下键盘显示提示" @keydown.13="showTip">

      键盘上各个键对应的keyCode(了解)

      keykeycode
      Escape27
      CapsLock20
      Shift16
      Control17
      Delete46
      Backspace8
      空格32
      Tab9
      Enter(回车)13
      左/上/右/下37/38/39/40
      a/b/c/d/…65/66/67/68/…
      0/1/2/3/4/…48/49/50/51/52/…

      自定义别名按键(了解)

      <body><div id="root"><input type="text" placeholder="按下键盘显示提示" @keydown.huiche="showTip"></div><script>Vue.config.productionTip = false//自定义回车按键的别名Vue.config.keyCodes.huiche = 13new Vue({el: '#root',data: {},methods: {showTip(event){console.log(event.key,event.keyCode);}}})</script></body>

      1.10.计算属性

      姓名案例:当输入框中的内容发生变化时,姓名也会发生相应的变化

      <body><div id="root">姓:<input type="text" v-model="firstName"><br>名: <input type="text" v-model="lastName"><br>姓名:<span>{{firstName}}-{{lastName}}</span></div><script>Vue.config.productionTip = falsenew Vue({el: '#root',data: {firstName: '张',lastName: '三'}})</script></body>

      使用插值语法虽然可以实现这个案例,但是当需求发生变化时,{{xxx}}中的表达式可能会变得很复杂,代码可读性差

      <body><div id="root">姓:<input type="text" v-model="firstName"><br><br>名:<input type="text" v-model="lastName"><br><br>姓名:<span>{{fullName()}}</span></div><script>Vue.config.productionTip = falsenew Vue({el: '#root',data: {firstName: '张',lastName: '三'},methods: {fullName(){//这里可以对firstName/lastName操作return this.firstName+'-'+this.lastName}}})</script></body>

      上面这种方式效率较低

      <body><div id="root">姓:<input type="text" v-model="firstName"><br><br>名:<input type="text" v-model="lastName"><br><br>姓名:<span>{{fullName}}</span></div><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {firstName: '张',lastName: '三'},//计算属性配置项 computed:{//写在data中的是属性//写在computed中的是计算属性fullName:{ //fullName就是计算属性get(){//什么时候调用//1.初次读取fullName时//2.所依赖的数据发生变化时return this.firstName+'-'+this.lastName},set(value){//set不是必须的//fullName发生变化时调用const arr = value.split('-')this.firstName = arr[0]this.lastName = arr[1]}} }})</script></body>

      计算属性的简写形式

      如果计算属性只读不改才可以使用简写形式

      computed:{fullName(){return this.firstName+'-'+this.lastName}}

      总结:

      ​ 计算属性的定义:要用的属性不存在,要通过已有的属性计算得来

      ​ 计算属性最终会出现在vm身上,可直接使用

      ​ 相比于methods方法实现,这种方式内部有缓存机制,效率更高

      1.11.监视属性(侦听属性)

      天气案例:点击按钮切换天气(将炎热改为凉爽)

      <body><div id="root"><h1>今天天气很{{weather}}</h1><button @click="changeWeather">点击切换天气</button></div><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {isHot: true},computed: {weather(){return this.isHot " />'炎热' : '凉爽'}},methods: {changeWeather(){this.isHot = !this.isHot}},//监视属性的配置项watch: {//key为要监视的属性isHot:{//初始化时是否执行handler函数immediate: false,//handler函数可以接收被监视属性被修改前后的值handler(newValue,oldValue){console.log('天气被修改了',newValue,oldValue);}}}})</script></body>

      监视属性的另外一种写法(创建好vm对象后,使用$watch()函数添加监视属性)

      这种方式适合在创建vue实例对象时不明确要监视哪个属性

      vm.$watch('isHot',{ immediate: false, handler(newValue,oldValue){console.log('天气被修改了',newValue,oldValue); }})

      监视属性的简写形式

      当监视属性只配置handler属性>时才能使用简写形式

      watch: {isHot (newValue,oldValue){console.log('天气被修改了',newValue,oldValue);}}//另一种简写形式vm.$watch('isHot',function(newValue,oldValue){console.log('天气被修改了',newValue,oldValue);})

      深度监视

      案例:点击按钮数值加1,并在控制台中打印变化的值

      <body><div id="root"><h1>a的值是{{numbers.a}}</h1><h1>b的值是{{numbers.b}}</h1><button @click="numbers.a++">点击a+1</button><button @click="numbers.b++">点击b+1</button></div><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {numbers: {a:10,b:20}},watch: {//a: { 当监视的是多级结构中的属性,这样写就无法监视到'numbers.a':{ //正确写法handler(newValue,oldValue){console.log('a的值变化了',newValue,oldValue);}},numbers:{deep: true, //开启深度监视,用于监视多级结构中的属性handler(){console.log('numbers中的值变化了');}}}})</script></body>

      总结:
      1.Vue中的watch默认不监视对象内部的属性值的改变
      2.配置deep:true可以监视到对象内部属性的改变

      计算属性VS监视属性

      姓名案例watch实现

      <body><div id="root">姓:<input type="text" v-model="firstName"><br><br>名:<input type="text" v-model="lastName"><br><br>姓名:<span>{{fullName}}</span></div><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {firstName: '张',lastName: '三',fullName:'张-三'},watch:{firstName(newVal){this.fullName = newVal +'-'+ this.lastName},lastName(newVal){this.fullName = this.firstName +'-'+ newVal}}})</script></body>

      新需求:在修改完姓之后3秒页面的全名再发生变化

      //这种写法是错误的computed:{fullName(){setTimeOut(()=>{return this.firstName+'-'+this.lastName},3000)}}//这种写法是正确的watch:{firstName(newVal){setTimeOut(()=>{this.fullName = newVal +'-'+ this.lastName},3000)},lastName(newVal){this.fullName = this.firstName +'-'+ newVal}}

      总结:

      ​ 1.computed能完成的功能 使用watch也都能完成

      ​ 2.watch能完成的功能 computed不一定能完成 例如watch可以进行异步操作

      ​ 3.计算属性中不能开启异步任务去维护数据

      ​ 4.两个重要的小原则:

      ​ 4.1所有被Vue管理的函数最好写成普通函数

      ​ 4.2所有不被Vue管理的函数最好写成箭头函数(定时器的回调、ajax的回调、Promise的回调)

      1.12.绑定样式

      css样式绑定

      <body><div id="root">绑定样式::class="xxx" xxx可以是字符串、数组、对象<div class="basic" :class="classStr" @click="changeClass"></div><div class="basic" :class="classArr"></div><div class="basic" :class="classObj"></div></div><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {classStr: 'style1',classArr: ['style1','style2','style3'],classObj:{style1: true,//使用style1样式style2: true,style3: false//不使用style3样式}},methods: {changeClass(){this.classStr = 'style2'}}})</script></body>

      style样式绑定

      <body><div id="root"><div :style="styleObj">绑定样式</div></div><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {styleObj: {fontSize: '40px',color: 'red'}}})</script></body>

      1.13.条件渲染

      <body><div id="root"><div v-show="isShow">{{info}}</div><!-- 
      --><div v-if="num === 1">Vue</div><div v-else-if="num === 2">React</div><div v-else>Angular</div><template v-if="isShow"><h1>Vue</h1><h1>React</h1> </template></div><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {info: 'hello vue',isShow: true,num: 0}})</script></body>

      总结:
      1.v-if
      写法:v-if="布尔表达式"
      适用于切换频率较低的场景
      不展示的DOM结点会被删除
      配合v-else使用时要求结构不能被打断
      2.v-show
      写法:v-show="布尔表达式"
      适用于切换频率较高的场景
      不展示的DOM结点不会被移除,仅仅使用样式隐藏

      1.14.列表渲染

      <body><div id="root"><ul><li v-for="p in persons" :key="p.id">{{p.name}}-{{p.age}}</li><li v-for="(p,index) in persons" :key="index">{{p}}-{{index}}</li><li v-for="p of persons" :key="p.id">{{p.name}}-{{p.age}}</li><li v-for="(value,key) in persons[0]" :key="key">{{key}}-{{value}}</li><li v-for="(char,index) in str" :key="index">{{char}}-{{index}}</li><li v-for="(number,index) in 5" :key="index">{{number}}-{{index}}</li></ul></div><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {persons: [{id:'001',name:'张三',age:18},{id:'002',name:'李四',age:19},{id:'003',name:'王五',age:20},],str: 'hello'}})</script></body>

      index作为key时diff算法过程

      id作为key时diff算法过程

      key的工作原理总结:

      ​ 1.key是虚拟DOM对象的标识

      ​ 2.当状态中的数据发生变化时,Vue会根据新数据生成新的虚拟DOM,再根据diff算法转换成真实DOM

      ​ 3.对比规则:

      ​ 3.1旧虚拟DOM中找到了与新虚拟DOM相同的key

      ​ a.若虚拟DOM中的内容没有改变,直接使用之前生成的真实DOM

      ​ b.若虚拟DOM中的内容发生改变,则生成新的真实DOM,替换掉之前的真实DOM

      ​ 3.2旧虚拟DOM中未找到与新虚拟DOM相同的key

      ​ 创建新的真实DOM,然后渲染到页面

      用index作为key可能会引发的问题

      ​ 若对数据进行逆序添加、删除等破坏顺序操作时

      ​ 1.如果结构中没有包含输入类DOM,会产生没必要的真实DOM更新

      ​ 2.如果结构中包含输入类DOM,会产生错误DOM更新

      如何选择key

      ​ 1.最好使用数据的唯一标识作为key

      ​ 2.如果不存在破坏顺序的操作,仅用于渲染列表用于展示,那么可以使用index作为key

      1.15.列表过滤

      案例:在输入框中输入关键字实现模糊搜索

      监视属性实现

      <body><div id="root"><input type="text" placeholder="请输入关键字" v-model="keyWord"/><ul><li v-for="(p,index) in filterPersons" :key="p.id">{{p.name}}-{{p.age}}-{{p.sex}}</li></ul></div><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {keyWord: '',persons: [{id:'001',name:'马冬梅',age:18,sex:'女'},{id:'002',name:'周冬雨',age:19,sex:'女'},{id:'003',name:'周杰伦',age:20,sex:'男'},{id:'004',name:'温兆伦',age:20,sex:'男'},],filterPersons: []},watch: {keyWord: {immediate: true,handler(newValue){this.filterPersons = this.persons.filter((p)=>{return p.name.indexOf(newValue) !== -1})}}}})</script></body>

      计算属性实现

      <body><div id="root"><input type="text" placeholder="请输入关键字" v-model="keyWord"/><ul><li v-for="(p,index) in filterPersons" :key="index">{{p.name}}-{{p.age}}-{{p.sex}}</li></ul></div><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {keyWord: '',persons: [{id:'001',name:'马冬梅',age:18,sex:'女'},{id:'002',name:'周冬雨',age:19,sex:'女'},{id:'003',name:'周杰伦',age:20,sex:'男'},{id:'003',name:'温兆伦',age:20,sex:'男'},],},computed:{filterPersons(){var arr = this.persons.filter((p)=>{return p.name.indexOf(this.keyWord) !== -1})return arr}}})</script></body>

      需求升级:为搜索的到的数据添加排序功能

      <body><div id="root"><input type="text" placeholder="请输入关键字" v-model="keyWord"/><input type="button"@click="sortType = 2" value="按年龄升序"/><input type="button"@click="sortType = 1" value="按年龄降序"/><input type="button"@click="sortType = 0" value="按原顺序排序"/><ul><li v-for="(p,index) in filterPersons" :key="index">{{p.name}}-{{p.age}}-{{p.sex}}</li></ul></div><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {keyWord: '',sortType: 0,persons: [{id:'001',name:'马冬梅',age:19,sex:'女'},{id:'002',name:'周冬雨',age:21,sex:'女'},{id:'003',name:'周杰伦',age:18,sex:'男'},{id:'003',name:'温兆伦',age:20,sex:'男'},],},computed:{filterPersons(){var arr = this.persons.filter((p)=>{return p.name.indexOf(this.keyWord) !== -1})if(this.sortType){arr.sort((p1,p2)=>{return this.sortType === 2 " />.age-p2.age : p2.age-p1.age})}return arr}}})</script></body>

      1.16.Vue检测数据变化的原理

      模拟简单默认数据监视

      <body><script>function Observer(obj) {//获取对象中的所有key,放入到keys数组中const keys = Object.keys(obj)//遍历每个key,为其添加get/set函数keys.forEach((key) => {Object.defineProperty(this, key, {get() {return obj[key]},set(value) {console.log(`${key}属性改变了,准备开始重新解析模板...`);obj[key] = value}})})}let data = {name: 'jack',age: 20}let obs = new Observer(data)let vm = {}vm._data = data = obs</script></body>

      Vue.set()和vm.$set()

      Vue提供了Vue.set()和vm.$set()可以实现后添加的数据也能有响应式的功能

      语法:Vue.set(target,'key/index','value')vm.$set(target,'key/index','value')

      ​ target:往谁的身上追加属性,target不允许是vm或vm._data

      ​ key/index: 要追加的属性名或者数组下标

      ​ value: 属性值

      这种方法存在局限

      set()不能给data追加属性,只能给data中的对象追加属性

      数组监视

      <body><div id="root"><ul><li v-for="(h, index) in hobby" :key="index">{{h}}</li></ul></div><script> Vue.config.productionTip = false const vm = new Vue({el:'#root',data: {hobby:['唱歌','跳舞','打球']} })</script></body>


      由于数组内并没有提供set和get,所以在修改hobby[0]时Vue监测不到数据发生变化,导致页面也不会变化

      Vue将被监听的数组的更变方法进行了包装,所以他们会触发视图的更新,这些被包装的方法有:

      • push() 在数组尾部插入元素
      • pop() 移除数组尾部元素
      • shift() 移除数组首部元素
      • unshift() 在数组首部插入元素
      • splice() 在指定位置替换、添加或删除数组元素
      • sort() 对数组进行排序
      • reverse() 反转数组

      Vue监视数据变化的原理

      1.Vue会监视data中所有层次的数据

      2.监视对象中的数据

      ​ 通过setter实现监视,且要在new Vue时就传入要监视的数据

      ​ 对象中后追加的属性Vue默认不做响应式处理

      ​ 如果需要对后追加的属性做响应式处理需要使用Vue.set()vm.$set()

      3.监视数组中的属性

      ​ 对于数组中非对象属性,Vue默认不提供setter和getter,直接修改vue监视不到变化

      ​ 通过包裹数组更新元素的函数实现

      注意:通过Vue.set()和vm.$set()不能给vm或vm._data添加属性

      1.17.收集表单数据

      <body><div id="root"><form action="#" method="post" @submit.prevent="submit">账号:<input type="text" v-model.trim="username"><br><br>密码:<input type="password" v-model="password"><br><br>年龄:<input type="number" v-model.number="age"><br><br> 性别:男 女<br><br>爱好:唱歌<input type="checkbox" name="hobby" v-model="hobby" value="sing">跳舞<input type="checkbox" name="hobby" v-model="hobby" value="dance">打球<input type="checkbox" name="hobby" v-model="hobby" value="ball"><br><br>校区:<select name="area" v-model="area"><option value="">请选择校区</option><option value="富春">富春校区</option><option value="滨江">滨江校区</option></select><br><br>其他信息:<textarea v-model.lazy="info"></textarea><br><br><input type="checkbox" v-model="agree">同意并接收<a href="#">《用户协议》</a><br><br> <input type="submit" value="提交"></form></div><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {username: '',password: '',age:'',sex: 'male',hobby: [],area: '',info:'',agree: ''},methods: {submit(){const data = JSON.stringify(thi._data)}},})</script></body>

      总结:

      ​ 1.若收集的是 则v-model收集的是用户输入的value值

      ​ 2.若收集的是 则v-model收集的是value值且要配置value属性

      ​ 3.若收集的是 则v-model收集的是

      ​ 3.1没有配置value属性.收集checked的值(true/false)

      ​ 3.1配置了value属性.如果v-model的初始值是非数组那么收集的还是checked的值.如果v-model的初始值 是数组.那么收集的就是value组成的数组

      ​ 4.v-model的三个修饰符

      ​ 4.1 lazy 失去焦点再收集数据

      ​ 4.2 number 输入的字符串转为有效数组,一般配置type=”number”使用

      ​ 4.3trim 去除输入首位多余空格

      1.18.过滤器

      BootCDN网址:https://www.bootcdn.cn/

      <head><meta charset="UTF-8"><title>Document</title><script src="../js/vue.js"></script><script src="../js/dayjs.min.js"></script></head><body><div id="root"><div>{{formatTime}}</div><div>{{time | timeFormater}}</div><div>{{time | timeFormater('YYYY年MM月DD日')}}</div><div>{{time | timeFormater('YYYY年MM月DD日') | getYear}}</div></div><script>Vue.config.productionTip = false//配置全局过滤器Vue.filter('globalFilter',function(value){return value.toUpperCase()})const vm = new Vue({el: '#root',data: {time: 1656912659058,},computed: {formatTime(){return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')}},filters: {timeFormater(value,formatStr = 'YYYY-MM-DD HH:mm:ss'){//第一个参数一定是value为管道符|前面的值//后面的参数为传递的参数return dayjs(value).format(formatStr)},getYear(value){return value.slice(0,5)}}})</script></body>

      总结:

      ​ 1.定义:对显示的数据进行特定格式化后再显示(适用于简单逻辑的处理)

      ​ 2.语法:

      Vue.filter('filterName',callback)全局过滤器

      new Vue{filters:{}} 局部过滤器

      ​ 3.过滤器可以接收额外的参数,第一个参数是管道符前面的值

      ​ 4.过滤器可以串联使用

      ​ 5.过滤器不改变原本的数据而是产生对应新的数据

      1.19.内置指令

      内置指令说明
      v-bind单向绑定 可简写为:xxx
      v-model双向绑定
      v-for列表渲染
      v-on绑定事件 可简写为@
      v-if条件渲染
      v-show条件渲染
      v-text向其所在的结点中渲染文本内容,不解析标签
      v-html向其所在结点中渲染html结构内容
      v-cloak没有值,Vue创建完容器后会删掉v-cloak属性
      v-once没有值,所在结点在初次动态渲染后就视为静态内容了
      v-pre没有值,跳过所在结点的编译过程,加快编译速度
      <body><div id="root"><div v-text="info"></div><div v-html="info"></div><div v-once>{{info}}</div><div v-pre>你好</div><div v-pre>{{info}}</div> </div><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {info: '

      Hello Vue

      '
      }})
      </script></body>
      <style>/* 一般配合css使用,可以解决网速慢时展示出未经编译的模板{{xxx}} */[v-cloak]{display: none;}</style>

      1.20.自定义指令

      需求1:自定义v-big指令 功能和v-text类似但是会把数值放大10倍

      <body><div id="root">当前的num值是:<span v-text="num"></span><br><br>num值放大10倍:<span v-big="num"></span><br><br><button @click="num++">点击num加1</button></div></body><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {num: 5},directives: {//定义指令的第一种方式,函数式/* 调用时机:1.指令与元素绑定成功时2.指令所在模板被重新解析时*/big(element,binding){ //big: function(element,binding){} 完整写法//element:指令所在真实DOM//bingding:binding.value常用element.innerText = binding.value * 10}}})</script>

      需求2:自定义v-fbind指令 功能与v-bind类似但可以默认获取焦点

      <body><div id="root"><input type="text" v-bind:value="num"><br><br><input type="text" v-fbind="num"><br><br><button @click="num++">点击num加1</button></div></body><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',data: {num: 5},directives: {//自定义指令的第二种方式:对象式fbind:{bind(ele,binding){//指令和元素成功绑定时调用ele.value = binding.value},inserted(ele,binding){//指令所在元素被插入页面时调用ele.focus()},update(ele,binding){//指令所在模板被重新解析时调用ele.value = binding.valueele.focus()}}}})</script>

      自定义指令总结:

      ​ 1.局部指令:定义在配置对象的directives中

      ​ 2.全局指令:Vue.directive(‘指令名’,配置对象)或Vue.directive(‘指令名’,回调函数)

      ​ 3.定义指令时不加v- 但使用指令时要加v-

      ​ 4.指令名如果是多个单词用-隔开,不要写成驼峰式

      ​ 5.directives中的函数中的this指向Window

      1.21.生命周期

      1.生命周期函数又叫生命周期钩子

      2.生命周期钩子就是Vue在关键的时刻帮我们调用的特殊名称的函数

      3.生命周期函数的名字不可更改

      4.生命周期函数中的this指向vm实例对象或组件实例

      5.vm.$destory() 完全销毁vm实例

      生命周期钩子说明
      beforeCreate数据代理被创建之前
      created数据代理被创建之后
      beforeMount真实DOM被挂在到页面之前
      mounted(常用)真实DOM被挂在到页面之后
      beforeUpdate_data数据发生变化之前
      updated_data数据发生变化之后
      beforeDestroy(常用)vm对象被销毁之前
      destroyedvm对象被销毁之后

      template属性使用

      <body><div id="root"></div></body><script>Vue.config.productionTip = falseconst vm = new Vue({el: '#root',//不能用标签作为组件的根标签template: `

      num的值为:{{num}}

      `
      ,data: {num: 0}})
      </script>

      2.Vue组件化

      传统方式编写应用

      使用组件的方式编写应用

      组件可以嵌套使用

      什么是组件?

      ​ 实现应用中局部功能代码和资源的集合

      组件可以分成2种

      ​ 非单文件组件:一个文件种包含n个组件

      ​ 单文件组件:只包含一个组件的xxx.vue

      2.1.创建非单文件组件

      <body><div id="root"><school></school><hr><student></student><student></student></div></body><script>Vue.config.productionTip = false//第一步:创建school组件const sch = Vue.extend({template:`

      学校名称:{{schoolName}}

      学校地址:{{address}}

      `
      ,//组件没有el配置项//data必须使用函数式data() {return {schoolName: '浙江中医药大学',address: '杭州滨江区'}},})//第一步:创建student组件const stu = Vue.extend({template:`

      学生姓名:{{studentName}}

      学生年龄:{{age}}

      `
      ,data() {return {studentName: '张三',age: 20}},})const vm = new Vue({el: '#root',//第二步:注册组件(局部注册)components: {school:sch,student:stu}})
      </script>

      定义组件的简写形式

      //完整形式const school = Vue.extend({...})//简写形式const school = {...}

      全局注册组件

      Vue.component('组件名',组件常量)

      总结:

      ​ Vue中使用组件的三个步骤:

      ​ 1.定义组件

      ​ 2.注册组件

      ​ 3.使用组件

      ​ 组件名命名规范

      ​ 1.组件名由一个单词组成

      ​ 第一种写法:首字母小写(school)

      ​ 第二种写法:首字母大写(School)

      ​ 2.组件名由多个单词组成

      ​ 第一种写法:用-划分单词(my-school)

      ​ 第二种写法:每个单词首字母大写(MySchool)这种要在脚手架中使用

      ​ 3.组件名尽可能回避HTML中已有元素名称,如h1、H1…

      ​ 组件标签的写法

      ​ 第一种写法:

      ​ 第二种写法: 这种要在脚手架中使用

      2.2.组件的嵌套

      <body><div id="root"><school></school></div></body><script>Vue.config.productionTip = falseconst stu = {template:`

      学生姓名:{{studentName}}

      学生年龄:{{age}}

      `
      ,data() {return {studentName: '张三',age: 20}},}const sch = Vue.extend({//注册在哪个组件就在哪个组件中使用template:`

      学校名称:{{schoolName}}

      学校地址:{{address}}

      `
      ,data() {return {schoolName: '浙江中医药大学',address: '杭州滨江区'}},//组件定义要在组件注册之前components:{student:stu}})const vm = new Vue({el: '#root',//第二步:注册组件(局部注册)components: {school:sch,}})
      </script>

      2.3.VueComponent构造函数

      1.组件的本质是VueComponent构造函数且不是程序员定义的而是Vue.extend生成的

      2.程序员只需写或组件标签,Vue在解析时会为我们创建该组件的实例对象

      3.每次调用Vue.extend返回的都是全新的VueComponent

      4.this指向

      ​ 在组件配置中this指向的是VueCompanent实例对象

      ​ 在new Vue(options)配置中this指向vm实例对象

      2.4.重要的内置关系

      prototype 显示原型属性

      _proto_ 隐式原型属性

      VueComponent.prototype._proto_ === Vue.prototype

      Vue为什么要设置这种关系?

      ​ 让组件实例对象可以访问到Vue原型上的属性和方法

      2.5.创建单文件组件

      创建School.vue文件

      学校名称:{{schoolName}}

      学校地址:{{address}}

      //组件交互相关代码export default {name: 'School',//一般与文件名保持相同data() {return {schoolName:'浙江大学',address:'杭州西湖区'}},}/* 组件的样式 */.demo{background-color: aqua;}

      创建Student.vue组件

      学生姓名:{{name}}

      学生年龄:{{age}}

      //组件交互相关代码export default {name: 'Student',//一般与文件名保持相同data() {return {name:'Jack',age:18}},}

      创建App.vue组件 (管理其他单文件组件)

      import School from './School.vue'import Student from './Student.vue'export default {//注册组件components: { Student,School },}

      创建main.js文件(作为程序的入口)

      import App from './App.vue';new Vue({el: "#root",template:``,components:{App}})

      创建index.html文件

      <html lang="en"><head><meta charset="UTF-8"><title>单文件组件</title></head><body><div id="root"></div><script type="text/javascript" src="../../js/vue.js"></script><script type="text/javascript" src="./main.js"></script></body></html>

      以上代码无法直接在浏览器上运行.因为浏览器不支持ES6模块化语法

      3.Vue脚手架

      3.1.初始化脚手架

      CLI (Command Line Interface) 翻译过来为脚手架

      文档:https://cli.vuejs.org/zh/

      初始化脚手架步骤:1.全局安装@vue/cli npm install -g @vue/cli2.切换到要创建项目的目录然后使用命令创建项目vue create 项目名3.启动项目npm run serve如果出现下载缓慢可以配置npm淘宝镜像npm config set registry https://registry.npm.taobao.org

      项目启动成功

      访问网页

      3.2.目录说明

      >node_modules--------------->用来管理项目中使用依赖>publicfavicon.icon------------>页签图标index.html-------------->项目主页>src------------------------>用来书写vue的源代码>assets----------------->用来存放静态资源>components------------->用来书写Vue组件App.vue----------------->根组件main.js----------------->项目的入口文件.gitignore------------------>git忽略文件babel.config.js------------->babel的配置文件(ES6转ES5)jsconfig.json--------------->指定了JavaScript 语言服务提供的功能的根文件和选项package-lock.json----------->包版本控制文件package.json---------------->类似与pom.xml依赖管理README.md------------------->对项目的说明vue.config.js--------------->可选配置文件(可以个性化的配置脚手架)

      3.3.利用脚手架实现2.5案例

      1.main.js

      //引入Vue(阉割版) 完整版是'vue/dist/vue'import Vue from 'vue';//引入App组件import App from './App.vue';Vue.config.productionTip = falsenew Vue({el: "#app",//将App组件放入容器render: h=>h(App)})

      1.替换App.vue

      import School from './components/School.vue'import Student from './components/Student.vue'export default {//注册组件components: { Student,School }}

      2.将Student和School组件放入components目录下

      学校名称:{{schoolName}}

      学校地址:{{address}}

      //组件交互相关代码export default {name: 'MySchool',//一般与文件名保持相同data() {return {schoolName:'浙江大学',address:'杭州西湖区'}},}/* 组件的样式 */.demo{background-color: aqua;}

      学生姓名:{{name}}

      学生年龄:{{age}}

      //组件交互相关代码export default {name: 'MyStudent',//一般与文件名保持相同data() {return {name:'Jack',age:18}},}

      3.启动项目

      3.4.render

      new Vue({el: "#app",//将App组件放入容器render: h=>h(App)/*完整写法 render(createElement){return createElement(App)} */})

      关于不同版本的Vue

      - vue.js 和 vue.runtime.xxx.js的区别

      ​ .vue.js是完整版的Vue.包含核心功能+模板解析器

      ​ .vue.runtime.xxx.js是运行版的Vue.只包含核心功能

      • 因为.vue.runtime.xxx.js没有模板解析器所以不能使用template配置项

        需要使用render函数接收到的createElement函数去指定具体的内容

      3.5脚手架配置

      #Vue脚手架隐藏了所有webpack相关配置#查看具体的webpack配置vue inspect > output.js#修改output.js配置文件无法生效要在vue.config.js文件中修改

      自定义配置在vue.config.js文件中

      module.exports = {pages: {index: {// page 的入口entry: 'mySrc/myMain.js',// 模板来源template: 'public/myIndex.html'//...//https://cli.vuejs.org/zh/config/#pages}},lintOnSave:false //关闭语法检查}

      3.6.ref属性

      <template><div><h1 v-text="info" ref="title"></h1><button @click="showDOM" ref="btn">显示ref</button><School ref="sch"></School></div></template><script>import School from './components/School.vue'export default {name: 'App',components: { School }, data() {return {info:'欢迎学习Vue'} }, methods: {showDOM(){console.log(this.$refs.title); //真实DOM h1console.log(this.$refs.btn); //真实DOM buttonconsole.log(this.$refs.sch); //School组件实例对象} },}</script>

      总结:

      ​ 1.ref属性被用于给元素或子组件注册应用信息(类似id)

      ​ 2.ref属性用在HTML标签上获取的是真实DOM.用在组件标签上获取的是组件实例对象

      ​ 3.打标识 \

      ...\

      ​ 3.获取方式this.$refs.xxx

      3.7.props配置项

      Student.vue

      <template><div><h1>学生姓名:{{name}}</h1><h1>学生性别:{{sex}}</h1><h1>学生年龄:{{age+1}}</h1></div></template><script>//组件交互相关代码export default {name: 'MyStudent',data() {return {}},//props:['name','sex','age'] //简单接收(string类型)//接收的同时对数据类型进行限制props: {name: String,age: Number,sex: String}//最完整写法/* props: {name: {type: String, //类型是Stringrequired:true //name是必须要传的},sex: {type: Number,default: 18 //默认值},age: {type: String,required:true}} */}</script>

      App.vue

      <template><div><Student name="Marry" sex="" age="18"></Student></div></template><script>import Student from './components/Student.vue'export default {//注册组件components: { Student, },}</script>

      总结

      ​ props配置项的作用: 让组件接收外部参数

      ​ 接收到的props是不可以改的.如果想修改就在data中添加属性 myAge: this.age

       传递的是data属性或计算属性或函数或表达式 传递的是普通字符串"name"

      3.8.mixin

      创建文件mixin.js

      export const mixin = {//组件配置对象中的可复用的配置项都可以写在这methods:{sayHello(){alert("hello")}}}

      引入混合

      <script>//引入mixin.js文件import {mixin} from '../mixin'export default {name: 'Student',data(){retrun{}},//引入混合mixins:[mixin]}</script>

      总结:

      1.可以将多个组件共用的配置提取成混合对象

      2.如果data、methods中属性发生冲突以组件配置为准3.但是对于生命周期钩子而言 两个都要且混合中的生命钩子先执行4.局部混合:组件配置对象中的```mixin: [xxx,xxx]```5.全局混合:在main.js中
      import {mixin1,mixin2} from '../mixin'Vue.mixin(mixin1)Vue.mixin(mixin1)

      3.9.插件

      定义插件: 创建文件plugin.js

      export default{install(Vue){//配置全局过滤器Vue.filter('filterName',function(value){...})//定义全局指令Vue.directive('directName',{...})//定义全局混入Vue.mixin({...})//其他功能增强代码...}}

      使用插件

      //先引入插件import plugins from './plugins'//使用插件Vue.use(plugins)

      总结:

      ​ 功能:用于增强Vue

      ​ 本质:包含一个install方法的对象

      ​ install方法的第一个参数是Vue,之后的参数是插件使用者传递的参数

      3.10.scoped

      如果不同组件中的样式名相同会引发冲突(与引入顺序有关)

      解决方法

      <style scoped>/*....*/</style>

      但是App组件中的style标签不适用scoped

      因为在App组件中写的样式一般都是多个组件共用的样式

      如果App组件中的style标签加了scoped则样式只能在App组件中使用

      3.11.TodoList案例

      案例Github地址:

      组件化编码流程

      ​ 1.拆分静态组件:组件按照功能点拆分 命名不要与HTML元素冲突

      ​ 2.实现动态组件:考虑好数据存放的位置 数据是一个组件在使用还是一些组件在使用

      ​ 一个组件在使用:放在组件自身即可

      ​ 一些组件在使用:放在他们共同的父组件上

      ​ 3.实现交互:从绑定事件开始

      3.12.组件间数据传递

      父组件向子组件传递数据:子组件使用props配置项接收父组件在组件标签中传递的数据

      子组件向父组件传递数据:父组件事先定义函数传给子组件,然后子组件调用父组件的函数

      ​ 通过函数的形参将数据传给父组件

      兄弟组件之间传递数据:(最初级的做法)

      ​ 1.将共用的数据定义在父组件中

      ​ 2.在父组件中定义操作数据的函数

      ​ 3.若组件A想给兄弟组件B传递数据,那么父组件将函数传递给A将数据传递给B

      3.13.WebStorage

      <body><button onclick="saveData()">点我保存数据</button><button onclick="readData()">点我获取数据</button><button onclick="deleteData()">点我删除数据</button><button onclick="deleteAll()">点我清空数据</button><script>function saveData(){//key,value都是字符串,如果不是字符串默认会调用toString()方法//如果value要存对象,要先将对象转化为字符串JSON.stringify(obj)window.localStorage.setItem('message','hello')window.localStorage.setItem('person',JSON.stringify({name: 'Jack',age: 20}))}function readData(){let message = localStorage.getItem('message')let personStr = localStorage.getItem('person')//将JSON字符串转化为对象const personObj = JSON.parse(personStr)}function deleteData(){localStorage.removeItem('person')}function deleteAll(){localStorage.clear()}</script></body>

      sessionStorage和localStorage的API相同这里不在演示

      总结:

      ​ 1.存储大小一般为5MB左右

      ​ 2.浏览器通过Window.localStorageWindow.sessionStorage实现本地存储机制

      ​ 3.sessionStorage存储的内容会随着浏览器窗口关闭而消失

      ​ 4.localStorage存储的内容需要手动清理缓存才会消失

      ​ 5.xxxxStorage.getItem('x')如果本地存储中不存在key为x则返回值为null

      ​ 6.JSON.parse(null)的结果依然是null

      3.14.组件的自定义事件

      案例:通过自定义事件实现子组件向父组件传递数据

      App.vue

      <template><div><h1>App</h1><School :getSchool="getSchoolData"/><!--  --><Student ref="student"/></div></template><script>import Student from './components/Student.vue'import School from './components/School.vue'export default {//注册组件components: { Student,School },methods: {getSchoolData(data){console.log('我是App组件 我接收到了School组件的数据',data);},//getStudentData(data,...params) 接收多个参数getStudentData(data){console.log('我是App组件 我接收到了Student组件的数据',data);}},mounted() {//绑定事件的另外一种方式this.$refs.student.$on('myEvent',this.getStudentData)//this.$refs.student.$once('myEvent',this.getStudentData) 事件只执行一次},}</script>

      Student.vue

      <template><div><h1>学生姓名:{{name}}</h1><h1>学生性别:{{sex}}</h1><button @click="sendName">向App组件发送数据</button><button @click="unbind">解绑自定义事件</button></div></template><script>export default {name: 'MyStudent',data() {return {name: 'Jack',sex: 'men'}},methods: {sendName(){this.$emit('myEvent',this.name) //触发是定义事件myEvent},unbind(){this.$off('myEvent') //解绑一个自定义事件this.$off(['myEvent1','myEvent2']) //解绑多个自定义事件this.$off() //解绑所有自定义事件}}}</script>

      School.vue

      <template><div><h1>学校名称:{{name}}</h1><h1>学校地址:{{address}}</h1><button @click="sendName">向App组件发送数据</button></div></template><script>export default {name: 'MyStudent',props:['getSchool'],data() {return {name: '浙江大学',address: '浙江杭州'}},methods: {sendName(){this.getSchool(this.name)}},}</script>

      组件如何绑定原生DOM事件

       正确写法 错误写法 这样写vue会把click当作自定义事件

      总结:

      ​ 1.自定义事件适用于子组件向父组件传递数据(事件的回调写在父组件中)

      ​ 2.绑定自定义事件 v-on:自定义事件名 $on(‘自定义事件名’,回调函数)

      ​ 3.触发自定义事件 this.$emit('自定义事件名')(给哪个组件绑定事件就在哪个组件中触发)

      ​ 4.解绑自定义事件 this.$off('自定义事件名')(给哪个组件绑定事件就在哪个组件中解绑)

      ​ 5.组件上也可以绑定原生的DOM事件,需要使用native修饰符

      ​ 6.使用tihs.$ref.xxx.$on('事件名',回调函数)如果回调不是在methods中

      ​ 6.1.如果函数是普通函数那么this是绑定事件的组件实例

      ​ 6.2.如果使用的是箭头函数那么this指向的是父组件的实例

      3.15全局事件总线

      全局事件总线可以实现任意组件间的通信

      哪个组件向收到数据就在哪个组件中给$bus绑定事件,哪个组件想发送数据就在哪个组件中触发事件

      如上图所示

      ​ 组件C给组件A发送数据,首先在组件A中给$bus绑定event1事件,然后组件C在触发event1事件的同时携带些数据

      ​ 组件D给组件B发送数据,首先在组件B中给$bus绑定event2事件,然后组件D在触发event1事件的同时携带些数据

      接收数据的组件绑定事件发送数据的组件触发事件

      案例:Student组件向School组件发送学生信息

      main.js

      import Vue from 'vue';import App from './App.vue';Vue.config.productionTip = falsenew Vue({el: "#app",render: h=>h(App),beforeCreate(){//原理VueComponent.prototype.__proto__ === Vue.prototypeVue.prototype.$bus = this //安装全局事件总线}})

      App.vue

      <template><div><h1>App</h1><School></School><Student></Student></div></template><script>import Student from './components/Student.vue'import School from './components/School.vue'export default {//注册组件components: { Student,School },}</script>

      School.vue

      <template><div><h1>学校名称:{{name}}</h1><h1>学校地址:{{address}}</h1></div></template><script>export default {name: 'MyStudent',data() {return {name: '浙江大学',address: '浙江杭州'}},methods: {getStudentMsg(studentMsg){console.log('我是School组件我收到了学生信息');console.log(studentMsg.name,studentMsg.sex);}},mounted() {//给$bus绑定自定义事件myEventthis.$bus.$on('myEvent',this.getStudentMsg)},beforeDestroy() {//给$bus解绑事件this.$bus.$off('myEvent')},}</script>

      Student.vue

      <template><div><h1>学生姓名:{{name}}</h1><h1>学生性别:{{sex}}</h1><button @click="sendStuMsg">点击发送学生信息</button></div></template><script>export default {name: 'MyStudent',data() {return {name: 'Jack',sex: 'men'}},methods: {sendStuMsg(){const student = {name:this.name,sex:this.sex}//触发事件myEventthis.$bus.$emit('myEvent', student)}},}</script>

      总结:

      ​ 1.安装全局事件总线

      ​ 2.使用事件总线

      ​ 接收数据:School组件想接收数据,就在School组件中给$bus绑定自定义事件,事件的回调留在School组件

      ​ 提供数据:Student组件提供数据就找Student组件中触发事件

      ​ 3.最好在beforeDestroy() 钩子中使用$off去解绑当前组件所用到的事件

      3.16消息订阅与发布

      消息订阅与发布也是完成任意组件间的通信

      安装消息订阅与发布第三方库 pubsub.js

      npm i pubsub-js

      School.vue

      <template><div><h1>学校名称:{{name}}</h1><h1>学校地址:{{address}}</h1></div></template><script>import pubsub from 'pubsub-js'export default {name: 'MyStudent',data() {return {name: '浙江大学',address: '浙江杭州',}},mounted() {//订阅消息this.pubId = pubsub.subscribe('message',(messageName,data)=>{//如果写成普通函数this为undefined//如果写成箭头函数this为组件实例对象console.log('有人发布了'+messageName+'消息,School组件收到消息:'+data);})},beforeDestroy() {//取消订阅pubsub.unpublish(this.pubId)},}</script>

      Student.vue

      <template><div><h1>学生姓名:{{name}}</h1><h1>学生性别:{{sex}}</h1><button @click="sendStuMsg">点击发送学生信息</button></div></template><script>import pubsub from 'pubsub-js'export default {name: 'MyStudent',data() {return {name: 'Jack',sex: 'men'}},methods: {sendStuMsg(){//发布message消息pubsub.publish('message',this.name)}},}</script>

      总结

      ​ 1.安装pubsub :npm i pubsub-js

      ​ 2.引入:import pubsub from 'pubsub-js'

      ​ 3.接收数据:想要收到数据的组件订阅消息 pubsub.subscribe('消息名',回调)

      ​ 4.提供数据:发送数据的组件发布消息pubsub.publish('消息名',数据)

      ​ 5.最好在beforeDestory钩子中取消订阅 pubsub.unpublish(消息ID)

      3.17.$nextTick

      语法:this.$nextTick(回调函数)

      作用:在下一次DOM更新结束后执行其指定的回调函数

      适用:当数据改变后,要基于更新后的新DOM进行某些操作时要在nextTick所指定的回调函数中执行

      3.18.过度和动画

      在插入、更新、移除DOM元素时给元素添加样式类名

      元素进入时的样式

      ​ 1.v-enter 进入的起点

      ​ 2.v-enter-active 进入的过程

      ​ 3.v-enter-to 进入的终点

      元素离开时的样式

      ​ 1.v-leave 离开时的起点

      ​ 2.v-leave-active 离开的过程

      ​ 3.v-leave-to 离开的终点

      案例:点击按钮h1标签向左滑动然后消失再次点击按钮h1向右滑动出现

      动画实现

      <template><div><button @click="isShow = !isShow">显示/隐藏</button><!-- 想让谁拥有动画效果就用标签包裹谁 --><transition name="h1" appear> <h1 v-show="isShow">你好啊</h1></transition></div></template><script>export default {name: 'MyTest',data() {return {isShow: true}},}</script><style>h1{background-color: orange;}/* vue规定的样式名 *//* 如果transition标签没有加name属性 可以使用v-enter-active...*/.h1-enter-active{animation: dhName 0.5s linear;}.h1-leave-active{animation: dhName 0.5s linear reverse;}/* linear 匀速 */@keyframes dhName{from{/* 动画的起点 */transform: translateX(-100%);}to{/* 动画的终点 */transform: translateX(0px);}}</style>

      过度实现

      <template><div><button @click="isShow = !isShow">显示/隐藏</button><transition name="h1" appear> <h1 v-show="isShow">你好啊</h1></transition></div></template><script>export default {name: 'MyTest1',data() {return {isShow: true}},}</script><style>h1{background-color: orange;}/* vue规定的样式名 *//* 进入的起点、离开的终点 */.h1-enter .h1-leave-to{transform: translateX(-100%);}/* 进入的终点、离开的起点 */.h1-enter-to .h1-leave{transform: translateX(0px);}/* 来去时候激活的样式 */.h1-enter-active .h1-leave-active{animation: 0.5 linear;}</style>

      多元素过度

      <transition-group name="h1" appear><h1 v-show="isShow" key="1">XXX</h1><h1 v-show="isShow" key="2">XXX</h1></transition-group>

      集成第三方动画

      animate网址

      安装

      npm i animate.css --save

      引入

      import 'animate.css'

      案例

      <template><div><button @click="isShow = !isShow">显示/隐藏</button><transition-group appearname="animate__animated animate__bounce" enter-active-class="animate__swing"leave-active-class="animate__backOutUp"><h1 v-show="!isShow" key="1">XXX</h1><h1 v-show="isShow" key="2">YYY</h1></transition-group></div></template><script>//引入cssimport 'animate.css'export default {name: 'MyTest1',data() {return {isShow: true}},}</script><style>h1{background-color: orange;}</style>

      4.Vue中的AJAX

      4.1.使用axios

      安装axios

      npm i axios

      引入axios

      import axios from 'axios'

      使用axios

      <script>import axios from 'axios'export default {name:'APP',methods:{getStu(){axios.get('http://localhost:5000/student').then(response=>{//返回成功的代码片段console.log('请求成功',response.data)},error=>{//失败的代码片段console.log('请求失败',error.message)})}}}</script>

      4.2.跨域问题

      跨域问题是指违背了同源策略

      ​ 同源策略规定了 协议名、主机名和端口号必须保持一致

      如何解决跨域问题?

      ​ 配置代理服务器

      注意:代理服务器和服务器之间的传递数据不用ajax所以不受同源策略限制

      使用vue-cli配置代理服务器

      配置代理的第一种方式:

      1.修改vue.config.js配置文件

      //添加下面代码//这种方式只能配置一个代理服务器module.exports = {devServer: {proxy: 'http://localhost:5000'}}

      2.修改axios请求地址

      axios.get('http://localhost:8080/student').then(...)

      说明:

      ​ 优点:配置简单,请求的资源直接发给代理服务器即可

      ​ 缺点:不能配置多个代理,不能灵活的控制请求是否走代理

      ​ 工作方式:当请求代理服务器不存在的资源时,该请求会转发给服务器

      配置代理的第二种方式:

      1.修改vue.config.js配置文件

      module.exports = {devServer: {proxy: {//'/stu'是请求前缀'/stu': {target: 'http://localhost:5000',pathRewrite:{'^/stu',''}, //转发时把路径中的/stu换成空字符串ws: true, //webscoket 默认为truechangeOrigin: true //用于控制请求头中的host值 默认为true},//配置多个代理服务器'/car': {target: 'http://localhost:5001',pathRewrite:{'^/car',''}}}}}

      2.修改axios请求地址

      axios.get('http://localhost:8080/stu/student').then(...)

      说明:

      ​ 优点:可以配置多个代理,可以灵活的控制请求是否走代理

      ​ 缺点:配置繁琐,请求资源时必须加前缀

      案例:github用户搜索

      样式、main.js、App.vue代码省略

      Search.vue

      <template><div class="search"><h1>Search User</h1><input type="text" placeholder="search" v-model="keyWord"><input type="button" value="search" @click="searchUser"></div></template><script>import axios from "axios";export default {name: 'MySearch',data() {return {keyWord: ''}},methods: {searchUser(){this.$bus.$emit('updateUser',{isFirst: false,isLoding: true,errorMessage: '',users: []})axios.get(`https://api.github.com/search/users" />${this.keyWord}`).then(response => {console.log(response.data.items);this.$bus.$emit('updateUser',{isLoding: false,errorMessage: '',users: response.data.items})},error => {console.log('请求失败',error.message);this.$bus.$emit('updateUser',{isLoding: false,errorMessage: error.message,users: []})})}},}</script>

      List.vue

      <template><div class="row"><div class="center" v-show="info.isFirst">WelCome!</div><div class="center" v-show="info.isLoding">Loding...</div><div class="center" v-show="info.errorMessage">{{info.errorMessage}}</div><div class="card"v-show="info.users.length"v-for="user in info.users":key="user.id"><a :href="user.html_url" target="_blank"><img:src="user.avatar_url"style="width: 100px"/></a><p class="card-text">{{user.login}}</p></div></div></template><script>export default {name: "MyList",data() {return {info:{isFirst: true,isLoding: false,errorMessage: '',users: []}}},methods: {updateUser(dataObj){this.info = {...this.info,...dataObj}}},mounted() {this.$bus.$on('updateUser',this.updateUser)},};</script>

      4.3.vue-resource

      vue-resource也是对xhr的封装的插件(了解即可)

      安装插件

      npm i vue-resource

      引入和使用插件

      import vueResource from 'vue-resource'Vue.user(vueResource)

      用法

      //用法与axios相同this.$http.get('url').then(response=>{},error=>{})

      4.4.插槽

      实现效果一(默认插槽)

      Category.vue (样式省略)

      <template><div class="category"><h3>{{title}}分类</h3><slot>我是默认值</slot></div></template><script>export default {name: 'MyCategory',props:['title']}</script>

      App.vue

      <template><div class="container"><Category title="美食"><img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg"></Category><Category title="饮料" :listData="drink"><ul><li v-for="(d,index) in drink" :key="index">{{d}}</li></ul></Category><Category title="电影"><video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video></Category></div></template><script>import Category from './components/Category.vue'export default {name: 'App',//注册组件components: { Category},data() {return {foods:['烧烤','炸鸡','牛排','小龙虾'],drink:['可乐','雪碧','啤酒','加多宝'],films:['《拆弹专家》','《复仇者联盟》','《夏洛特烦恼》','《飞驰人生》']}},}</script>

      具名插槽

      <div class="category"><h3>{{title}}分类</h3><slot name="slotName1">我是默认值1</slot><slot name="slotName2">我是默认值2</slot></div><Category title="饮料"><img slot="slotName1" src="#"><template v-slot:slotName2><a href="#">汽水</a><a href="#">奶茶</a><a href="#">咖啡</a></template></Category>

      作用域插槽

      实现效果

      Category.vue

      <template><div class="category"><h3>{{title}}分类</h3><slot :foods="foods">我是默认值</slot></div></template><script>export default {name: 'MyCategory',props:['title'],data() {return {foods:['烧烤','炸鸡','牛排','小龙虾'],}},}</script>

      App.vue

      <template><div class="container"><Category title="游戏"><template scope="xxx"><ul><li v-for="(g,index) in xxx.foods" :key="index">{{g}}</li></ul></template></Category><Category title="游戏"><template scope="{foods}"><ol><li v-for="(g,index) in foods" :key="index">{{g}}</li></ol></template></Category><Category title="游戏"><template slot-scope="{foods}"><h4 v-for="(g,index) in foods" :key="index">{{g}}</h4></template></Category></div></template><script>import Category from './components/Category.vue'export default {name: 'App',//注册组件components: { Category},}</script>

      总结:

      ​ 1.插槽的作用:让夫组件可以向子组件指定位置插入HTML结构 同时也是一种组件间通信方式 适用于父→子

      ​ 2.分类:默认插槽、具名插槽、作用域插槽

      ​ 3.作用域插槽的注意点:数据在组件自身,但根据数据生成的结构需要组件的使用者来决定

      5.vuex

      5.1.vuex概念

      vuex是专门在Vue中实现集中式状态(数据)管理的Vue插件,对vue应用中多个组件的共享状态进行集中式管理(读/写),也是组件间的通信方式 且适用于任意组件间的通信

      vuex的Github地址 https://github.com/vuejs/vuex

      对比vuex和全局事件总线

      什么时候使用vuex

      1.多个组件依赖同一状态 (数据)

      2.来自不同组件的行为需要变更同一状态

      vuex原理图

      5.2.搭建vuex环境

      安装vuex

      #vue2中用vuex3,vue3中用vuex4npm i vuex@3

      在src文件夹中创建store文件夹,然后在这个文件夹中创建index.js文件

      //该文件用于创建Vuex中最为核心的storeimport Vue from 'vue'import Vuex from 'vuex'//应用vuex插件之后就多了一个配置项storeVue.use(Vuex)//准备actions——用于响应组件中的动作const actions = {}//准备mutations——用于操作数据const mutations = {}//准备state——用于存储数据const state = {}//创建并暴露storeexport default new Vuex.Store({actions,mutations,state})

      main.js

      import Vue from 'vue';import App from './App.vue';import store from './store/index'Vue.config.productionTip = falsenew Vue({el: "#app",render: h=>h(App),store,})

      案例

      App.vue

      <template><div class="container"><Count/></div></template><script>import Count from './components/Count.vue'export default {name: 'App',//注册组件components: { Count},}</script>

      index.js

      import Vue from 'vue'import Vuex from 'vuex'//应用vuex插件Vue.use(Vuex)//准备actions——用于响应组件中的动作const actions = {//actions中的方法名一般小写addOdd(context,value){if(context.state.sum%2)context.commit('ADD',value)},addWait(context,value){setTimeout(()=>{context.commit('ADD',value)},1000)}}//准备mutations——用于操作数据const mutations = {//mutatios中的方法名一般大写ADD(state,value){state.sum += value},SUB(state,value){state.sum -= value},}//准备state——用于存储数据const state = {sum: 0}//创建并暴露storeexport default new Vuex.Store({actions,mutations,state})

      Count.vue

      <template><div><h1>当前求和为:{{$store.state.sum}}</h1><select v-model="n"><option :value="1">1</option><option :value="2">2</option><option :value="3">3</option></select><button @click="increment">+</button><button @click="decrement">-</button><button @click="incrementOdd">奇数再加1</button><button @click="incrementWait">等一等加1</button></div></template><script>export default {name: 'MyCount',data() {return {n: 1}},methods: {increment(){//可以直接提交this.$store.commit('ADD',this.n)},decrement(){this.$store.commit('SUB',this.n)},incrementOdd(){this.$store.dispatch('addOdd',this.n)},incrementWait(){this.$store.dispatch('addWait',this.n)}},}</script>

      5.3.getters配置项

      //省略代码....const getters = {bigSum(state){return state.sum*10}}//创建并暴露storeexport default new Vuex.Store({actions,mutations,state,getters})

      组件中使用

      <h1>{{$store.getters.bigSum}}</h1>

      5.4.四个map方法

      1.mapState方法:用于帮助我们映射state中的数据为计算属性

      <script>import { mapState } from 'vuex'export default {//其他配置项...computed:{/*xuexiao(){return this.$store.state.school},xinming(){return this.$store.state.name},*///对象写法(相当于上面注释的代码)...mapState({xuexiao:'school',xinming:'name'})//数组写法(前提是对象中的key和value相等)//如...mapState({school:'school',name:'name'})可以简写为下面形式//...mapState['school','name']}}</script>

      2.mapGetters方法:用于帮助我们映射getters中的数据为计算属性

      <script>import { mapGetters, mapState } from 'vuex'export default {//其他配置项...computed:{/*bigSum(){return this.$store.getters.bigSum},*///对象写法(相当于上面注释的代码)...mapGetters({bigSum:'bigSum'})//数组写法(前提是对象中的key和value相等)...mapGetters['bigSum']}}</script>

      3.mapMutations方法:用于帮助我们生成与mutations对话的方法,即包含$store.commit(xxx)的函数

      <script>import { mapMutations } from 'vuex'export default {//其他配置项...methods:{/*increment(){this.$store.commit('ADD',this.n)},decrement(){this.$store.commit('SUB',this.n)},*///对象写法(相当于上面注释的代码)...mapMutations({increment:'ADD',decrement:'SUB'})//数组写法(前提是对象中的key和value相等)//...mapMutations['ADD','SUB']}}</script>

      4.mapActionsfa方法:用于帮助我们生成与actions对话的方法,即包含$store.dispatch(xxx)的函数

      <script>import { mapActions } from 'vuex'export default {//其他配置项...methods:{/*incrementOdd(){this.$store.dispatch('addOdd',this.n)},incrementWait(){this.$store.dispatch('addWait',this.n)},*///对象写法(相当于上面注释的代码)...mapActions({incrementOdd:'addOdd',incrementWait:'addWait'})//数组写法(前提是对象中的key和value相等)//...mapActions['addOdd','addWait']}}</script>

      注意:使用mapMutations和mapMutations方法参数需要在模板绑定事件时传 如下所示

      5.5.vuex的模块化

      import Vue from 'vue'import Vuex from 'vuex'//应用vuex插件Vue.use(Vuex)const countOption = {namespaced:true,//默认是falseactions:{addOdd(context,value){/*内容略...*/},addWait(context,value){/*内容略...*/}},mutations:{ADD(state,value){/*内容略...*/},SUB(state,value){/*内容略...*/},},state:{sum: 0},getters:{bigSum(state){/*内容略...*/}},}const personOption = {namespaced:true,actions:{/*内容略...*/},mutations:{/*内容略...*/},state:{personList:[{id:'001',name:'Jack'},{id:'002',name:'Eric'}]},getters:{/*内容略...*/},}//创建并暴露storeexport default new Vuex.Store({modules:{//可以简写countOption,countOption:countOption,personOption:personOption}})
      <script>import { mapState,mapMutations,mapActions } from 'vuex'export default {//其他配置项省略...methods: {/*increment(){this.$store.commit('countOption/ADD',this.n)},*/...mapMutations('countOption',{increment:'ADD',decrement:'SUB'}),/*incrementOdd(){ this.$store.dispatch('countOption/addOdd',this.n)}*/...mapActions('countOption',{incrementOdd:'addOdd',incrementWait:'addWait'})},computed:{/*sum(){return this.$store.state.countOption.sum}*/...mapState('countOption',{sum:'sum'}),...mapState('personOption',['personList']),/*bigSum(){return this.$store.getters['countOption/bigSum']}*/...mapGettera('countOption',['bigSum']),}}</script>

      6.路由

      1.路由就是一组key-value的对应关系

      2.多个路由需要经过路由器的管理

      6.1.路由的基本使用

      实现案例

      安装vue-router

      #vue-router@3只能在vue2中使用#vue-router@4只能在vue3中使用npm i vue-router@3 

      在src文件夹中创建文件夹router,然后在这个文件夹中创建index.js

      //该文件专门用于创建路由器import VueRouter from 'vue-router'//引入组件import About from '../components/About'import Home from '../components/Home'//创建并暴露路由器export default new VueRouter({routes:[{path: '/about',component: About},{path: '/home',component: Home},]})

      main.js

      import Vue from 'vue';import App from './App.vue';import VueRouter from 'vue-router';import router from "./router/index";Vue.config.productionTip = falseVue.use(VueRouter)new Vue({el: "#app",render: h=>h(App),router})

      App.vue(样式省略)

      <template><div class="container"><Banner/><div class="btns"><router-link to="/about" active-class="active" class="optionAbout">About</router-link><router-link to="/home"active-class="active" class="optionHome">Home</router-link></div><div class="component"><router-view></router-view></div></div></template><script>import Banner from './components/Banner'export default {name: 'App',//注册组件components: { Banner },}</script>

      Home.vue和About.vue

      <template><h1>我是Home组件</h1></template><script>export default {name: 'MyHome'}</script><template><h1>我是About组件</h1></template><script>export default {name: 'MyAbout'}</script>

      使用路由的几个注意点:

      ​ 1.路由组件一般存放在src/pages文件夹中 普通组件放在components文件夹中

      ​ 2.切换路由时,被切换掉的路由默认是被销毁了

      ​ 3.每个组件都有自己的$route属性,里面存放着自己的路由信息

      ​ 4.整个应用只有一个router,通过组件的$router属性可以获得

      6.2.嵌套路由

      routes:[{//一级路由path: '/about',component: About,children:[{//子级路由path属性无需加'/'path:'news',component: News},{path:'msgs',component:Message}]},{...},{...},]

      在使用router-link标签时的to属性要写完整的路径

      <router-link to="/about/news" active-class="active" class="aboutBtn">News</router-link><router-link to="/about/msgs" active-class="active" class="aboutBtn">Msgs</router-link>

      6.3.路由传参

      query参数

      <router-link to="/about/news/detail" />">News</router-link><router-link :to="`/about/news/detail?id=${m.id}&title=${m.title}`">News</router-link><router-link :to="{path: '/about/news/detail',query:{id: m.id,title: m.title}}">xxx内容</router-link>{{$route.query.id}}{{$route.query.title}}

      params参数

      使用params参数需要在创建路由时配置path参数时使用占位符

      export default new VueRouter({routes:[{//一级路由name: 'guanyu',path: '/about',component: About,children:[{//二级路由name: 'xinwen'path:'news/:id/:title',component: News,},]},]})
      <router-link to="/about/news/666/hello">News</router-link><router-link to="`/about/news/${m.id}/${m.title}`">News</router-link><router-link :to="{name: 'xinwen',params:{id: m.id,title: m.title}}">xxx内容</router-link>{{$route.params.id}}{{$route.params.title}}

      6.4.路由命名

      作用:简化路由跳转

      export default new VueRouter({routes:[{//一级路由name: 'guanyu',path: '/about',component: About,children:[{//二级路由path:'news',component: News,children:[{//三级路由name: 'xiangqing',path:'detail',component: Detail} ]},{...},{...}]},{...},{...}]})

      使用命名路由在跳转时可以使用name属性,这样就无需写很长的path属性

      <router-link :to="{name: 'xiangqing',query:{id: m.id,title: m.title}}">xxx内容</router-link>

      6.5.路由的props配置

      export default new VueRouter({routes:[{path: '/home',component: Home,//第一种写法:值为对象,该对象中的所有k-v都会以props的形式传给Home组件// props:{a:1,b:100}//第二种写法:值为布尔值,若为true,就会把该组件收到的params参数以props的形式传给Home组件// props:true//第三种写法:值为函数,将函数的返回值对象的k-v以props的形式传给Home组件props($route){return {id:$route.query.id,title:$route.query.title}}//第三种写法的结构赋值写法// props({query}){// return {id:query.id,title:query.title}// }},]})

      6.6.replace属性

      标签的replace属性

      ​ 1.作用:控制路由跳转时操作浏览器历史记录的模式

      ​ 2.浏览器的历史记录有两种写入方式

      ​ push模式:追加历史记录(默认值)

      ​ replace:替换当前记录

      ​ 3.开启replace模式:

      <router-link replace ...>xxx</router-link>

      6.7.编程式路由导航

      编程式路由导航不借助实现路由跳转,让路由跳转更加灵活

      <button @click='pushShow(m)'>xxx</button>

      $router.push

      methods:{pushShow(m){this.$router.push({path: '...',//path只能配合query使用,不能配合paramsquery:{id:m.id,title:m.title}})}}

      $router.replace

      methods:{pushShow(m){this.$router.replace({name: '...',//query和params都行params:{id:m.id,title:m.title}})}}

      历史记录前进和后退

      method:{test(){this.$router.back() //后退一步this.$router.forward() //前进一步this.$router.go(num) //num为正数表示前进num步,负数表示后退num步}}

      6.8.缓存路由组件

      作用:让不展示的路由保持挂载,不被销毁

      <keep-alive include="News"><router-view></router-view></keep-alive><keep-alive :include="[News,Messages,...]"><router-view></router-view></keep-alive>

      6.9.两个新的生命周期钩子

      <script>export default {name: 'MyNews',activated(){//路由组件被激活时调用},deactivated(){//路由组件失活时调用},}</script>

      总结:

      ​ 1.作用:路由组件所独有的钩子,用于捕获路由的激活状态

      ​ 2.具体名字:

      ​ activated 路由组件被激活时调用

      ​ deactivated 路由组件失活时调用

      6.10.路由守卫

      全局前置路由守卫

      //....//创建路由const router = new VueRouter({routes:[{...},{...},...]})//全局前置路由守卫——初始化和每次路由切换前调用router.beforeEach((to,from,next)=>{//to表示切换到哪个组件 from表示从哪个组件切换 next是放行路由切换if(to.name === 'xinwen' || to.path === '/message'){if(localStorage.getItem('school') === 'zcmu'){//如果school是zcmu就放行next()}else{alert('暂无权限')}}else{next()}})//暴露路由export default router

      全局后置路由守卫

      const router = new VueRouter({routes:[{name: 'guanyu'path: '/about',component: About, //meta路由元信息meta:{//可以配置些自定义信息isAuth: true,title: '关于'}},{...},]})//全局后置路由守卫——初始化和每次路由切换后调用router.afterEach((to,from)=>{documemnt.title = to.meta.title || 'xxx'})

      独享路由守卫

      独享路由守卫只有前置守卫没有后置守卫

      const router = new VueRouter({routes:[{name: 'guanyu'path: '/about',component: About,meta: {isAuth: true,title: '关于'},//配置独享路由守卫,About组件独享的beforeEnter:(to,from,next)=>{//......}},{...},]})

      组件内路由守卫

      <script>export default {name: 'MyNews',data() { return {/* ... */}},beforeRouteEnter (to, from, next) {//通过路由规则进入该组件时调用},beforeRouteLeave (to, from, next) {//通过路由规则离开该组件时调用}}</script>

      6.11.路由器的工作模式

      hash模式 (默认)

      ​ 1.地址中带有#, #后面的时hash值,hash值不会带给服务器

      ​ 2.兼容性好

      history模式

      ​ 1.地址中不会出现# 比较美观

      ​ 2.兼容性略差

      ​ 3.应用部署上线时需要后端人员支持,解决刷新页面服务端404问题

      const router = new VueRouter({mode:'history',routes: [{...},{...},...]})

      项目打包命令

      #会生成dist文件夹npm run build

      创建简单服务器

      1.创建文件夹用VSCode打开2.npm init初始化3.npm i express,安装express4.创建server.js文件,编写下面代码5.创建static文件夹,将dist种的文件拷贝到该文件夹6.node server启动7.打开浏览器访问http://localhost:5000
      const express = require('express')const app = express()app.use(express.static(__dirname+'/static'))/*app.get('/person',(req,res)=>{res.send({name:'Jack',age: 20})})*/app.listen(5000,(err)=>{if(!err)console.log('服务器启动成功');})

      connect-history-api-fallback可以解决history模式刷新页面404问题

      //安装npm i connect-history-api-fallback //导入const history = require('connect-history-api-fallback')//使用app.use(history())//要在这行代码之前使用app.use(express.static(__dirname+'/static'))

      7.VueUI组件库

      7.1.移动端常用UI组件库

      1. Vant https://youzan.github.io/vant
      2. Cube UI https://didi.github.io/cube-ui
      3. Mint UI http://mint-ui.github.io

      7.2 PC 端常用UI组件库

      1. Element UI https://element.eleme.cn
      2. IView UI https://www.iviewui.com

      7.3.ElementUI简单使用

      安装

      npm i element-ui

      在main.js中引入

      //完整引入import ElementUI from 'element-ui';import 'element-ui/lib/theme-chalk/index.css';Vue.use(ElementUI);

      接下来CV就行了

      7.4 按需引入

      安装插件

      npm install babel-plugin-component -D

      修改babel.config.js

      {"presets": [["@babel/preset-env", { "modules": false }]],"plugins": [["component",{"libraryName": "element-ui","styleLibraryName": "theme-chalk"}]]}

      只引入Button和Select的话

      //按需引入import {Button,Select} from 'element-ui';Vue.component(Button.name,Button)Vue.component(Select.name,Select)/*或者写为Vue.use(Button)Vue.use(Select)*/

      若出现模块找不到就安装模块

      如 Error:Cannot find module ‘xxx’

      npm i xxx

      8.Vue3

      8.1创建vue3.0工程

      使用vue-cli创建

      #查看vue-cli版本 确保在4.5.0以上vue -V 或 vue --version#创建vue create 项目名#启动npm run serve

      使用vite创建

      #创建工程npm init vite-app 项目名#进入工程目录cd 项目名#安装依赖npm install#运行npm run dev

      vue3工程中的变化

      1.vue中main.js引入的不在是Vue构造函数

      //vue3引入的不再是Vue的构造函数//而是名为createApp的工厂函数import { createApp } from 'vue'import App from './App.vue'//创建应用实例对象并挂载createApp(App).mount('#app')

      2.vue3组件模板结构中可以没有根标签

      <template><div>xxx</div><div>yyy</div><Hello/></template>

      8.2.常用Composition API

      8.2.1.setup函数

      <template><h3>姓名:{{name}}</h3><h3>年龄:{{age}}</h3><button @click="sayHello">sayHello</button></template><script>//import {h} from 'vue'export default {name: 'App',setup(){let name = 'Jack'let age = 20function sayHello() {alert(`大家好,我是${name},我今年${age}岁`)}//返回对象return {name,age,sayHello}//返回渲染函数 需要引入h (import {h} from 'vue')//return ()=>{ return h('h1',name) }}}</script>

      总结:

      1. 组件中所用到的:数据、方法等等,均要配置在setup中

      2. setup函数的两种返回值:

        1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用(重点关注!)
        2. 若返回一个渲染函数:则可以自定义渲染内容(了解)
      3. 注意点:

        1. 尽量不要与Vue2.x配置混用

          Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法

          但在setup中不能访问到Vue2.x配置(data、methos、computed…)

          如果有重名, setup优先

        2. setup不能是一个async函数

      8.2.2.ref函数

      <template><h3>姓名:{{name}}</h3><h3>年龄:{{age}}</h3><h3>工作类型:{{job.type}}</h3><h3>工作薪水:{{job.salary}}</h3><button @click="changeMsg">修改信息</button></template><script>import { ref } from '@vue/reactivity'export default {name: 'App',setup(){// 这样写的数据是非响应式的// let name = 'Jack'// let age = 20let name = ref('Jack')let age = ref(20)let job = ref({type:'前端工程师',salary: '20K'})function changeMsg(){name.value = 'Eric'age.value = '18'job.value.type = '后端工程师'}//返回对象return {name,age,changeMsg}}}</script>

      总结:

      ​ 1.作用: 定义一个响应式的数据

      ​ 2.语法: const xxx = ref(initValue)

      ​ 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)

      ​ JS中操作数据: xxx.value

      ​ 模板中读取数据: 不需要.value,直接:

      {{xxx}}

      ​ 备注:

      ​ 接收的数据可以是:基本类型、也可以是对象类型

      ​ 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的

      ​ 对象类型的数据:内部求助了Vue3.0中的一个新函数—— reactive函数

      8.2.3.reactive函数

      <template><h3>工作类型:{{job.type}}</h3><h3>工作薪水:{{job.salary}}</h3><h3>工作薪水:{{job.a.b.c}}</h3><h3>个人爱好:{{hobby}}</h3><button @click="changeMsg">修改信息</button></template><script>import { reactive, ref } from '@vue/reactivity'export default {name: 'App',setup(){//reactive只能用于引用数据类型 //不能用于基本数据类型let job = reactive({type:'前端工程师',salary: '20K',a:{b:{c: 666}}})let hobby = reactive(['唱歌','跳舞','篮球'])function changeMsg(){job.type = '后端工程师'job.salary = '30k'job.a.b.c = 888hobby[0] = 'rapper'}//返回对象return {job,hobby,changeMsg}}}</script>

      总结:

      ​ 1.作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)

      ​ 2.语法:const 代理对象= reactive(源对象)接收一个对象(或数组)返回一个代理对象

      ​ 3.reactive定义的响应式数据是“深层次的”

      ​ 4.内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作

      8.2.4.Vue3响应式原理

      回顾Vue2中的响应式原理

      • 实现原理:

        ​ 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)

        ​ 数组类型:通过重写更新数组的一系列方法来实现拦截

      • 存在问题:

        ​ 新增属性、删除属性, 界面不会更新

        ​ 直接通过下标修改数组, 界面不会自动更新

      模拟Vue3中的响应式

      <script>//源数据let person = {name: 'Jack',sex: '男'}//p是代理对象 person是源数据const p = new Proxy(person,{get(target,propName){//target是被代理的对象,propName是属性名console.log(`person的${propName}属性被读取了`);//return target[propName]return Reflect.get(target,propName)},set(target,propName,value){console.log(`person的${propName}属性被修改了,修改的值是${value}`);//target[propName] = valueReflect.set(target,propName,value)},deleteProperty(target,propName){console.log(`person的${propName}属性被删除了`);//return delete target[propName]return Reflect.deleteProperty(target,propName)}})</script>

      实现原理:

      • 通过Proxy(代理): 拦截对象中任意属性的变化 包括:属性值的读写、属性的添加、属性的删除等。
      • 通过Reflect(反射): 对源对象的属性进行操作。

      8.2.5.reactive和ref的对比

      1.从定义数据角度对比:

      ​ ref用来定义:基本类型数据

      ​ reactive用来定义:对象(或数组)类型数据

      ​ 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象

      2.从原理角度对比:

      ​ ref通过Object.defineProperty()getset来实现响应式(数据劫持)

      ​ reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflec操作源对象内部的数据

      3.从使用角度对比:

      ​ ref定义的数据:操作数据需要.value, 读取数据时模板中直接读取不需要.value

      ​ reactive定义的数据:操作数据与读取数据:均不需要.value

      8.2.6.setup的两个注意点

      1.setup执行的时机

      ​ 在beforeCreate之前执行一次, this是undefined

      2.setup的参数

      ​ props:值为对象,包含:组件外部传递过来, 且组件内部声明接收了的属性

      ​ context:上下文对象

      ​ 1.attrs: 值为对象, 包含:组件外部传递过来, 但没有在props配置中声明的属性,相当于 this.$attrs

      ​ 2.slots: 收到的插槽内容, 相当于 this.$slots

      ​ 3.emit: 分发自定义事件的函数, 相当于 this.$emit

      8.2.7.计算属性

      <template>姓:<input type="text" placeholder="请输入姓" v-model="person.firstName"> <br><br>名:<input type="text" placeholder="请输入名" v-model="person.lastName"> <br><br><h1>全名:{{fullName}}</h1></template><script>import { reactive } from '@vue/reactivity'import { computed } from '@vue/runtime-core'export default {name: 'Demo',setup() {//数据let person = reactive({firstName: '张',lastName: '三'})//计算属性 简写形式let fullName = computed(()=>{return person.firstName + '-' + person.lastName})//计算属性 完整写法/* let fullName = computed({get(){...},set(value){...},}) */return{person,fullName}}}</script>

      8.2.8监视属性

      <script>import { ref } from '@vue/reactivity'import { watch } from '@vue/runtime-core'export default {name: 'Demo',setup() {let sum = ref(0)let messgae = ref('hello')let person = {name: 'Jack',job:{type: '前端工程师',salary: '20K'}}//情况一:监视ref定义的属性watch(sum,(newVal,oldVal)=>{console.log('sum的值改变了',newVal,oldVal);},{immediate:true})//情况二:同时监视多个ref定义的属性watch([sum,messgae],(newVal,oldVal)=>{//同时监视多个属性时newVal和oldVal是数组console.log('sum或message的值改变了',newVal,oldVal);})//情况三:监视reactive定义的响应数据的全部属性watch(person,(newVal,oldVal)=>{//注意://1.无法获取oldVal (oldVal和newVal的值相同)//2.强制开启深度监视 (deep:false无效)console.log('person变化了',newVal,oldVal);})//情况四:监视reactive所定义的响应式数据的某个属性watch(()=>person.name,(newVal,oldVal)=>{console.log('person变化了',newVal,oldVal);})//情况五:监视reactive所定义的响应式数据的某些属性watch([()=>person.name,()=>person.age],(newVal,oldVal)=>{console.log('person变化了',newVal,oldVal);})//特殊情况:监视reactive所定义的响应式数据的对象类型属性watch(()=>person.job,(newVal,oldVal)=>{console.log('person变化了',newVal,oldVal);},{deep:true})return{sum,messgae,person}}}</script>

      两个小坑:

      • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)
      • 监视reactive定义的响应式数据中某个属性时:deep配置有效

      watchEffect函数

      <script>import { ref } from '@vue/reactivity'import { watchEffect } from '@vue/runtime-core'export default {name: 'Demo',setup() {let sum = ref(0)let person = {name: 'Jack',job:{type: '前端工程师',salary: '20K'}}watchEffect(()=>{const x1 = sum.valueconst x2 = person.job.salaryconsole.log('sum或salary的值变化了');})return{sum,person}}}</script>

      总结:

      ​ watch的套路是:既要指明监视的属性,也要指明监视的回调

      ​ watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性

      ​ watchEffect有点像computed:

      ​ 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值

      ​ 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值

      8.2.9.Vue3生命周期

      <script>import { onMounted, onUnmounted, onUpdated, ref } from 'vue'export default {name: 'Demo',setup() {let sum = ref(0)//通过组合式API的形式使用生命周期钩子onMounted(()=>{console.log('===mounted===');})onUpdated(()=>{console.log('===updated===');})onUnmounted(()=>{console.log('===unmounted===');})return{sum}}}</script>

      Vue3.0中可以继续使用Vue2.x中的生命周期钩子, 但有有两个被更名:

      • beforeDestroy改名为 beforeUnmount
      • destroyed改名为 unmounted

      Vue3.0也提供了 组合式API(写在setup函数中) 形式的生命周期钩子, 与Vue2.x中钩子对应关系如下:

      • beforeMount ===>onBeforeMount
      • mounted===>onMounted
      • beforeUpdate===>onBeforeUpdate
      • updated ===>onUpdated
      • beforeUnmount= ==>onBeforeUnmount
      • unmounted ===>onUnmounted

      8.2.10.自定义hook函数

      案例:显示鼠标坐标

      普通写法

      <template><h1>当前鼠标的坐标:X{{point.x}} Y{{point.y}}</h1></template><script>import { onBeforeUnmount, onMounted, reactive, } from 'vue'export default {name: 'Demo',setup() {let point = reactive({x:0,y:0})function savePoint(event){point.x = event.pageXpoint.y = event.pageY}onMounted(()=>{window.addEventListener('click',savePoint)})onBeforeUnmount(()=>{window.removeEventListener('click',savePoint)})return{point}}}</script>

      将setup中关于显示鼠标坐标的组合式API封装成hook函数

      首先再src文件夹下创建hooks文件夹,在该文件夹中创建usePoint.js

      import { onBeforeUnmount, onMounted, reactive, } from 'vue'export default function(){let point = reactive({x:0,y:0})function savePoint(event){point.x = event.pageXpoint.y = event.pageY}onMounted(()=>{window.addEventListener('click',savePoint)})onBeforeUnmount(()=>{window.removeEventListener('click',savePoint)})return point}

      在组件中使用hook函数

      <template><h1>当前鼠标的坐标:X{{point.x}} Y{{point.y}}</h1></template><script>import usePoint from '../hooks/usePoint'export default {name: 'Demo',setup() {//其他功能的数据、函数...//使用自定义的hook函数let point = usePoint()return{point}}}</script>

      总结:

      ​ 1.什么是hook?

      ​ 本质是一个函数,把setup函数中使用的Composition API进行了封装

      ​ 2.自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂

      8.2.11.toRef和toRefs函数

      <script>import { reactive, toRef, toRefs } from 'vue'export default {name: 'Demo',setup() {let person = reactive({name: 'Jack',age: 20,job:{type: '前端工程师',salary: '20K'}})return{person,name: toRef(person,'name'),age: toRef(person,'age'),type: toRef(person.job,'type'),salary: toRef(person.job,'salary'),// ...toRefs(person)}},}</script>

      总结:

      • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性

      • 语法:const name = toRef(person,'name')

      • 应用: 要将响应式对象中的某个属性单独提供给外部使用时

      • 扩展:toRefstoRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)

      8.3.其他Composition API

      8.3.1.shallowRef和shallowReactive

      1.shallowReactive:只处理对象最外层属性的响应式(浅响应式)

      2.shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理

      什么时候使用” />let person = reactive({name: ‘Jack’,age: 20,job:{type: ‘前端工程师’,salary: ’20K’}})//person中所有的属性都变成只读的person = readonly(person)//person中浅层次的属性变成只读的person = shallowReadonly(person)

      总结:

      ​ 1.readonly: 让一个响应式数据变为只读的(深只读)

      ​ 2.shallowReadonly:让一个响应式数据变为只读的(浅只读)

      ​ 3.应用场景: 不希望数据被修改时

      8.3.3.toRaw和markRaw

      let person = reactive({name: 'Jack',age: 20,job:{type: '前端工程师',salary: '20K'}})//将响应式的数据变为普通数据const p = toRow(person)let car = {name:"宝马",price:'40W'} //标记一个对象,它永远不会再成为响应式对象p.car = markRaw(car)

      总结:

      1.toRaw:

      • 作用:将一个由reactive生成的响应式对象转为普通对象
      • 使用场景:用于读取响应式对象对应的普通对象, 对这个普通对象的所有操作, 不会引起页面更新

      2.markRaw:

      • 作用:标记一个对象,使其永远不会再成为响应式对象

      • 应用场景:

        1. 有些值不应被设置为响应式的,例如复杂的第三方类库等
        2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能

      8.3.4.customRef

      作用:创建自定义的ref,并对其依赖项目跟踪和更新触发进行显示控制

      实现案例:上面的输入框修改1秒后下面的输入框改变(实现防抖效果)

      <template><input type="text" v-model="keyWord"><br><br><input type="text" :value="keyWord"></template><script>import { customRef } from 'vue'export default {name: 'App',setup(){//自定义ref函数function myref(value,delay){let timer//通过customRef去实现自定义return customRef((track,trigger)=>{return {get(){//通知vue追踪value的变化track()return value},set(newValue){clearTimeout(timer) //实现防抖timer = setTimeout(() => {value = newValue//通知vue重新解析模板trigger()}, delay);}}})}let keyWord = myref('hello',1000)return{keyWord}}}</script>

      8.3.5.provide和inject

      作用:实现祖与后代组件间通信

      套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

      具体写法:

      1. 祖组件中:

        import { provide } from 'vue'//...setup(){......let car = reactive({name:'奔驰',price:'40万'})provide('car',car)......}
      2. 后代组件中:

        import { inject } from 'vue'//...setup(props,context){......const car = inject('car')return {car}......}

      8.3.6.响应式数据的判断

      • isRef: 检查一个值是否为一个 ref 对象
      • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
      • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
      • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

      8.3.7.组合式API的优点

      我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起

      8.4新的组件

      8.4.1.Fragment

      在Vue2中: 组件必须有一个根标签

      在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中

      好处: 减少标签层级, 减小内存占用

      8.4.2.Teleport

      什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术

      我是一个弹窗

      8.4.3.Suspense

      等待异步组件时渲染一些额外内容,让应用有更好的用户体验

      使用步骤:

      • 异步引入组件

        import {defineAsyncComponent} from 'vue'const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
      • 使用Suspense包裹组件,并配置好defaultfallback

        <template><div class="app"><h3>我是App组件</h3><Suspense><template v-slot:default><Child/></template><template v-slot:fallback><h3>加载中.....</h3></template></Suspense></div></template>

      8.5其他

      8.5.1.全局API的转移

      Vue 2.x 有许多全局 API 和配置 例如注册全局组件、注册全局指令等

      Vue3.0中对这些API做出了调整:

      • 将全局的API,即:Vue.xxx调整到应用实例(app)上

        2.x 全局 API(Vue3.x 实例 API (app)
        Vue.config.xxxxapp.config.xxxx
        Vue.config.productionTip移除
        Vue.componentapp.component
        Vue.directiveapp.directive
        Vue.mixinapp.mixin
        Vue.useapp.use
        Vue.prototypeapp.config.globalProperties

      8.5.2.其他改变

      1.data选项应始终被声明为一个函数。

      2.过度类名的更改:

      • Vue2.x写法

        .v-enter,.v-leave-to {opacity: 0;}.v-leave,.v-enter-to {opacity: 1;}
      • Vue3.x写法

        .v-enter-from,.v-leave-to {opacity: 0;}.v-leave-from,.v-enter-to {opacity: 1;}

      3.移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes

      4.移除v-on.native修饰符

      • 父组件中绑定事件

        <my-componentv-on:close="handleComponentEvent"v-on:click="handleNativeClickEvent"/>
      • 子组件中声明自定义事件

        <script>export default {emits: ['close']}</script>

      5.移除过滤器(filter)

      6…