前端VueVue学习笔记
定西Vue
2023年1月4日 19点31分
axios的源码我没看,直接开始vue吧
Vue基础
Vue简介
Vue是什么?
一套用于构建用户见面的渐进式JavaScript框架
尤雨溪,好像是美籍华人,很厉害
vue的特点
- 采用组件化模式,提高代码复用率,且让代码更好维护
 
- 声明式编码,让编码人员无需直接操作DOM,提高开发效率
 
- 使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点
 
原生Js

Vue实现

不得不说,vue官网的文档做的是真的nice
Vue开发环境
下载地址
还要下载一个插件-Vue Devtools
直接在chrome插件商店里就可以下载了
然后第一个Vue实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   | <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <meta http-equiv="X-UA-Compatible" content="IE=edge">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <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.config.productionTip = false  
                   new Vue({             el:'#root',               data:{                  name: 'ZZMR'             }         })
      </script> </body> </html>
   | 
 
- 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
 
- root容器里面的代码依然符合html规范,只不过混入了一些特殊的Vue语法
 
- root容器里的代码被称为Vue
 
一些小细节
- 容器和Vue实例是一一对应的,不能多对一/一对多
 
- 真是开发中只有一个Vue实例,并且会配合着组件一起使用
 
{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性 
- 一旦data中的数据发生改变,那么模板中用到该数据的地方也会自动更新
 
模板语法
首先就是插值语法:{{}}
还有指令语法:v-bind:xxx(这个可以简写成一个冒号)
但是什么时候用插值,什么时候用指令
- 插值语法:
- 功能: 用于解析标签体内容
 
写法: {{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性 
 
- 指令语法:
- 功能: 用于解析标签(包括: 标签属性,标签体内容,绑定事件)
 
- 举例: v-bind:href=”xxx” 或 简写成 :href=”xxx”,xxx同样要写成js表达式
且可以直接读取到data中的所有属性 
- 备注: Vue中有很多的指令,且形式都是: v-????,此处我们只是拿v-bind举个例子
 
 
多级结构
1 2 3 4 5 6 7 8 9 10
   | new Vue({             el:'#root',             data:{                 name:'Jack',                 school:{                     name:'ZZMR',                     url:'https://jimmy66886.github.io/'                 }             }         })
  | 
 
此时再获取,就可以用school.name,用于和name区分开
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   |  <div id="root">     <h1>插值语法</h1>     <h3>你好,{{name}}</h3>     <hr>     <h1>指令语法</h1>     <a v-bind:href="school.url">点我去{{school.name}}</a>      </div>
 
  <script type="text/javascript">     new Vue({         el:'#root',         data:{             name:'Jack',             school:{                 name:'ZZMR',                 url:'https://jimmy66886.github.io/'             }         }     }) </script>
 
  | 
 
数据绑定
数据绑定分为单向数据绑定和双向数据绑定
而v-bind就是单向数据绑定:data数据影响页面数据,但是页面数据不会影响data中的数据
- 单向绑定(v-bind): 数据只能从data流向页面
 
- 双向绑定(v-model): 数据不仅能从data流向页面,还可以从页面流向data
- 备注:
- 双向绑定一般都应用在表单类元素上(input,select等)
 
- v-model:value可以简写成 v-model, 因为v-model默认收集的就是value值
 
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   |  <div id="root">     单向数据绑定: <input type="text" v-bind:value="name"><br>     双向数据绑定: <input type="text" v-model:value="name"><br>          双向数据绑定: <input type="text" v-model="name"><br>           </div>
 
  <script type="text/javascript">     new Vue({         el:'#root',         data:{             name:'ZZMR'         }     }) </script>
 
  | 
 
el与data的两种写法
data与el的两种写法
- el的两种写法
- new Vue时配置el属性
 
- 先创建Vue实例,随后再通过vm.$mount(‘#root’)指定el的值
 
 
- data有两种写法
- 对象式
 
- 函数式
如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错 
 
- 一个重要的原则:
由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
   |      <div id="root">         <h1>你好 {{name}}</h1>     </div>
 
      <script type="text/javascript">         
 
 
 
 
 
 
 
 
                   new Vue({             el: '#root',                          
 
 
                           data() {                 console.log('@@@', this);                  return {                     name: 'ZZMR'                 }             }         })
      </script>
 
  | 
 
MVVM模型
- M: 模型(Model) 对应data中的数据
 
- V: 视图(View) 模板
 
- VM: 视图模型(ViewModel) Vue实例对象
 

特点:
- data种所有的属性,最后都出现在了vm身上
 
- vm身上所有的属性及Vue原型上所有属性,在Vue模板中都可以直接使用
 
数据代理
defineProperty方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   | <script type="text/javascript">
      let number = 18
      let person = {         name: '张三',         sex: '男'     }
      Object.defineProperty(person, 'age', {         
 
 
 
                   get(){             console.log('有人读取age属性');             return number         },
                   set(value){             console.log('有人修改了age属性,修改为:'+value);             number = value         }
         })          console.log(person);
  </script>
   | 
 
这个的确不知道,但是听了听还是没啥问题的
何为数据代理
数据代理: 通过一个对象对另一个对象中属性的操作(读/写)
就是利用了Object.defineProperty()方法的特性-getter和setter
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   |  <script>     let obj = {x:100}     let obj2 = {y:200}
      Object.defineProperty(obj2,'x',{         get(){             return obj.x;         },         set(value){             obj.x = value;         }     }) </script>
 
  | 
 
Vue中的数据代理

- Vue中的数据代理
- 通过vm对象来代理data对象中属性的操作(读/写)
 
 
- Vue中数据代理的好处
 
- 基本原理
- 通过Object.defineProperty()把data对象中所有属性添加到vm上,为每一个添加到vm上的属性,都指定一个getter/setter,在getter/setter内部去操作(读/写)data中对应的属性
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | <div id="root">     <h2>学校名称: {{name}}</h2>     <h2>学校地址: {{address}}</h2> </div>
  <script>     const vm = new Vue({         el: '#root',         data: {             name: 'ZZMR',             address: 'China'         }     }) </script>
   | 
 
事件处理
事件的基本使用
- 使用v-on:xxx或@xxx绑定事件,其中xxx是事件名
 
- 事件的回调需要配置在methods对象中,最终会在vm上
 
- methods中配置的函数,不要用箭头函数,否则this就不是vm了
 
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象
 
- @click=”demo”和@click=”demo($event)” 效果一致,但后者可以传参
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | <div id="root">         <h2>欢迎 {{name}} </h2>                           <button @click="showInfo1">点我提示信息1-不传参</button>         <button @click="showInfo2(66,$event)">点我提示信息2-传参</button>     </div>
      <script>         const vm = new Vue({             el: '#root',             data: {                 name: 'ZZMR'             },             methods: {                 showInfo1(event) {                     alert('同学你好1')                                      },                 showInfo2(number, event) {                                          alert('同学你好2')                 }             }         })     </script>
   | 
 
事件修饰符
- prevent: 阻止默认事件(常用)
 
- stop: 阻止事件冒泡(常用)
 
- once: 事件只触发一次(常用)
 
- capture: 使用事件的捕获模式
 
- self: 只有event.target是当前操作的元素时才触发事件
 
- passive: 事件的默认行为立即执行,无需等待事件回调执行完毕
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
   | <div id="root">         <h2>欢迎 {{name}} </h2>                  <a href="https://jimmy66886.github.io/" @click.prevent="showInfo">点我提示信息</a>
                   <div @click="showInfo" class="demo1">             <button @click.stop="showInfo">点我提示信息</button>         </div>
                   <button @click.once="showInfo">点我提示信息</button>         <br>
                   <div class="box1" @click.capture="showMsg(1)">             div1             <div class="box2" @click="showMsg(2)">                 div2             </div>         </div>
                   <div @click.self="showInfo" class="demo1">             <button @click="showInfo">点我提示信息</button>         </div>
                   <ul @wheel="demo" class="list">             <li>1</li>             <li>2</li>             <li>3</li>             <li>4</li>         </ul>
      </div>
      <script>         const vm = new Vue({             el: '#root',             data: {                 name: 'ZZMR'             },             methods: {                 showInfo(e) {                                                               alert('同学你好')                                      },                 showMsg(msg) {                     console.log(msg)                 },                 demo() {                     console.log('@')                 }
              }         })     </script>
   | 
 
键盘事件
按键别名
- 回车 => enter
 
- 删除 => delete(捕获”删除”和”退格”键)
 
- 退出 => esc
 
- 空格 => space
 
- 换行 => tab (特殊,必须配合keydown使用)
 
- 上 => up
 
- 下 => down
 
- 左 => left
 
- 右 => right
 
- Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转化为kebab-case(短横线命名),比如CapsLock按键=caps-lock
 
- 系统修饰键(用法特殊): ctrl alt shift meta
- 配合keyup使用,按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
 
- 配合keydown使用,正常触发事件
 
 
- 也可以使用keyCode去指定具体的按键(不推荐)
 
- Vue.config.keyCodes.自定义键名 = 键码 ,可以去定制按键别名
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | <div id="root">         <h2>欢迎来到{{name}}</h2>         <input type="text" placeholder="按下回车提示输入" @keydown.alt="showInfo">     </div>
      <script>
          Vue.config.keyCodes.huiche = 13
          const vm = new Vue({             el: '#root',             data: {                 name: 'ZZMR'             },             methods: {                 showInfo(e) {                                                               console.log(e.target.value)                 }             }         })     </script>
   | 
 
计算属性
插值语法实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | <div id="root">     姓: <input type="text" v-model="firstName"><br>     名: <input type="text" v-model="lastName"><br>     全名: <span>{{firstName}}-{{lastName}}</span> </div>
  <script>     const vm = new Vue({         el: '#root',         data: {             firstName: '张',             lastName: '三'         }     }) </script>
   | 
 
很简单啊,直接拼
methods实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | <div id="root">     姓: <input type="text" v-model="firstName"><br>     名: <input type="text" v-model="lastName"><br>     全名: <span>{{fullName()}}</span> </div>
  <script>     const vm = new Vue({         el: '#root',         data: {             firstName: '张',             lastName: '三'         },         methods: {             fullName() {                 return this.firstName + '-' + this.lastName             }         }     }) </script>
   | 
 
更便于观察了,在插值语法中直接调用函数,来实现拼接姓名
计算属性实现
- 定义: 要使用的属性不存在,要通过已有**属性(一定要是属性,如果是变量什么的不行)**计算得到
 
- 原理: 底层借助了Object.defineproperty方法提供的getter和setter
 
- get函数什么时候执行
- 初次读取时会执行一次
 
- 当以来的数据发生改变时会被再次调用
 
 
- 优势: 与methods实现相比,内部有缓存机制(复用) 效率更高,调试方便
 
- 备注:
- 计算属性最终会出现在vm上,直接读取使用即可
 
- 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   | <div id="root">         姓: <input type="text" v-model="firstName"><br>         名: <input type="text" v-model="lastName"><br>         全名: <span>{{fullName}}</span>     </div>
      <script>         const vm = new Vue({             el: '#root',             data: {                 firstName: '张',                 lastName: '三',             },             computed: {                 fullName: {                                                                                    get() {                                                  return this.firstName + '-' + this.lastName                     },                                          set(value) {                         console.log('set '+value)                         const arr = value.split('-')                         this.firstName = arr[0]                         this.lastName = arr[1]                     }                 }             }         })     </script>
   | 
 
计算属性的简写
确定只读不改(只有get没有set)才能用简写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | <script>     const vm = new Vue({         el: '#root',         data: {             firstName: '张',             lastName: '三',         },         computed: {             fullName() {                 console.log('get被调用了')                 return this.firstName + '-' + this.lastName             }         }     }) </script>
   | 
 
监视属性
天气案例
实现点击按钮切换内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | <div id="root">     <h2>今天天气很{{info}}</h2>          <button @click="changeWeather">切换天气</button> </div>
  <script>     const vm = new Vue({         el: '#root',         data: {            isHot:true         },         computed: {             info() {                 return this.isHot ? '炎热' : '凉爽'             }         },         methods: {             changeWeather(){                 this.isHot = !this.isHot             }         },     }) </script>
   | 
 
监视属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
   | <div id="root">         <h2>今天天气很{{info}}</h2>                  <button @click="changeWeather">切换天气</button>     </div>
      <script>         const vm = new Vue({             el: '#root',             data: {                 isHot: true             },             computed: {                 info() {                     return this.isHot ? '炎热' : '凉爽'                 }             },             methods: {                 changeWeather() {                     this.isHot = !this.isHot                 }             },                          
 
 
 
 
 
              
 
 
 
 
 
                       })
          vm.$watch('isHot', {             immediate: true,                          handler(newValue, oldValue) {                 console.log('isHot被修改了' + newValue + '@' + oldValue)             }         })     </script>
   | 
 
监视有两种方法,第一种直接在watch里面写,一般是创建vue对象时就明确了要监视哪个属性,第二种是用vm.$watch()的形式,第一个参数是属性名(要用单引号),第二个参数就是配置项
- 当被监视的属性变化时,回调函数自动调用,进行相关操作
 
- 监视的属性必须存在,才能进行监视
 
深度监视
深度监视
- Vue中的watch默认不检测对象内部值的改变(一层)
 
- 配置deep:true可以监测对象内部值改变(多层)
 
- Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以
 
- 使用watch时根据数据的具体结构,决定是否采用深度监视
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
   | <div id="root">         <h2>今天天气很{{info}}</h2>                  <button @click="changeWeather">切换天气</button>         <hr>         <h3>a的值是{{numbers.a}}</h3>         <button @click="numbers.a++">点我让a+1</button><br>         <h3>b的值是{{numbers.b}}</h3>         <button @click="numbers.b++">点我让b+1</button>         <button @click="numbers = {a:666,b:888}">点我彻底替换掉numbers</button>     </div>
      <script>         const vm = new Vue({             el: '#root',             data: {                 isHot: true,                 numbers: {                     a: 1,                     b: 1                 }             },             computed: {                 info() {                     return this.isHot ? '炎热' : '凉爽'                 }             },             methods: {                 changeWeather() {                     this.isHot = !this.isHot                 }             },             watch: {                 isHot: {                                                               handler(newValue, oldValue) {                         console.log('isHot被修改了' + newValue + '@' + oldValue)                     }                 },                                  'numbers.a': {                     handler() {                         console.log('a改变了')                     }                 },                                  numbers:{                     deep:true,                       handler(){                         console.log('某个属性变化了')                     }                 }
              }         })
      </script>
   | 
 
深度监视的简写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
   | <div id="root">     <h2>今天天气很{{info}}</h2>          <button @click="changeWeather">切换天气</button>
  </div>
  <script>     const vm = new Vue({         el: '#root',         data: {             isHot: true,         },         computed: {             info() {                 return this.isHot ? '炎热' : '凉爽'             }         },         methods: {             changeWeather() {                 this.isHot = !this.isHot             }         },                           
 
 
 
 
 
          
 
               })
           
 
 
 
 
 
 
           vm.$watch('isHot', function (newValue, oldValue) {         console.log('isHot被修改了' + newValue + '@' + oldValue)     })
  </script>
   | 
 
计算属性和监视属性
拿监视属性再写一遍姓名案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | <div id="root">         姓: <input type="text" v-model="firstName"><br>         名: <input type="text" v-model="lastName"><br>         全名: <span>{{fullName}}</span>     </div>
      <script>         const vm = new Vue({             el: '#root',             data: {                 firstName: '张',                 lastName: '三',                 fullName: '张-三'             },             watch: {                 firstName(newValue) {                     setTimeout(() => {                         this.fullName = newValue + '-' + this.lastName                     }, 1000)                 },                 lastName(newValue) {                     this.fullName = this.firstName + '-' + newValue                 }             }         })     </script>
   | 
 
没什么要说的,主要就是:计算属性不适合写异步任务,因为计算属性靠的是返回值,而这个返回值并不能延迟返回
但是这里的setTimeout为什么可以使用箭头函数?
setTimeout函数的确是在vm中,但是定时器的回调函数并不是vm管理的,而是浏览器管理的
箭头函数没有自己的this,所以就会往外找,就找到了vm
总结:
computed和watch之间的区别
- computed能完成的功能,watch都可以完成
 
- watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
 
两个重要的小原则
- 所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或实例对象
 
- 所有不被Vue管理的函数(定时器的回调函数,ajax的回调函数,promise的回调函数等),最好写成箭头函数,这样this的指向才是vm或组件实例对象
 
绑定样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
   | <div id="root">                  <div class="basic" :class="mood" @click="changeMood">{{name}}</div>         <hr>
                   <div class="basic" :class="classArr">{{name}}</div>         <hr>
                   <div class="basic" :class="classObj">{{name}}</div>
                      <div class="basic" :style="[styleObj,styleObj2]">{{name}}</div>     </div>
      <script>         const vm = new Vue({             el: '#root',             data: {                 name: 'ZZMR',                 mood: 'normal',                 arr: ['atguigu1', 'atguigu2', 'atguigu3'],                 classObj: {                     atguigu1: false,                     atguigu2: false,                     atguigu3: false                 },                 styleObj: {                     fontSize: '40px'                 },                 styleObj2: {                     color: 'red'                 }             },             methods: {                 changeMood() {                     const arr = ['happy', 'sad', 'normal']                     this.mood = arr[Math.floor(Math.random() * 3)]                 }             }         })     </script>
   | 
 
- class样式
- 写法:class=”xxx”, xxx可以是字符串,对象,数组
 
- 字符串写法适用于:类名不确定,要动态获取
 
- 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定
 
- 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但是不确定用不用
 
 
- style样式
- :style=”{fontSize:xxx}” 其中xxx是动态的
 
- :style=”[a,b]”,其中a,b是样式对象
 
 
条件渲染
渲染语法
- v-if
- 写法:
- v-if=”表达式”
 
- v-else-if=”表达式”
 
- v-else=”表达式”
 
 
- 适用于:切换频率较低的场景
 
- 特点:不展示的DOM元素直接被移除
 
- 注意: v-if可以和v-else-if,v-if一起使用,但是结构不能被打断
 
 
- v-show
- 写法: v-show表达式
 
- 适用于:切换频率较高的场景
 
- 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
 
 
- 备注: 使用v-if时,元素可能无法获取到,而使用v-show一定可以获取到
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
   | <div id="root">               
                
      <h2>当前的n值是{{n}}</h2>     <button @click="n++">点我n+1</button>
      
 
 
           <template v-if="n === 1">         <h2>你好</h2>         <h2>ZZMR</h2>         <h2>China</h2>     </template>
 
  </div>
  <script>     const vm = new Vue({         el: '#root',         data: {             name: 'ZZMR',             n: 0         }     })
  </script>
   | 
 
列表渲染
基本列表
v-for指令
- 用于展示列表数据
 
- 语法:v-for=”(item, index) in xxx :key=”yyy”
 
- 可遍历: 数组,对象,字符串(用的很少),指定次数(用的很少)
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
   | <div id="root">                  <h2>人员列表</h2>         <ul>                          <li v-for="(p,index) in persons" :key="index">                 {{p.name}}--{{p.age}}             </li>         </ul>
                   <h2>汽车信息</h2>         <ul>             <li v-for="(value,k) in car" :key="k">                 {{k}}:{{value}}             </li>         </ul>
                   <h2>遍历字符串</h2>         <ul>             <li v-for="(char,index) in str" :key="index">                 {{char}}:{{index}}             </li>         </ul>
                   <h2>遍历指定次数</h2>         <ul>             <li v-for="(number,index) of 5" :key="index">                 {{number}}:{{index}}             </li>         </ul>     </div>
      <script>         const vm = new Vue({             el: '#root',             data: {                 persons: [                     { id: '001', name: '张三', age: 18 },                     { id: '002', name: '李四', age: 19 },                     { id: '003', name: '王五', age: 20 }                 ],                 car: {                     name: "DMC",                     price: "100万",                     color: "white"                 },                 str: "hello"             }         })     </script>
   | 
 
key的作用与原理
用index和用id(对象的唯一标识)的区别


但是如果不写,key就会默认加一个index,所以不写就和写了index一样出问题
面试题: react,vue中的key有什么作用(key的内部原理)
- 虚拟DOM中key的作用
- key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue进行新虚拟DOM与旧虚拟DOM的差异比较,比较规则如下
 
 
- 对比规则
- 旧虚拟DOM中找到了与新虚拟DOM相同的key
- 若虚拟DOM中内容没变,直接使用之前的真实DOM
 
- 若虚拟DOM中的内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
 
 
- 旧虚拟DOM中未找到与新虚拟DOM相同的key
 
 
- 用index作为key可能会引发的问题
- 若对数据进行:逆序添加,逆序删除等破坏顺序操作,就会产生没有必要的真实DOM更新=>界面效果没问题,但效率低
 
- 如果结构中还包含输入类DOM=>会产生错误的DOM更新,界面有问题
 
 
- 开发中如何选择key?
- 最好使用每条数据的唯一标识作为key,比如id,手机号,身份证号,学号等唯一值
 
- 如果不存在对数据的逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
 
 
列表过滤
此时就会发现,如果watch和computed都能实现时,要用computed会好一点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
   | <div id="root">                  <h2>人员列表</h2>         <input type="text" placeholder="请输入名字" v-model="keyWord">         <ul>             <li v-for="p in filPersons" :key="p.id">                 {{p.name}}-{{p.age}}-{{p.sex}}             </li>         </ul>     </div>
      <script>
                   
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
                   const 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: 21, sex: '男' }                 ]             },             computed: {                 filPersons() {                     return this.persons.filter((p) => {                         return p.name.indexOf(this.keyWord) !== -1                     })                 }             }         })     </script>
   | 
 
列表排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
   | <div id="root">                  <h2>人员列表</h2>         <input type="text" placeholder="请输入名字" v-model="keyWord">         <button @click="sortType = 2">年龄升序</button>         <button @click="sortType = 1">年龄降序</button>         <button @click="sortType = 0">原顺序</button>         <ul>             <li v-for="p in filPersons" :key="p.id">                 {{p.name}}-{{p.age}}-{{p.sex}}             </li>         </ul>     </div>
      <script>
 
 
                   const vm = new Vue({             el: '#root',             data: {                 keyWord: '',                 persons: [                     { id: '001', name: '马冬梅', age: 28, sex: '女' },                     { id: '002', name: '周冬雨', age: 19, sex: '女' },                     { id: '003', name: '周杰伦', age: 31, sex: '男' },                     { id: '004', name: '温兆伦', age: 18, sex: '男' }                 ],                 sortType: 0,             },             computed: {                 filPersons() {                     const arr = this.persons.filter((p) => {                         return p.name.indexOf(this.keyWord) !== -1                     })                                          if (this.sortType) {                         arr.sort((p1, p2) => {                             return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age                         })                     }                     return arr                 }             }         })     </script>
   | 
 
主要是看一下排序的地方吧,js的语法,其实和java的比较器挺像的
更新时问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   | <div id="root">                  <button @click="updateMei">更新马冬梅信息</button>         <ul>             <li v-for="p in persons" :key="p.id">                 {{p.name}}-{{p.age}}-{{p.sex}}             </li>         </ul>     </div>
      <script>
 
 
                   const vm = new Vue({             el: '#root',             data: {                 persons: [                     { id: '001', name: '马冬梅', age: 28, sex: '女' },                     { id: '002', name: '周冬雨', age: 19, sex: '女' },                     { id: '003', name: '周杰伦', age: 31, sex: '男' },                     { id: '004', name: '温兆伦', age: 18, sex: '男' }                 ],             },             methods: {                 updateMei(){                                          this.persons[0] = { id: '001', name: '马冬梅', age: 288, sex: '男' }                 }             },         })     </script>
   | 
 
这时,直接更改某一项是生效的,但是直接更改整个对象时,是不生效的?Vue没有检测到数据改变了,但是数据的的确确变了
Vue.set()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
   | <div id="root">         <h1>学校信息</h1>         <h2>学校名称 {{name}}</h2>         <h2>学校地址 {{address}}</h2>         <h2>校长是:{{leader}}</h2>         <hr>         <h1>学生信息</h1>         <button @click="addSex">添加一个性别属性,默认值是男</button>         <h2>姓名: {{student.name}}</h2>         <h2>姓名: 真实{{student.age.rAge}},对外:{{student.age.sAge}}</h2>         <h2 v-if="student.sex">性别: {{student.sex}}</h2>         <h2>朋友们</h2>         <ul>             <li v-for="(f,index) of student.friends" :key="index">                 {{f.name}}--{{f.age}}             </li>         </ul>     </div>
      <script>         const vm = new Vue({             el: '#root',             data: {                 name: 'ZZMR',                 address: 'China',                 student: {                     name: 'Tom',                     age: {                         rAge: 18,                         sAge: 20                     },                                          friends: [                         { name: 'Jerry', age: 35 },                         { name: 'Pyt', age: 15 }                     ]                 }             },             methods: {                 addSex() {                                          this.$set(this.student, 'sex', '女')                 }             },         })     </script>
   | 
 
总结在后面
Vue监视数据-总结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
   | <div id="root">         <h1>学生信息</h1>
          <button @click="student.age++">年龄+1岁</button>         <button @click="addSex">添加性别属性,默认值:男</button>         <button @click="student.sex = '未知'">修改性别为未知</button>         <button @click="addFriend">在列表首位添加一个朋友</button>         <button @click="updateNameFirst">修改第一个朋友的名字为: 张三</button>         <button @click="addHobby">添加一个爱好</button>         <button @click="updateToCar">修改第一个爱好为: 开车</button>         <button @click="removeSmoke">移除爱好中的抽烟</button>
          <h2>姓名: {{student.name}}</h2>         <h2>年龄: {{student.age}}</h2>         <h2 v-if="student.sex">性别: {{student.sex}}</h2>         <h2>朋友们</h2>         <ul>             <li v-for="(f,index) of student.friends" :key="index">                 {{f.name}}--{{f.age}}             </li>         </ul>         <h2>爱好</h2>         <ul>             <li v-for="(h,index) of student.hobby" :key="index">                 {{h}}             </li>         </ul>     </div>
      <script>         const vm = new Vue({             el: '#root',             data: {                 student: {                     name: 'Tom',                     age: 18,                     hobby: ['抽烟', '喝酒', '烫头'],                     friends: [                         { name: 'Jerry', age: 35 },                         { name: 'Pyt', age: 15 }                     ]                 }             },             methods: {                 addSex() {                     Vue.set(this.student, 'sex', '男')                 },                 addFriend() {                     
 
                      this.student.friends.unshift({ name: 'Potty', age: 18 })                 },                 updateNameFirst() {                     this.student.friends[0].name = '张三'                 },                 addHobby() {                     this.student.hobby.push('打篮球')                 },                 updateToCar() {                                          Vue.set(this.student.hobby, 0, '开车')                 },                 removeSmoke() {                     this.student.hobby = this.student.hobby.filter((h) => {                         return h !== '抽烟'                     })                 }             },         })     </script>
   | 
 
Vue监视数据的原理:
vue会监视data中所有层次的数据。
 
如何监测对象中的数据-通过setter实现监视,且要在new Vue时就传入要监测的数据。
- 对象中后追加的属性,Vue默认不做响应式处理
 
- 如需给后添加的属性做响应式,请使用如下API:
 
- Vue.set(target,propertyName/index,value) 或
 
- vm.$set(target,propertyName/index,value)
 
 
如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
- 调用原生对应的方法对数组进行更新。
 
- 重新解析模板,进而更新页面。
 
 
在Vue修改数组中的某个元素一定要用如下方法:
- 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
 
- Vue.set() 或 vm.$set()
 
 
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
数据劫持:将data加工成_data
收集表单数据
分几种情况
- 若 
<input type="text"/> 则v-model收集的是value值,用户输入的就是value值 
- 若 
<input type="radio"/> 则v-model收集的是value值,且要给标签配置value值 
- 若 
<input type="checkbox">
- 没有配置input的value属性,那么收集的就是checked(勾选or未勾选,是布尔值)
 
- 配置了input的value属性
- v-model的初始值是非数组,那么收集的就是checked(勾选or未勾选,是布尔值)
 
- v-model的初始值是数组,那么收集的就是value组成的数组
 
 
 
- v-model的三个修饰符
- lazy: 失去焦点再收集数据
 
- number: 输入字符串转为有效的数字
 
- trim: 输入首位空格过滤
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
   | <div id="root">         <form @submit.prevent="demo">             账号: <input type="text" v-model.trim="userinfo.account"><br><br>             密码: <input type="password" v-model="userinfo.password"><br><br>             年龄: <input type="number" v-model.number="userinfo.age"><br><br>             性别:             男<input type="radio" name="sex" v-model="userinfo.sex" value="male">             女<input type="radio" name="sex" v-model="userinfo.sex" value="female"><br><br>             爱好:             学习<input type="checkbox" v-model="userinfo.hobby" value="study">             打游戏<input type="checkbox" v-model="userinfo.hobby" value="game">             吃饭<input type="checkbox" v-model="userinfo.hobby" value="eat"><br><br>             所属校区             <select v-model="userinfo.city">                 <option value="">请选择校区</option>                 <option value="beijing">北京</option>                 <option value="shanghai">上海</option>                 <option value="shenzhen">深圳</option>                 <option value="wuhan">武汉</option>             </select>             <br><br>             其他信息             <textarea v-model.lazy="userinfo.other"></textarea><br><br>             <input type="checkbox" v-model="userinfo.agree"> 阅读并接受<a href="https://jimmy66886.github.io/">《用户协议》</a>             <button type="submit">提交</button>         </form>     </div>
      <script>         const vm = new Vue({             el: '#root',             data: {                 userinfo: {                     account: '',                     password: '',                     age: '',                     sex: '',                     hobby: [],                     city: '',                     other: '',                     agree: '',                 }             },             methods: {                 demo() {                     console.log(JSON.stringify(this.userinfo))                 }             },         })     </script>
   | 
 
过滤器
- 过滤器定义:对要显示的数据进行特定格式化后再显示(适用于一些简单的逻辑的处理)
 
- 语法:
- 注册过滤器: Vue.filter(name,callback) 或 new Vue(filters:{})
 
- 使用过滤器
{{ xxx | 过滤器名}}  或 v-bind:属性 = “xxx | 过滤器名” 
 
- 备注:
- 过滤器也可以接收额外的参数,多个过滤器也可以串联
 
- 并没有改变原的数据,是产生新的对应的数据
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
   | <div id="root">         <h2>显示格式化后的时间</h2>                  <h3>现在是{{fmtTime}}</h3>                  <h3>现在是{{getFmtTime()}}</h3>                  <h3>现在是{{time | timeFormater}}</h3>                  <h3>现在是{{time | timeFormater('YYYY-MM-DD') | mySlice}}</h3>
          <h3 :x="msg | mySlice">ZZMR</h3>     </div>
      <div id="root2">         <h2>{{msg | mySlice}}</h2>     </div>
      <script>
                   Vue.filter('mySlice', function (value) {             return value.slice(0.4)         })
          const vm = new Vue({             el: '#root',             data: {                 time: 1673148512081,                 msg: 'ZZMR123'             },             computed: {                 fmtTime() {                     return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')                 }             },             methods: {                 getFmtTime() {                     return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')                 }             },                          filters: {                 timeFormater(value, str = 'YYYY-MM-DD HH:mm:ss') {                                          return dayjs(value).format(str)                 }             }         })
 
          const vm2 = new Vue({             el: '#root2',             data: {                 msg: 'ZZMR'             }         })
      </script>
   | 
 
内置指令
v-text
- 作用: 向其所在的节点中渲染文本内容
 
- 与插值语法的区别,v-text会替换掉节点中的内容,则不会(也就是说插值语法可以进行拼接)
 
1 2 3 4 5 6 7 8 9 10 11 12 13
   | <div id="root">        <div>{{name}}</div>        <div v-text="name"></div>    </div>
     <script>        const vm = new Vue({            el: '#root',            data: {                name: 'ZZMR',            }        })    </script>
   | 
 
v-html
这里要说一下cookie

这个说实话已经在springboot里体验过了,当时就是拿了cookie,然后用postman发送请求
永远不要相信用户的输入
所以v-html呢
- 作用: 向指定节点中渲染包含html结构的内容
 
- 与插值语法的区别
- v-html会替换掉节点中的所有内容,则不会
 
- v-html可以识别html结构
 
 
- 严重注意:
- 在网站上动态渲染任意HTML是非常危险的,容易导致XSS工具
 
- 一定要在可信的内容上使用v-html,永不要用在用户提交的内容上
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | <div id="root">         <div v-html="str"></div>         <div v-html="str2"></div>     </div>
      <script>         const vm = new Vue({             el: '#root',             data: {                 name: 'ZZMR',                 str: '<h3>hello</h3>',                 str2: '<a href=javascript:location.href="https://jimmy66886.github.io?"+document.cookie>点一下</a>'             }         })     </script>
   | 
 
v-cloak
写了一个控制器方法,用于返回一个Vue.js
但是我发现读取和写入有点问题,用Vue.min.js就没问题了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | 
 
 
 
 
 
 
      @GetMapping("/resource/{s}/vue.js")     public String getVue(@PathVariable("s") String s) throws InterruptedException, IOException {                  int ms = Integer.parseInt(s.split("s")[0]) * 1000;         Thread.sleep(ms);                  BufferedReader bufferedReader = new BufferedReader(new FileReader("D:\\Codefield\\Vue\\Vue基础\\js\\vue.min.js"));
          String line;         StringBuffer sb = new StringBuffer();         while ((line = bufferedReader.readLine())!= null){             sb.append(line);         }
          bufferedReader.close();         return sb.toString();     }
 
  | 
 
?我到现在才发现,之前一直把script标签写在了body里面,一般都是写body外面的…
情景:
将引入Vue的script标签放到body结尾处
这时,页面由于没有Vue的引入,会显示出原来的内容,然后等待Vue引入后,再被Vue渲染页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
   | <!DOCTYPE html> <html lang="en">
  <head>     <meta charset="UTF-8">     <meta http-equiv="X-UA-Compatible" content="IE=edge">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <title>v-text</title>          <style>         [v-cloak] {             display: none;         }     </style> </head>
  <body>
      <div id="root">         <h2 v-cloak>{{name}}</h2>     </div>
 
      <script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
  </body>
  <script>     console.log(1)     const vm = new Vue({         el: '#root',         data: {             name: 'ZZMR',         }     }) </script>
  </html>
   | 
 
原理:v-cloak会在Vue开始渲染页面的时候消失
所以在Vue因网络原因还未引入,此时v-cloak生效,而v-cloak被style中的选择器选中,经过display: none处理,所以就不显示该标签,等到Vue引入,这个v-cloak就消失,标签正常展示
v-once指令
v-once指令:
- v-once所在节点在初次动态渲染后,就视为静态内容了
 
- 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | <div id="root">         <h2 v-once>初始化的的n值是{{n}}</h2>         <h2>当前的n值是{{n}}</h2>         <button @click="n++">点我n+1</button>     </div>
 
 
  </body>
  <script>     console.log(1)     const vm = new Vue({         el: '#root',         data: {             n: 1,         }     }) </script>
   | 
 
v-pre
v-pre指令
- 跳过其所在节点的编译过程
 
- 可利用它跳过:没有使用指令语法,没有使用插值语法的节点,会加快编译
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | <div id="root">         <h2 v-pre>Vue其实很简单</h2>         <h2>n的值为{{n}}</h2>         <button @click="n++">点我n+1</button>     </div>
 
 
  </body>
  <script>     const vm = new Vue({         el: '#root',         data: {             n: 1,         }     }) </script>
   | 
 
自定义指令
定义一个v-big指令 和v-text功能类似,但会把绑定的数值放大10倍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | <div id="root">         <h2>当前的n值是 <span v-text="n"></span></h2>         <h2>放大后的n值是 <span v-big="n"></span></h2>         <button @click="n++">点我n+1</button>     </div>
  </body>
  <script>     const vm = new Vue({         el: '#root',         data: {             n: 1         },         directives: {                          big(element, binding) {                 element.innerText = binding.value * 10              }         }     }) </script>
   | 
 
定义一个v-fbind指令 和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
   | <div id="root">         <h2>当前的n值是 <span v-text="n"></span></h2>         <h2>放大后的n值是 <span v-big="n"></span></h2>         <button @click="n++">点我n+1</button>         <hr>         <input type="text" v-fbind:value="n">     </div>
  </body>
  <script>     const vm = new Vue({         el: '#root',         data: {             n: 1         },         directives: {                          big(element, binding) {                 element.innerText = binding.value * 10             },             fbind: {                                  bind(element, binding) {                     element.value = binding.value                 },                                  inserted(element, binding) {                     element.focus()                 },                                  update(element, binding) {                     element.value = binding.value                     element.focus()                 }             }         }     }) </script>
   | 
 
- 定义语法:
- 局部指令
- new Vue({
directives:{指令名:配置对象}
}) 
- new Vue({
directives{指令名:回调函数}
}) 
 
- 全局指令
-Vue.directive(指令名,配置对象/回调函数) 
 
- 配置对象中常用的三个回调
- bind: 指令与元素被插入页面时调用
 
- inserted: 指令所在元素被插入页面时调用
 
- update: 指令所在模板结构被重新解析时调用
 
 
- 备注:
- 指令定义时不加v-,但使用时要加v-
 
- 指令名如果是多个单词,不能使用小驼峰的形式,要使用kebab-case也就是短横线分隔形式,然后定义时也要加上单引号,不然报错
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
   | <div id="root">         <h2>当前的n值是 <span v-text="n"></span></h2>         <h2>放大后的n值是 <span v-big="n"></span></h2>         <button @click="n++">点我n+1</button>         <hr>         <input type="text" v-fbind:value="n">     </div>
  </body>
  <script>
           Vue.directive('fbind', {                  bind(element, binding) {             element.value = binding.value         },                  inserted(element, binding) {             element.focus()         },                  update(element, binding) {             element.value = binding.value             element.focus()         }     })
      const vm = new Vue({         el: '#root',         data: {             n: 1         },         directives: {                          big(element, binding) {                 element.innerText = binding.value * 10             },             
 
 
 
 
 
 
 
 
 
 
 
 
 
          }     }) </script>
   | 
 
生命周期
引出生命周期
生命周期
- 又名:生命周期回调函数,生命周期函数,生命周期钩子
 
- 是什么: Vue在关键时刻帮我们调用的一些特殊名称的函数
 
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
 
- 生命周期函数中的this指向是vm或组件实例对象
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
   | <div id="root">         <h2 v-if="a">hello</h2>         <h2 :style="{opacity}">欢迎学习Vue</h2>     </div> </body>
  <script>     const vm = new Vue({         el: '#root',         data: {             opacity: 1,             a: false,         },         methods: {
          },                  mounted() {             console.log('mounted')             setInterval(() => {                 this.opacity -= 0.01                 if (this.opacity <= 0) {                     this.opacity = 1                 }             }, 16)         },     })
 
           
 
 
 
 
  </script>
   | 
 
分析生命周期

老师画的:

内容挺多的哎,但是没啥事,后面有总结,会说哪几个最常用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
   | <div id="root">         <h2 v-text="n"></h2>         <h2>当前的n值是{{n}}</h2>         <button @click="add">点我n+1</button>         <button @click="bye">点我销毁vm</button>     </div>
 
  </body>
  <script>     const vm = new Vue({         el: '#root',         data: {             n: 1,         },         methods: {             add() {                 console.log('add')                 this.n++             },             bye() {                 console.log('bye')                 this.$destroy()             }         },         beforeCreate() {             console.log('beforeCreate')         },         created() {             console.log('created')         },
          beforeMount() {             console.log('beforeMount')         },         mounted() {             console.log('mounted')         },         beforeUpdate() {             console.log('beforeUpdate')             console.log(this.n)         },         updated() {             console.log('updated')         },         beforeDestroy() {             console.log('beforeDestroy')             console.log('销毁之前'+this.n)         },         destroyed() {             console.log('destroyed')         },         watch: {             n() {                 console.log('n变了')             }         }     }) </script>
   | 
 
生命周期总结
- 常用的生命周期钩子
- mounted: 发送Ajax请求,启动定时器,绑定自定义事件,订阅消息等(初始化操作)
 
- beforeDestroy: 清除定时器,解绑自定义事件,取消订阅消息等
 
 
- 关于销毁Vue实例
- 销毁后借助Vue开发者工具看不到任何信息
 
- 销毁后自定义事件会失效,但是原生DOM事件依然有效
 
- 一般不会再beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
   | <div id="root">         <h2 :style="{opacity}">欢迎学习Vue</h2>         <button @click="opacity=1">透明度设置为1</button>         <button @click="stop">停止</button>     </div> </body>
  <script>     const vm = new Vue({         el: '#root',         data: {             opacity: 1,         },         methods: {             stop() {                                  this.$destroy()             }
          },                  mounted() {             console.log('mounted')             this.timer = setInterval(() => {                 this.opacity -= 0.01                 if (this.opacity <= 0) {                     this.opacity = 1                 }             }, 16)         },         beforeDestroy() {             clearInterval(this.timer)         },     }) </script>
   | 
 
组件

组件方式编写应用

组件的定义:实现应用中局部动能代码和资源的集合
非单文件组件
一个文件中包含有n个组件
Vue中使用组件的三大步骤
- 定义组件(创建组件)
 
- 注册组件
 
- 使用组件(写组件标签)
 
如何定义一个组件?
使用Vue.extend(options)创建,其中options和Vue(options)时传入的那个options几乎一样,但区别如下
- el不能写,为什么? 因为最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
 
- data必须写成函数,为什么? 避免组件被复用时,数据存在引用关系
 
- 备注: 使用template可以配置组件结构
 
如何注册组件
- 局部注册: 靠new Vue的时候传入components选项
 
- 全局注册: 靠Vue.component(‘组件名’,组件)
 
使用组件
编写组件标签 <student></student>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
   | <div id="root">                  <school></school>         <hr>         <student></student>
          <hello></hello>     </div>
      <hr>
      <div id="root2">                  <hello></hello>     </div> </body>
  <script>
           const school = Vue.extend({                  template: `         <div>             <h2>学校名称: {{schoolName}}</h2>             <h2>学校地址: {{address}}</h2>             <button @click="showName">点我提示学校名</button>         </div>         `,         data() {             return {                 schoolName: 'ZZMR',                 address: '汉中',             }         },         methods: {             showName() {                 alert(this.schoolName)             }         },     })
           const student = Vue.extend({         template: `             <div>                 <h2>学生姓名: {{studentName}}</h2>                 <h2>学生年龄: {{age}}</h2>               </div>         `,         data() {             return {                 studentName: '张三',                 age: 28             }         }     })
 
           const hello = Vue.extend({         template: `             <div>                 <h2>你好呀{{name}}</h2>             </div>         `,         data() {             return {                 name: 'Tom'             }         }     })
           Vue.component('hello',hello)
           const vm = new Vue({         el: '#root',                  components: {             
              school,             student,         }     })
      const vm2 = new Vue({         el: '#root2',     }) </script>
   | 
 
组件注意事项
- 几个注意点:
关于组件名:
- 一个单词组成:
- 第一种写法(首字母小写):school
 
- 第二种写法(首字母大写):School
 
 
- 多个单词组成:
- 第一种写法(kebab-case命名):my-school
 
- 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
 
 
- 备注:
- 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
 
- 可以使用name配置项指定组件在开发者工具中呈现的名字。
 
 
 
关于组件标签:
- 第一种写法:
<school></school> 
- 第二种写法:
<school/> 
- 备注:不用使用脚手架时,
<school/>会导致后续组件不能渲染。 
 
一个简写方式:
- const school = Vue.extend(options) 可简写为:const school = options
 
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
   | <div id="root">         <h1>{{msg}}</h1>                  <my-school></my-school>         <my-school />     </div> </body>
  <script>     /* const school = Vue.extend({         // name: 'ZZMR',         template: `             <div>                 <h2>学校名称 {{name}}</h2>                 <h2>学校地址 {{address}}</h2>             </div>         `,         data() {             return {                 name: 'ZZMR',                 address: 'P'             }         }     }) */
      // 简写     const school = {         // name: 'ZZMR',         template: `             <div>                 <h2>学校名称 {{name}}</h2>                 <h2>学校地址 {{address}}</h2>             </div>         `,         data() {             return {                 name: 'ZZMR',                 address: 'P'             }         }     }
      const vm = new Vue({         el: '#root',         components: {             'my-school': school             // MySchool: school         },         data: {             msg: '欢迎'         }     }) </script>
   | 
 
组件的嵌套
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
   | 
 
 
      <div id="root">              </div> </body>
  <script>
      // 创建student组件     const student = {         // name: 'ZZMR',         template: `             <div>                 <h2>学校姓名 {{name}}</h2>                 <h2>学生年龄 {{age}}</h2>             </div>         `,         data() {             return {                 name: '张三',                 age: 18             }         }     }
 
      // 创建school组件     const school = {         // name: 'ZZMR',         template: `             <div>                 <h2>学校名称 {{name}}</h2>                 <h2>学校地址 {{address}}</h2>                 <student></student>             </div>         `,         data() {             return {                 name: 'ZZMR',                 address: 'P'             }         },         // 注册组件:(局部)         components: {             student         }     }
      // 定义一个hello组件     const hello = {         template: `             <h1>{{msg}}</h1>         `,         data() {             return {                 msg: 'hello欢迎'             }         }     }
      // 定义一个app组件     const app = {         template: `             <div>                 <school></school>                   <hello></hello>               </div>         `,         components: {             school,             hello,         }     }
 
      // 创建vm     const vm = new Vue({         template: '<app></app>',         el: '#root',         // 注册组件(局部)         components: {             /* 'my-school': school,             // MySchool: school             hello, */             app         },     }) </script>
 
  | 
 
就是嵌套啊,注意子组件要在父组件之前定义好,不然会报错
VueComponent构造函数
school组件本质是一个名为VueComponent的构造函数,且不是程序定义的,是Vue.extend生成的
 
我们只需要些<school>或<school></school>,Vue解析时会帮我们创建shool组件的实例对象,即Vue帮我们执行的: new VueComponent(options)
 
特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent
 
关于this指向
- 组件配置中,data函数,methods函数,watch中的函数,computed中的函数,它们的this均是VueComponent实例对象
 
- new Vue(options)配置中,data函数,methods中的函数,watch中的函数,computed中的函数,他们的this均是Vue实例对象
 
 
VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
   | <div id="root">         <school></school>         <hello></hello>     </div> </body>
  <script>          const school = Vue.extend({         template: `             <div>                 <h2>学校名称 {{name}}</h2>                 <h2>学校地址 {{address}}</h2>             </div>         `,         data() {             return {                 name: 'ZZMR',                 address: 'P'             }         }     })
           const hello = Vue.extend({         template: `         <h2>{{msg}}</h2>           `,         data() {             return {                 msg: '你好'             }         }
      })
      console.log('@', school)     console.log('#', hello)
      const vm = new Vue({         el: '#root',         components: {             school, hello         }     }) </script>
   | 
 
内置关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
   | 
 
 
 
      <div id="root">         <school></school>     </div> </body>
  <script>          const school = Vue.extend({         template: `             <div>                 <h2>学校名称 {{name}}</h2>                 <h2>学校地址 {{address}}</h2>             </div>         `,         data() {             return {                 name: 'ZZMR',                 address: 'P'             }         }     })
           const vm = new Vue({         el: '#root',         data: {             msg: '你好'         },         components:{             school         }     })
      console.log(school.prototype.__proto__ === Vue.prototype)
           
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  </script>
 
  | 
 
Vue脚手架
安装还挺简单的,主要就是注意要在管理员环境下run
创建一个vue项目:
默认引入的是精简版的Vue
1
   | "module": "dist/vue.runtime.esm.js",
   | 
 
精简版的Vue精简掉了模板解析器,所以在解析模板时要使用render()
1 2 3 4
   | new Vue({   el: '#app',   render: h => h(App), })
  | 
 
使用了简写模式
关于不同版本的Vue
- vue.js与vue.runtime.xxx.js的区别
- vue.js是完整版的Vue,包含:核心功能+模板解析器
 
- vue.runtime.xxx.js是运行版的Vue,只包含:核心功能:没有模板解析器
 
 
- 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到createElement函数去指定具体内容
 
修改默认配置
都是在vue.config.js里面
- 修改入口js(在最大的大括号下)
 
1 2 3 4 5 6
   | pages: {   index: {          entry: 'src/main.js',   } }
  | 
 
- 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
 
- 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
 
脚手架文件结构
├── node_modules
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── HelloWorld.vue
│   │── App.vue: 汇总所有组件
│   │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
ref属性
- 被用来给元素或子组件注册引用信息(id的替代者)
 
- 应用在html标签上的是真实的DOM元素,应用在组件标签上是组件实例对象(vc)
 
- 使用方式:
- <h1 ref=”xxx”></h1> 或<School ref=”xxx”></School>
 
- 获取 this.$refs.xxx
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   | <template>     <div>         <h1 v-text="msg" ref="title"></h1>         <button @click="showDOM">点我输出上方的DOM元素</button>         <School ref="sch" />
      </div> </template>
  <script>
  import School from './components/School'
  export default {     name: 'App',     data() {         return {             msg: '灼灼'         }     },     components: {         School,     },     methods: {         showDOM() {                          console.log(this.$refs.sch)                          console.log(this.$refs.title)         }     } } </script>
   | 
 
props配置
功能:让组件接受外部传过来的数据
- 传递数据 <Demo name=”xxx”/>
 
- 接收数据:
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | 
      props: ['name', 'sex', 'age'],
           props: {         name: String,         age: Number,         sex: String     }
           props: {         name: {             type: String,                required: true,          },         age: {             type: Number,             default: 99,         },         sex: {             type: String,             required: true,         }     }
 
  | 
 
- 备注: props是只读的,Vue底层会监视你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,可以复制一份props的内容到data中,然后去修改data中的数据
Student.vue 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
   | <template>     <div>         <h1>{{ msg }}</h1>         <h2>学生姓名 {{ name }}</h2>         <h2>学校年龄 {{ myAge }}</h2>         <h2>学校性别 {{ sex }}</h2>         <button @click="updateAge">Age++</button>     </div> </template>
  <script> export default {     name: 'Student',     data() {         return {             msg: 'ZZMR里的世界',             myAge: this.age,         }     },     methods: {         updateAge() {             this.myAge++         }     },          props: ['name', 'sex', 'age'],
           
 
 
 
 
           
 
 
 
 
 
 
 
 
 
 
 
 
 
  } </script>
   | 
 
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | <template>     <div>         <Student name="李四" sex="女" :age="20" />     </div> </template>
  <script> import Student from './components/Student'
  export default {     name: 'App',     components: {         Student,     }, } </script>
  <style>
  </style>
   | 
 
mixin
混合/混入
功能:可以把多个组件共用的配置提取成一个混入对象
使用方式:
1 2 3 4 5 6 7 8
   | {     data(){         ...     },     methods:{         ...     } }
  | 
 
- 第二步使用混入
- 全局混入: Vue,mixin(xxx)
 
- 局部混入: mixins: [‘xxx’]
 
 
全局混入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   |  import Vue from "vue"
  import App from './App'
  import { mixin, mixin2 } from "./mixin"
  Vue.mixin(mixin) Vue.mixin(mixin2)
 
  Vue.config.productionTip = false
  const vm = new Vue({     el: '#app',     render: h => h(App) })
 
  | 
 
mixin.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | export const mixin = {     methods: {         showName() {             alert(this.name)         }     },     mounted() {         console.log('Hello')     } }
  export const mixin2 = {     data() {         return {             x: 100,             y: 200,         }     } }
  | 
 
School.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | <template>     <div>         <h2 @click="showName">学校名称 {{ name }}</h2>         <h2>学校地址 {{ address }}</h2>     </div> </template>
  <script>
 
  export default {     name: 'School',     data() {         return {             name: 'ZZMR',             address: 'Peijing',         }     },      } </script>
   | 
 
插件
功能: 用于增强Vue
本质: 包含install方法的一个对象,install的第一个参数是Vue,第二个参数是插件使用者传递的数据
定义插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
   | export default {     install(Vue,x,y,z) {         console.log(x,y,z)                  Vue.filter('mySlice', function (value) {             return value.slice(0,4)         })
                   Vue.directive('fbind', {                          bind(element, binding) {                 element.value = binding.value             },                          inserted(element, binding) {                 element.focus()             },                          update(element, binding) {                 element.value = binding.value                 element.focus()             }         })
                   Vue.mixin({             data() {                 return {                     x: 100,                     y: 200,                 }             }         })
                   Vue.prototype.hello = () => {             alert('Hello H')         }
      } }
  | 
 
使用插件
1 2 3 4 5
   |  import plugins from "./plugins"
 
  Vue.use(plugins)
 
  | 
 
scoped
less样式是什么?没学过啊
不管了,先听着吧
也没啥啊,就是可以嵌套着写样式
1 2 3 4 5 6 7 8 9
   | <style lang="less"> .demo {     background-color: red;
      .zzmr {         font-size: 40px;     } } </style>
   | 
 
Scoped样式的作用:让样式在局部生效,防止冲突
写法: <style scoped>
1 2 3 4 5
   | <style scoped> .demo {     background-color: blue; } </style>
   | 
 
Todo-list案例
写的有些乱了,还是看总结吧
组件化编码流程(通用)
- 实现静态组件:抽取组件,使用组件实现静态页面效果
 
- 展示动态数据:
 
- 交互-从绑定事件监听开始
 
完成静态组件
复制and剪切
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
   | <template>     <div id="root">         <div class="todo-container">             <div class="todo-wrap">                                  <MyHeader></MyHeader>                 <MyList></MyList>                 <MyFooter></MyFooter>             </div>         </div>     </div> </template>
  <script> import MyHeader from './components/MyHeader.vue' import MyList from './components/MyList.vue'
  import MyFooter from './components/MyFooter.vue'
  export default {     name: 'App',     components: {         MyHeader, MyList, MyFooter     }, } </script>
 
  <style>
  body {     background: #fff; }
  .btn {     display: inline-block;     padding: 4px 12px;     margin-bottom: 0;     font-size: 14px;     line-height: 20px;     text-align: center;     vertical-align: middle;     cursor: pointer;     box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);     border-radius: 4px; }
  .btn-danger {     color: #fff;     background-color: #da4f49;     border: 1px solid #bd362f; }
  .btn-danger:hover {     color: #fff;     background-color: #bd362f; }
  .btn:focus {     outline: none; }
  .todo-container {     width: 600px;     margin: 0 auto; }
  .todo-container .todo-wrap {     padding: 10px;     border: 1px solid #ddd;     border-radius: 5px; } </style>
   | 
 
MyHeader.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | <template>     <div class="todo-header">         <input type="text" placeholder="请输入你的任务名称,按回车键确认" />     </div> </template>
  <script> export default {     name: 'MyHeader' } </script>
  <style scoped>
  .todo-header input {     width: 560px;     height: 28px;     font-size: 14px;     border: 1px solid #ccc;     border-radius: 4px;     padding: 4px 7px; }
  .todo-header input:focus {     outline: none;     border-color: rgba(82, 168, 236, 0.8);     box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>
   | 
 
MyList.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
   | <template>     <ul class="todo-main">         <MyItem></MyItem>         <MyItem></MyItem>         <MyItem></MyItem>         <MyItem></MyItem>     </ul> </template>
  <script> import MyItem from './MyItem.vue' export default {     name: 'MyList',     components: {         MyItem     } } </script> <style scoped>
  .todo-main {     margin-left: 0px;     border: 1px solid #ddd;     border-radius: 2px;     padding: 0px; }
  .todo-empty {     height: 40px;     line-height: 40px;     border: 1px solid #ddd;     border-radius: 2px;     padding-left: 5px;     margin-top: 10px; } </style>
   | 
 
MyItem.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
   | <template>     <li>         <label>             <input type="checkbox" />             <span>xxxxx</span>         </label>         <button class="btn btn-danger" style="display:none">删除</button>     </li> </template>
  <script> export default {     name: 'MyItem' } </script>
  <style scoped>
  li {     list-style: none;     height: 36px;     line-height: 36px;     padding: 0 5px;     border-bottom: 1px solid #ddd; }
  li label {     float: left;     cursor: pointer; }
  li label li input {     vertical-align: middle;     margin-right: 6px;     position: relative;     top: -1px; }
  li button {     float: right;     display: none;     margin-top: 3px; }
  li:before {     content: initial; }
  li:last-child {     border-bottom: none; } </style>
   | 
 
MyFooter.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
   | <template>     <div class="todo-footer">         <label>             <input type="checkbox" />         </label>         <span>             <span>已完成0</span> / 全部2         </span>         <button class="btn btn-danger">清除已完成任务</button>     </div> </template>
  <script> export default {     name: 'MyFooter' } </script>
  <style scoped>
  .todo-footer {     height: 40px;     line-height: 40px;     padding-left: 6px;     margin-top: 5px; }
  .todo-footer label {     display: inline-block;     margin-right: 20px;     cursor: pointer; }
  .todo-footer label input {     position: relative;     top: -1px;     vertical-align: middle;     margin-right: 5px; }
  .todo-footer button {     float: right;     margin-top: 5px; } </style>
   | 
 
展示动态数据
这个有一个挺重要的点,子组件给父组件传递数据
要求父组件声明一个函数,传给子组件,然后子组件调用

代码挺多的,到最后再粘贴吧
还有就是注意不要修改props的值
就是Vue没有检测到,也不要修改
总结
写完了,一个小小的案例内容还是挺多的
- 组件化编程流程
- 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突
 
- 实现组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用
- 一个组件在用:放在组件自身即可
 
- 一些组件在用,放在它们共同的父组件上
 
 
- 实现交互: 从绑定事件开始
 
 
- props适用于:
- 父组件 ===> 子组件 通信
 
- 子组件 ===> 父组件 通信(要求父先给子一个函数)
 
 
- 使用v-model时要切记: v-model绑定的值不能是props传过来的值,因为props是不可以修改的
 
- props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但是不推荐这么做
 
浏览器的本地存储
localStorage.html
localStorage的基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
   | <!DOCTYPE html> <html lang="en">
  <head>     <meta charset="UTF-8">     <meta http-equiv="X-UA-Compatible" content="IE=edge">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <title>localStorage</title> </head>
  <body>     <h2>localStorage</h2>     <button onclick="saveData()">点我保存一个数据</button>     <button onclick="readData()">点我读取一个数据</button>     <button onclick="deleteData()">点我删除一个数据</button><br><br>     <button onclick="deleteAllData()">点我清除数据</button>
      <script>
          let p = { name: '张三', age: 18 }
          function saveData() {             localStorage.setItem('msg', 'hello')             localStorage.setItem('person', JSON.stringify(p))         }
          function readData() {             console.log(localStorage.getItem('msg'))             const result = localStorage.getItem('person')             console.log(JSON.parse(result))                      }
          function deleteData() {             localStorage.removeItem('msg')         }
          function deleteAllData() {             localStorage.clear()         }     </script> </body>
  </html>
   | 
 
SessionStorage.html
sessionStorage的基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
   | <!DOCTYPE html> <html lang="en">
  <head>     <meta charset="UTF-8">     <meta http-equiv="X-UA-Compatible" content="IE=edge">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <title>sessionStorage</title> </head>
  <body>     <h2>sessionStorage</h2>     <button onclick="saveData()">点我保存一个数据</button>     <button onclick="readData()">点我读取一个数据</button>     <button onclick="deleteData()">点我删除一个数据</button><br><br>     <button onclick="deleteAllData()">点我清除数据</button>
      <script>
          let p = { name: '张三', age: 18 }
          function saveData() {             sessionStorage.setItem('msg', 'hello')             sessionStorage.setItem('person', JSON.stringify(p))         }
          function readData() {             console.log(sessionStorage.getItem('msg'))             const result = sessionStorage.getItem('person')             console.log(JSON.parse(result))                      }
          function deleteData() {             sessionStorage.removeItem('msg')         }
          function deleteAllData() {             sessionStorage.clear()         }     </script> </body>
  </html>
   | 
 
总结
浏览器本地存储-也叫WebStorage
- 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
 
- 浏览器端通过Window.sessionStorage和Window.localStorage属性来实现本地存储机制
 
- 相关API
- xxxxxStorage.setItem(‘key’,’value’),该方法接收一个键值对作为参数,如果键名存在,则会进行覆盖
 
- xxxxxStorage.getItem(‘key’),根据传入的键,返回键对应的值
 
- xxxxxStorage.removeItem(‘key’),根据key删除元素
 
- xxxxxStorage.clear(),清空存储中的所有数据
 
 
- 备注
- SessionStorage存储的内容会随着浏览器窗口关闭而消失
 
- LocalStorage的内容,需要手动清除才会消失
 
- xxxxxStorage.getItem(‘key’),如果key不存在,那么getItem返回值是null
 
- JSON.parse(null)的结果仍然是null
 
 
组件自定义事件
直接上代码,后面会有总结
组件自定义事件-绑定
Student.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
   | <template>     <div class="student">         <h2>学生姓名 {{ name }}</h2>         <h2>学生性别 {{ sex }}</h2>         <button @click="sendStudentName">把学生名给App</button>     </div> </template>
  <script>
  export default {     name: 'Student',     data() {         return {             name: 'zhangsan',             sex: '男',         }     },     methods: {         sendStudentName() {                          this.$emit('zzmr', this.name)         }     }
  } </script>
  <style scoped> .student {     padding: 5px;     background-color: blue;     margin-top: 30px; } </style>
   | 
 
School.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
   | <template>     <div class="school">         <h2>学校名称 {{ name }}</h2>         <h2>学校地址 {{ address }}</h2>         <button @click="sendSchoolName">把学校名给App</button>     </div> </template>
  <script>
  export default {     name: 'School',     data() {         return {             name: 'ZZMR12345',             address: 'Peijing',         }     },     props: ['getSchoolName'],     methods:{         sendSchoolName(){             this.getSchoolName(this.name)         }     } } </script>
 
 
  <style scoped> .school {     background-color: red;
      
 
  } </style>
   | 
 
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
   | <template>     <div class="app">         <h1>{{ msg }}</h1>                           <Student @zzmr="getStudentName" />
                   
          <hr>                  <School :getSchoolName="getSchoolName" />     </div> </template>
  <script> import Student from './components/Student' import School from './components/School'
  export default {     name: 'App',     data() {         return {             msg: 'Hello'         }     },     components: {         Student,         School     },     methods: {         getSchoolName(name) {             console.log('App收到了学校名:', name)         },         getStudentName(name) {             console.log('App收到了学生名:', name)         }     },     mounted() {                                    
      } } </script>
  <!-- 写了这个属性那app的样式就只能app用了,一般不会在app里加 --> <!-- <style scoped> --> <style> .app {     padding: 10px;     background-color: pink; } </style>
   | 
 
组件自定义事件-解绑
就是使用$off或者直接摧毁vc或者vm
组件的自定义事件-总结
一种组件间的通信方式,适用于: 子组件===>父组件
 
使用场景: A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
 
绑定自定义事件
- 第一种方式,在父组件中 
<Demo @zzmr="test"/>或 <Demo v-on:zzmr="test"/> 
- 第二种方式,在父组件中
 
1 2 3 4 5
   | <Demo ref="demo"/> ... mounted(){     this.$refx.demo.$on('zzmr',this.test) }
   | 
 
- 若想让自定义事件只能触发一次,可以使用once修饰符,或者$once方法
 
 
触发自定义事件:this.$emit('zzmr',数据)
 
解绑自定义事件:```this.$off(‘zzmr’)
 
组件上也可以绑定原生的DOM事件,需要使用native修饰符
 
注意: 通过 this.$refs.demo.$on('zzmr',回调),绑定自定义事件,回调要么配置在methods中,要么使用箭头函数没否则this的指向会出问题
 
TodoList案例-自定义事件
使用组件的自定义事件来修改TodoList案例的子传父数据的情景
基本就是把传递函数的地方改成了@
然后不用props接收了,只需要在原来调用函数的地方改成$emit
1 2 3 4 5 6 7 8 9 10 11 12 13
   | <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodoIfTrue="clearAllTodoIfTrue"> </MyFooter>         ...
  isAll: {     get() {         return this.doneTotal === this.total && this.total > 0     },     set(value) {                  this.$emit('checkAllTodo', value)     } }
   | 
 
全局事件总线
GlobalEventBus
- 一种组件间通信的方式,适用于任意组件间的通信
 
- 安装全局事件总线
 
1 2 3 4 5 6 7 8
   |  const vm = new Vue({     el: '#app',     render: h => h(App),     beforeCreate() {         Vue.prototype.$bus = this      } })
 
  | 
 
使用事件总线
- 接收数据:A组件想要接受数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身
 
1 2 3 4 5 6
   |  mounted() {     this.$bus.$on('hello', (data) => {         console.log('我是School组件,收到了数据', data)     }) },
 
  | 
 
- 提供数据 
this.$bus.$emit('hello', 数据) 
 
最好在beforeDstroy钩子中,用$off去解绑当前组件用到的事件
 
1 2 3
   | beforeDestroy(){     this.$bus.$off('hello') }
  | 
 
消息订阅与发布
要引入第三方库:pubsub-js
直接 npm i pubsub-js 就安装好了
然后在script里面引入(要在发送消息和订阅消息的组件里面都引入)
订阅消息的School
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   |  import pubsub from 'pubsub-js' ...
 
      mounted() {                  this.pubId = pubsub.subscribe('hello', (msgName, data) => {             console.log('有人发布了hello消息,hello消息的回调执行了')             console.log('@', msgName, data)             console.log(this)          })     },     beforeDestroy() {                  pubsub.unsubscribe(this.pubId)     }
 
  | 
 
这就很简洁明了了,直接使用 pubsub.subscribe(消息名,回调)
就完成了订阅消息,写在mounted钩子中,是为了让组件挂在完毕后就一直听是否有消息过来了,最后在组件销毁之前,解除订阅消息 pubsub.unsubscribe(消息id)
发布消息的student
1 2 3 4 5 6 7 8
   | import pubsub from 'pubsub-js' ...
      methods: {         sendStudentName() {             pubsub.publish('hello',this.name)         }     }
   | 
 
就在要发送消息的地方写上 pubsub.publish(消息名,传送的数据)
就可以了
总结
一种组件间通信的方式,适用于任意组件间通信
 
使用步骤
- 安装pubsub: 
npm i pubsub-js 
- 引入: 
import pubsub from 'pubsub-js' 
- 接收数据,A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身(上面的是使用箭头函数,下面的是使用了一个函数来作为回调)
 
1 2 3 4 5 6 7
   | methods(){     demo(data){...} } ... mounted(){     this.pid = pubsub.subscribe('xxx',this.demo) }
  | 
 
- 提供数据: 
pubsub.publish('xxx',数据) 
- 最好在beforeDestroy钩子中,用pubsub.unsubscribe(pid)去取消订阅
 
 
TodoList结合消息订阅与发布
只改了删除一个部分的
show me the code
MyItem.vue
1 2 3 4 5 6 7 8
   |          handleDelete(id) {             if (confirm('确定删除吗')) {                                                   pubsub.publish('deleteTodo',id)             }         }
 
  | 
 
原来是放到事件总线中,现在换成了pubsub,发布消息
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   |     mounted(){         this.$bus.$on('checkTodo',this.checkTodo)                           this.pid = pubsub.subscribe('deleteTodo',this.deleteTodo)     },     beforeDestroy(){         this.$bus.$off('checkTodo')                  pubsub.unsubscribe(pid)     }
  ....          deleteTodo(_,id) {         
 
          this.todos = this.todos.filter(todo => todo.id !== id)     },
  | 
 
就是要注意这个delteTodo的形参了,因为订阅消息,接收到的数据是两个,一个是msgName,一个是数据(在这里面是id),但我们只要id,所以前面就用下划线占位
TodoList添加编辑功能
nextTick
- 语法: 
this.$nextTick(回调函数) 
- 作用: 在下一次DOM更新结束后执行其回调
 
- 什么时候用:当改变数据后,要基于更新后的DOM进行某些操作时,要在nextTick所指定的回调函数中执行
 
编辑功能具体代码
MyItem.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
   | <template>     <li>         <label>                                       <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" />             <span v-show="!todo.isEdit">{{ todo.title }}</span>             <input type="text" v-show="todo.isEdit" :value="todo.title" @blur="handleBlur(todo, $event)"                 ref="inputTitle">         </label>         <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>         <button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit">编辑</button>     </li> </template>
  <script> import pubsub from 'pubsub-js'
  export default {     name: 'MyItem',          props: ['todo'],     methods: {                  handleCheck(id) {                          this.$bus.$emit('checkTodo', id)         },                  handleDelete(id) {             if (confirm('确定删除吗')) {                                                   pubsub.publish('deleteTodo', id)             }         },                  handleEdit(todo) {                          if (todo.hasOwnProperty('isEdit')) {                 todo.isEdit = true             } else {                 this.$set(todo, 'isEdit', true)             }             
 
                           this.$nextTick(function () {                 this.$refs.inputTitle.focus()             })         },                  handleBlur(todo, e) {             todo.isEdit = false             if (e.target.value.trim() === '') return alert('title不能为空')             this.$bus.$emit('updateTodo', todo.id, e.target.value)         }     } } </script>
  <style scoped>
  li {     list-style: none;     height: 36px;     line-height: 36px;     padding: 0 5px;     border-bottom: 1px solid #ddd; }
  li label {     float: left;     cursor: pointer; }
  li label li input {     vertical-align: middle;     margin-right: 6px;     position: relative;     top: -1px; }
  li button {     float: right;     display: none;     margin-top: 3px; }
  li:before {     content: initial; }
  li:last-child {     border-bottom: none; }
  li:hover {     background-color: green; }
  li:hover button {     display: block; } </style>
   | 
 
这里还是使用的事件总线
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13
   |          updateTodo(id,title) {             this.todos.forEach((todo) => {                 if (todo.id === id) {                     todo.title = title                 }             })         },
          ...
                   this.$bus.$on('updateTodo',this.updateTodo)
 
  | 
 
就实现了编辑功能了
动画效果
transition标签
使用```````````````标签包裹住要执行动画的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
   | <template>     <div>         <button @click="isShow = !isShow">显示/隐藏</button>         <transition name="hello" appear>             <h1 v-show="isShow">Hello</h1>         </transition>     </div> </template>
  <script> export default {     name: 'Test',     data() {         return {             isShow: true,         }     },
  } </script>
  <style scoped> h1 {     background-color: pink; }
  .hello-enter-active {     animation: zzmr 1s linear; }
  .hello-leave-active {     animation: zzmr 1s linear reverse; }
  @keyframes zzmr {     from {         transform: translateX(-100%);     }
      to {         transform: translateX(0px);     } } </style>
   | 
 
过度实现
上一个是通过动画实现,这个通过过度实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
   | <template>     <div>         <button @click="isShow = !isShow">显示/隐藏</button>         <transition name="hello" appear>             <h1 v-show="isShow">Hello</h1>         </transition>     </div> </template>
  <script> export default {     name: 'Test',     data() {         return {             isShow: true,         }     },
  } </script>
  <style scoped> h1 {     background-color: pink;      }
 
 
 
 
 
 
 
  .hello-enter, .hello-leave-to {     transform: translateX(-100%) }
 
  .hello-enter-to, .hello-leave {     transform: translateX(0) }
  .hello-enter-active, .hello-leave-active {     transition: 0.5s linear; }
 
 
 
 
 
 
 
 
 
  </style>
   | 
 
多个元素过度
要使用transition-group标签,然后每个child都要有自己的key
1 2 3 4
   | <transition-group name="hello" appear>     <h1 v-show="isShow" key="1">Hello</h1>     <h1 v-show="!isShow" key="2">zzmr</h1> </transition-group>
   | 
 
集成第三方动画
搞了个乌龙,animate.css我装成animate了
animate.css
安装上:npm install animate.css --save
然后就可以引入使用了
1 2 3 4 5 6 7 8 9 10 11 12
   |  import 'animate.css' ...
  <transition-group appear          name="animate__animated animate__bounce"         enter-active-class="animate__swing"         leave-active-class="animate__backOutUp"         >             <h1 v-show="isShow" key="1">Hello</h1>             <h1 v-show="!isShow" key="2">zzmr</h1>         </transition-group>
 
  | 
 
有很多很多的样式,但是由于官网不知道为啥打不开
所以,就用了老师说的这几个
Vue封装的过渡与动画-总结
又到了喜闻乐见的总结环节
- 作用:在插入,更新或移除DOM元素时,在合适的时候给元素添加样式类名
 
- 图示:
 

3. 写法
    1. 准备好样式
        - 元素进入的样式
            1. v-enter: 进入的起点
            2. v-enter-active: 进入的过程中
            3. v-enter-to: 进入的终点
        - 元素离开的样式
            1. v-leave: 离开的起点
            2. v-leave-active: 离开的过程中
            3. v-leave-to: 离开的终点
    2. 使用transition包裹要过度的元素,并配置name属性
    3. 备注: 若有多个元素需要过度,则需要使用transition-group,且每个元素都要指定key值
这里正好把TodoList案例给加一些动画
效果还可以
MyList.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
   | <template>     <ul class="todo-main">         <transition-group name="todo" appear>             <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj"></MyItem>         </transition-group>     </ul> </template>
  <script> import MyItem from './MyItem.vue' export default {     name: 'MyList',     components: {         MyItem     },     props: ['todos']
  } </script> <style scoped>
  .todo-main {     margin-left: 0px;     border: 1px solid #ddd;     border-radius: 2px;     padding: 0px; }
  .todo-empty {     height: 40px;     line-height: 40px;     border: 1px solid #ddd;     border-radius: 2px;     padding-left: 5px;     margin-top: 10px; }
  .todo-enter-active {     animation: zzmr 1s linear; }
  .todo-leave-active {     animation: zzmr 0.5s linear reverse; }
  @keyframes zzmr {     from {         transform: translateX(-100%);     }
      to {         transform: translateX(0px);     } } </style>
   | 
 
使用Vue发送Ajax请求
终于到这了
使用脚手架解决跨域问题
代理服务器解决跨域问题
就是设置一台代理服务器,然后前端跟代理服务器直接进行通信,代理服务器的端口号是和前端一样的,然后给代理服务器配置目标的url,让代理服务器给目标服务器进行通信
方法一
在vue.config.js中添加如下配置
1 2 3
   | devServer: {     proxy: 'http://localhost:5000'   }
  | 
 
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可
 
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
 
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么请求会转发给服务器(优先匹配前端资源)
 
方法二
编写vue.config.js配置具体代理规则,还是在devServer中
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | proxy: {       '/zzmr': {         target: 'http://localhost:5000',         pathRewrite: { '^/zzmr': '' },         ws: true,           changeOrigin: true        },       '/demo': {         target: 'http://localhost:5001',         pathRewrite: { '^/demo': '' },         ws: true,           changeOrigin: true        },     }
  | 
 
说明:
- 优点: 可以配置多个代理,且可以灵活的控制请求是否走代理
 
- 缺点: 配置略微繁琐,请求资源时必须加前缀
 
测试代码 App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
   | <template>   <div>     <button @click="getStudents">获取学生信息</button>     <button @click="getCars">获取汽车信息</button>   </div> </template>
  <script>
  import axios from "axios"
  export default {   name: 'App',   methods: {     getStudents() {       axios.get('http://localhost:8080/zzmr/students').then(         response => {           console.log('请求成功了', response.data)         },         error => {           console.log('请求失败了', error.message)         }       )     },     getCars() {       axios.get('http://localhost:8080/demo/cars').then(         response => {           console.log('请求成功了', response.data)         },         error => {           console.log('请求失败了', error.message)         }       )     }   } } </script>
   | 
 
github案例-静态组件
老师给的那个url获取的图片已经没了
所以用我自己的吧,我自己的favicon.ico

这个案例还挺有意思,可以搜索github里的用户
代码放着吧
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   |  import Vue from "vue"
  import App from './App'
 
  Vue.config.productionTip = false
 
  const vm = new Vue({     el: '#app',     render: h => h(App),     beforeCreate() {         Vue.prototype.$bus = this     } })
 
  | 
 
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | <template>   <div class="container">     <Search></Search>     <List></List>   </div> </template>
  <script>
  import Search from "./components/Search.vue" import List from "./components/List.vue"
  export default {   name: 'App',   methods: {
    },   components: {     List, Search   } } </script>
  <style>
  </style>
   | 
 
List.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
   | <template>     <div class="row">                  <div v-show="info.users.length" class="card" v-for="user of info.users" :key="user.login">             <a :href="user.html_url" target="_blank">                 <img :src="user.avatar_url" style="width: 100px" />             </a>             <p class="card-text">{{ user.login }}</p>         </div>                  <h1 v-show="info.isFirst">欢迎使用</h1>                  <h1 v-show="info.isLoading">Loading</h1>                  <h1 v-show="info.errMsg">{{ info.errMsg }}</h1>     </div> </template>
  <script>
  import pubsub from "pubsub-js"
  export default {     name: 'List',     data() {         return {             info: {                 isFirst: true,                 isLoading: false,                 errMsg: '',                 users: []             }         }     },     mounted() {                  
 
 
          this.pubId = pubsub.subscribe('updateListData', (msgName, dataObj) => {             this.info = { ...this.info, ...dataObj }         })     } } </script>
  <style scoped> .album {     min-height: 50rem;          padding-top: 3rem;     padding-bottom: 3rem;     background-color: #f7f7f7; }
  .card {     float: left;     width: 33.333%;     padding: .75rem;     margin-bottom: 2rem;     border: 1px solid #efefef;     text-align: center; }
  .card>img {     margin-bottom: .75rem;     border-radius: 100px; }
  .card-text {     font-size: 85%; } </style>
   | 
 
Search.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
   | <template>     <section class="jumbotron">         <h3 class="jumbotron-heading">Search Github Users</h3>         <div>             <input type="text" placeholder="enter the name you search" v-model="keyWord"                 @keyup.enter="searchUsers" /> <button @click="searchUsers">Search</button>         </div>     </section> </template>
  <script>
  import axios from "axios" import pubsub from "pubsub-js"
  export default {     name: 'Search',     data() {         return {             keyWord: ''         }     },     methods: {                  
 
 
 
 
 
 
 
 
 
 
 
 
 
                   searchUsers() {                                       pubsub.publish('updateListData', { isFirst: false, isLoading: true, errMsg: '', users: [] })
                           axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(response => {                                                                    pubsub.publish('updateListData', { isLoading: false, errMsg: '', users: response.data.items })             }, error => {                                  console.log('请求失败了', error.message)                                  pubsub.publish('updateListData',{ isLoading: false, errMsg: error.message, users: [] })             })         }
      } } </script>
  <style>
  </style>
   | 
 
vue-resouces
安装:npm i vue-resource
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | 
  import vueResource from 'vue-resource'
  Vue.use(vueResource) ...         searchUsers() {                                       pubsub.publish('updateListData', { isFirst: false, isLoading: true, errMsg: '', users: [] })
                           this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(response => {                                                                    pubsub.publish('updateListData', { isLoading: false, errMsg: '', users: response.data.items })             }, error => {                                  console.log('请求失败了', error.message)                                  pubsub.publish('updateListData',{ isLoading: false, errMsg: error.message, users: [] })             })         }
 
 
  | 
 
如上代码,就是把axios换成this.$http,用法和axios一摸一样
插槽
再给图床上传一张大的头像
还行,没啥难的
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件。
 
分类:默认插槽、具名插槽、作用域插槽
 
使用方式:
- 默认插槽:
 
1 2 3 4 5 6 7 8 9 10 11
   | 父组件中:         <Category>            <div>html结构1</div>         </Category> 子组件中:         <template>             <div>                                <slot>插槽默认内容...</slot>             </div>         </template>
   | 
 
- 具名插槽:
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | 父组件中:         <Category>             <template slot="center">               <div>html结构1</div>             </template>
              <template v-slot:footer>                <div>html结构2</div>             </template>         </Category> 子组件中:         <template>             <div>                                <slot name="center">插槽默认内容...</slot>                <slot name="footer">插槽默认内容...</slot>             </div>         </template>
   | 
 
作用域插槽:
- 理解:
<span style="color:red">数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定) 
- 具体编码:
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
   | 父组件中: 		<Category> 			<template scope="scopeData"> 				 				<ul> 					<li v-for="g in scopeData.games" :key="g">{{g}}</li> 				</ul> 			</template> 		</Category>
  		<Category> 			<template slot-scope="scopeData"> 				 				<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4> 			</template> 		</Category> 子组件中:         <template>             <div>                 <slot :games="games"></slot>             </div>         </template>
          <script>             export default {                 name:'Category',                 props:['title'],                                  data() {                     return {                         games:['红色警戒','穿越火线','劲舞团','超级玛丽']                     }                 },             }         </script>
   | 
 
 
vuex
vuex简介
概念: 专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间的通信
 
什么时候使用vuex?
- 多个组件依赖于同一状态
 
- 来自不同组件的因为需要变更同一状态
 
 
求和案例-vue版
Count.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
   | <template>     <div>         <h1>当前求和为: {{ sum }}</h1>         <select v-model.number="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">当前求和为奇数再加</button>         <button @click="incrementWait">等一等再加</button>     </div> </template>
  <script> export default {     name: 'Count',     data() {         return {             n: 1,              sum: 0          }     },     methods: {         increment() {             this.sum += this.n         },         decrement() {             this.sum -= this.n         },         incrementOdd() {             if (this.sum % 2) {                 this.sum += this.n             }         },         incrementWait(){             setTimeout(()=>{                 this.sum += this.n             },500)         }     }
  } </script>
  <style> button {     margin-left: 5px; } </style>
   | 
 
vuex工作原理
一张图:

安装及搭建vuex环境
执行命令:npm i vuex@3
现在vuex已经更新到4了,适配vue3,而vue2只能使用vuex3
搭建环境:
- 在src下创建文件 
src/store/index.js 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   |  import Vue from 'vue'
  import Vuex from 'vuex'
  Vue.use(Vuex)
 
  const actions = {}
  const mutations = {}
  const state = {}
 
  export default new Vuex.Store({ 	actions, 	mutations, 	state })
 
  | 
 
- 在main.js中创建vm时传入store配置项
 
1 2 3 4 5 6 7 8 9 10 11
   | ......
  import store from './store' ......
 
  new Vue({ 	el:'#app', 	render: h => h(App), 	store })
   | 
 
基本使用
Count.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
   | <template>     <div>         <h1>当前求和为: {{ $store.state.sum }}</h1>         <select v-model.number="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">当前求和为奇数再加</button>         <button @click="incrementWait">等一等再加</button>     </div> </template>
  <script> export default {     name: 'Count',     data() {         return {             n: 1,          }     },     methods: {                  increment() {             this.$store.commit('JIA', this.n)         },         decrement() {             this.$store.commit('JIAN', this.n)         },         incrementOdd() {             this.$store.dispatch('jiaOdd', this.n)         },         incrementWait() {             this.$store.dispatch('jiaWait', this.n)         }     }
  } </script>
  <style> button {     margin-left: 5px; } </style>
   | 
 
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
   | 
 
  import Vuex from 'vuex'
 
  import Vue from 'vue'
 
  Vue.use(Vuex)
 
 
  const actions = {          
 
 
 
 
 
 
      jiaOdd(context, value) {         if (context.state.sum % 2) {             context.commit('JIA', value)         }                  
 
 
      },
      
 
 
 
 
 
 
 
           jiaWait(context, value) {         setTimeout(() => {             context.commit('JIA', value)         }, 500);     } }
 
  const mutations = {     JIA(state, value) {         state.sum += value     },     JIAN(state, value) {         state.sum -= value     },
  }
 
  const state = {     sum: 0 
  }
 
 
  export default new Vuex.Store({     actions,     mutations,     state })
 
  | 
 
- 组件中读取vuex中的数据: 
$store.state.sum 
- 组件中修改vuex中的数据: 
$store.dispatch('action中的方法名',数据)或 $store.commit('mutation中的方法名',数据) 
- 若没有网络请求或者其他业务逻辑,组件中可以越过actions,即不屑dispatch,直接写commit
 
_getter配置项
- 概念:当state中的数据需要进行加工后再使用,可以使用getters加工
 
- 在store(index).js中追加getters配置
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   |  const getters = {     bigSum(state) {         return state.sum * 10     } }
 
  export default new Vuex.Store({     actions,     mutations,     state,     getters  })
 
  | 
 
- 组件中读取数据 
$store.getters.bigSum 
四个map方法的使用
- mapState方法:用于帮助我们映射state中的数据为计算属性
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | computed: {
           ...mapState([sum:'sum',school:'school',subject:'subject'])
           ...mapState(['sum', 'school', 'subject']),
           sum() {         return this.$store.state.sum     },     school() {         return this.$store.state.school     },     subject() {         return this.$store.state.subject     }, }
  | 
 
- mapGetters方法: 用于帮助我们映射getters中的数据为计算属性(使用方法和mapState类似,或者说一摸一样)
 
1 2 3 4 5 6 7 8 9 10
   |  bigSum() {     return this.$store.getters.bigSum },
 
  ...mapGetters(['bigSum']),
 
  ...mapGetters({bigSum:'bigSum'})
 
  | 
 
- mapActions方法: 用于帮助我们生成与actions对话的方法,即包含 
$store.dispatch(xxx)的函数 
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | methods :{          ...mapActions({ incrementOdd: 'jiaOdd', incrementWait: 'jiaWait' })
           ...mapActions(['jiaOdd','jiaWait'])          incrementOdd() {         this.$store.dispatch('jiaOdd', this.n)         },     incrementWait() {         this.$store.dispatch('jiaWait', this.n)     } }
  | 
 
- mapMutations方法: 用于帮助我们生成与mutations对话的方法,即:包含``$store.commit(xxx)```的函数
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | methods: {          
 
 
 
 
 
           ...mapMutations({ increment: 'JIA', decrement: 'JIAN' }),
            }
  | 
 
备注: mapActions与mapMutations使用时,若需要传递参数需要,在模板中绑定事件传递好参数,否则参数时事件对象
vuex模块化编码
目的:让代码更好维护,让多种数据分类更加明确。
 
修改 store.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | const countAbout = {   namespaced:true,   state:{x:1},   mutations: { ... },   actions: { ... },   getters: {     bigSum(state){        return state.sum * 10     }   } }
  const personAbout = {   namespaced:true,   state:{ ... },   mutations: { ... },   actions: { ... } }
  const store = new Vuex.Store({   modules: {     countAbout,     personAbout   } })
  | 
 
开启命名空间后,组件中读取state数据:
1 2 3 4
   |  this.$store.state.personAbout.list
  ...mapState('countAbout',['sum','school','subject']),
 
  | 
 
开启命名空间后,组件中读取getters数据:
1 2 3 4
   |  this.$store.getters['personAbout/firstPersonName']
  ...mapGetters('countAbout',['bigSum'])
 
  | 
 
开启命名空间后,组件中调用dispatch
1 2 3 4
   |  this.$store.dispatch('personAbout/addPersonWang',person)
  ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
 
  | 
 
开启命名空间后,组件中调用commit
1 2 3 4
   |  this.$store.commit('personAbout/ADD_PERSON',person)
  ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
 
  | 
 
路由
路由简介
vue-router
vue的一个插件库,专门用来实现SPA应用
SPA
- 单页Web应用(single page web application ,SPA)
 
- 整个应用只有一个完整的页面
 
- 点击页面中的导航链接不会刷新页面,只会做页面的局部更新
 
- 数据需要通过ajax请求获取
 
路由的理解
什么是路由?
- 路由就是一组key-value的对应关系
 
- 多个路由,需要经过路由器管理
 
路由分类
- 后端路由
- 理解: value是function,用于处理客户端提交的请求
 
- 工作过程: 服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
 
 
- 前端路由
- 理解: value是component,用于展示页面内容
 
- 工作过程: 当浏览器的路劲改变时,对应的组件就会显示
 
 
路由基本使用
安装:npm i vue-router@3
router3版本对应vue2,现在是4版本了,对应vue3,所以要装3版本的
实现如图的功能

应用插件,还是引入后直接user即可
看main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   |  import Vue from "vue"
  import App from './App'
 
  import VueRouter from "vue-router"
 
  import router from './router'
 
  Vue.use(VueRouter)
 
  Vue.config.productionTip = false
 
 
  const vm = new Vue({     el: '#app',     render: h => h(App),     router: router })
 
  | 
 
看index.js(router目录下的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   |  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         }     ], })
 
  | 
 
看App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
   | <template>   <div>     <div class="row">       <div class="col-xs-offset-2 col-xs-8">         <div class="page-header">           <h2>Vue Router Demo</h2>         </div>       </div>     </div>     <div class="row">       <div class="col-xs-2 col-xs-offset-2">         <div class="list-group">                      
 
                       <router-link active-class="active" class="list-group-item" to="/about">About</router-link>           <router-link active-class="active" class="list-group-item" to="/home">Home</router-link>
          </div>       </div>       <div class="col-xs-6">         <div class="panel">           <div class="panel-body">                          <router-view></router-view>           </div>         </div>       </div>     </div>   </div> </template>
  <script>
 
  export default {   name: 'App',   methods: {
    },   components: {
    },   mounted() {   } }
  </script>
  <style scoped>
  </style>
   | 
 
还有两个组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   |  <template>     <h2>我是Home的内容</h2> </template>
  <script> export default {     name: 'Home', } </script>
  <style>
  </style>
 
  <template>             <h2>我是About的内容</h2> </template>
  <script> export default {     name: 'About' } </script>
  <style>
  </style>
 
  | 
 
总结
安装vue-router,命令:npm i vue-router
 
应用插件:Vue.use(VueRouter)
 
编写router配置项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   |  import VueRouter from 'vue-router'
  import About from '../components/About' import Home from '../components/Home'
 
  const router = new VueRouter({ 	routes:[ 		{ 			path:'/about', 			component:About 		}, 		{ 			path:'/home', 			component:Home 		} 	] })
 
  export default router
 
  | 
 
 
实现切换(active-class可配置高亮样式)
1
   | <router-link active-class="active" to="/about">About</router-link>
   | 
 
 
指定展示位置
1
   | <router-view></router-view>
   | 
 
注意事项
- 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹
 
- 通过切换,”隐藏了路由组件”,默认是被销毁掉的,需要的时候再去挂载
 
- 每个组件都有自己的
$route属性,里面存储着自己的路由信息 
- 整个应用只有一个router,可以通过组件的
$router属性获取到 
嵌套路由
配置路由规则,使用children配置项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | routes: [         {             path: '/about',             component: About         },         {             path: '/home',             component: Home,             children: [                 {                                          path: 'news',                     component: News                 },                 {                     path: 'message',                     component: Message                 },             ]         },
      ],
   | 
 
 
跳转-要写完整路径
1
   | <router-link active-class="active" class="list-group-item" to="/home/news">News</router-link>
   | 
 
路由的query参数
- 传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | <li v-for="msg in messageList" :key="msg.id">         <!-- 跳转路由并携带query参数,to的字符串写法 -->         <!-- <router-link             :to="`/home/message/detail?id=${msg.id}&title=${msg.title}`">{{ msg.title }}</router-link> -->         <!-- to的对象写法 -->         <router-link :to="{             path: '/home/message/detail',             query: {                 id: msg.id,                 title: msg.title,             }         }">             {{ msg.title }}         </router-link> </li>
   | 
 
- 接收参数
 
$route.query.xxx
命名路由
作用: 可以简化路由的跳转
 
如何使用
- 给路由命名: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   | routes: [     {                  name: 'guanyu',         path: '/about',         component: About     },     {         path: '/home',         component: Home,         children: [             {                                  path: 'news',                 component: News             },             {                 path: 'message',                 component: Message,                 children: [                     {                                                  name: 'xiangqing',                         path: 'detail',                         component: Detail                     }                 ]             },         ]     },
  ],
   | 
 
- 简化跳转: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | <router-link :to="{                 // path: '/home/message/detail',                 name: 'xiangqing',                 query: {                     id: msg.id,                     title: msg.title,                 }             }">                 {{ msg.title }} </router-link>
 
  <router-link active-class="active" class="list-group-item" to="/about">About</router-link> <router-link active-class="active" class="list-group-item" :to="{ name: 'guanyu' }">About</router-link>
  | 
 
 
params参数
配置路由,声明接收params参数:
1 2 3 4 5 6 7 8 9 10 11
   | {     path: 'message',     component: Message,     children: [         {             name: 'xiangqing',             path: 'detail/:id/:title',               component: Detail         }     ] },
  | 
 
 
传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   |  <router-link :to="`/home/message/detail/${msg.id}/${msg.title}`">     {{ msg.title }} </router-link>
  <router-link :to="{
      // 注意,如果使用params,就不能使用path,必须使用name
      // path: '/home/message/detail',     name: 'xiangqing',     params: {         id: msg.id,         title: msg.title,     } }">     {{ msg.title }} </router-link>
 
  | 
 
特别注意: 路由携带params参数时,若使用to的对象写法,不能使用path配置项,必须使用name配置
- 接收参数:
$route.params.xxx 
路由的props配置
作用: 让路由组件更加方便的收到并使用参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | {     name: 'xiangqing',     path: 'detail',     component: Detail,
           props: {         a: 1, b: 'hello',     }
           props:true
 
           props($route) {         return { id: $route.query.id, title: $route.query.title }     },          
 
 
  }
  | 
 
接收的地方还是正常的接收,和原来一样
router-link的replace属性
- 作用: 控制路由跳转时操作浏览器历史记录的模式
 
- 浏览器的历史记录有两种写入方式,分别为push和replace,push是追加历史记录,replace是替换当前记录,路由跳转时候默认为push
 
- 如何开启replace模式:```<router-link replace … >News
 
编程式路由导航
什么是编程式路由导航?就是不借助router-link标签的路由导航,让路由跳转更加灵活
具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   | methods: {         pushShow(msg) {             this.$router.push({                                  path: '/home/message/detail',                 query: {                     id: msg.id,                     title: msg.title,                 }             })         },         replaceShow(msg) {             this.$router.replace({                 path: '/home/message/detail',                 query: {                     id: msg.id,                     title: msg.title,                 }             })
          }     }
                   forward(){             this.$router.forward()         },         back(){             this.$router.back()         },         test(){             this.$router.go(-2)          }
  | 
 
缓存路由组件
- 作用:让不展示的路由组件保持挂载,不被销毁
 
- 具体编码
1 2 3 4 5 6
   | <!-- 不写include,所有的组件都会保存 -->
 
  <keep-alive :include="['News','Message']">     <router-view></router-view> </keep-alive>
   | 
 
activated和deactivated生命周期钩子
作用: 路由组件所独有的两个钩子,用于捕获路由组件的激活状态
 
具体使用:
1 2 3 4 5 6 7 8 9 10 11
   | activated(){     this.timer = setInterval(() => {         this.opacity -= 0.01         if (this.opacity <= 0) this.opacity = 1              }, 16) }, deactivated(){     console.log('News组件失活了')     clearInterval(this.timer) }
  | 
 
路由守卫
- 作用:对路由进行权限控制
 
- 分类:全局守卫,独享守卫,组件内守卫
 
全局守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   |  router.beforeEach((to, from, next) => {     console.log('前置路由守卫', to, from)     if (to.meta.isAuth) {           if (localStorage.getItem('school') === 'zzmr') {             next()         } else {             alert('学校名不对,无权限查看')         }     } else {         next()     } })
 
 
  router.afterEach((to, from) => {     document.title = to.meta.title || 'ZZMR' })
 
  | 
 
同时也引入新的路由配置项:meta,用于自定义一些属性,来辅助一些功能
独享守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | {                     name: 'xinwen',                     path: 'news',                     component: News,                     meta: { isAuth: true, title: '新闻' },                     beforeEnter: (to, from, next) => {                         console.log('独享路由守卫', to, from)                         if (to.meta.isAuth) {                               if (localStorage.getItem('school') === 'zzmr') {                                 next()                             } else {                                 alert('学校名不对,无权限查看')                             }                         } else {                             next()                         }                     }                 },
  | 
 
就是直接在路由里面写,但是只有前置,没有后置
组件内路由守卫
进入和离开守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
   | <template>     <h2>我是About的内容</h2> </template>
  <script> export default {     name: 'About',     
 
 
 
 
 
           beforeRouteEnter(to, from, next) {         console.log('App---beforeRouteEnter')         next()     },          beforeRouteLeave(to, from, next) {         console.log('App---beforeRouteLeave')         next()     } } </script>
  <style>
  </style>
   | 
 
hostory模式和hash模式
‘#’官方名:hash(哈希)
- 对于一个url来说,什么是hash值?—#及其后面的内容就是hash值
 
- hash值不会包含HTTP请求种,即:hash值不会带给服务器
 
- hash模式:
- 地址中永远带着#号,不美观
 
- 若以后地址通过第三方手机App分享,若app校验严格,则地址会被标记为不合法
 
- 兼容性比较好
 
 
- history模式
- 地址干净,美观
 
- 兼容性和hash模式相比略差
 
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
 
 
springboot解决(解决了,但没完全解决)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | package com.zzmr.boot.config;
  import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.ErrorPageRegistrar; import org.springframework.boot.web.server.ErrorPageRegistry; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component;
 
 
 
 
 
  @Component public class ErrorConfig implements ErrorPageRegistrar {     @Override     public void registerErrorPages(ErrorPageRegistry registry) {         ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/index.html");         registry.addErrorPages(error404Page);     } }
   | 
 
还有就是一个打包命令:
npm run build
打包整个项目,然后放到springboot项目下的static目录下,就可以正常访问了
Vue UI组件库
两大类
Element UI
安装:npm i element-ui
然后,简单的引入使用就行了
1 2 3 4 5 6 7
   |  import ElementUI from 'element-ui';
  import 'element-ui/lib/theme-chalk/index.css';
 
  Vue.use(ElementUI)
 
  | 
 
没错,全部引入肯定不太行
Element UI 按需引入
- 安装
 
npm install babel-plugin-component -D
- 修改引入方式
1 2 3 4 5 6 7 8 9 10 11
   |  import { Button, Row, DatePicker } from 'element-ui';
 
 
  Vue.config.productionTip = false
 
  Vue.component(Button.name, Button); Vue.component(Row.name, Row); Vue.component(DatePicker.name, DatePicker);
 
  | 
 
用哪个就引入哪个
- 还有一个文件要改:babel.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | module.exports = {   presets: [     '@vue/cli-plugin-babel/preset',          ["@babel/preset-env", { "modules": false }]   ],   plugins: [     [       "component",       {         "libraryName": "element-ui",         "styleLibraryName": "theme-chalk"       }     ]   ] }
  | 
 
2023年1月16日 21点20分
好了,Vue2学完了
Vue3
其实我在想到底是先去做一个vue2的项目,还是直接看完Vue3然后直接去写一个前后端交互的项目
现在想想,还是后者吧
创建工程
使用vue-cli创建项目
- 创建Vue3项目:vue create vue3_test
还是要在管理员权限下才可以创建 
使用vite创建项目
什么是vite?—新一代的前端构建工具
创建项目:
1 2 3 4 5 6 7 8
   | ## 创建工程 npm init vite-app <project-name> ## 进入工程目录 cd <project-name> ## 安装依赖 npm install ## 运行 npm run dev
   | 
 
是非常快,但是后面使用还是cli哈哈哈哈
项目结构
最大的改变:main.js
1 2 3 4 5 6 7 8
   |  import { createApp } from 'vue' import App from './App.vue'
 
  createApp(App).mount('#app')
 
 
 
  | 
 
还有就是组件不用写跟标签了,可以直接写内容
1 2 3 4 5
   | <template>   <!-- Vue3组件中的模板结构可以没有根标签 -->   <img alt="Vue logo" src="./assets/logo.png">   <HelloWorld msg="Welcome to Your Vue.js App" /> </template>
   | 
 
常用Composition API
组合式API
setup
- 理解: Vue3.0中一个新的配置项,值为一个函数
 
- setup是所有CompositionAPI(组合API)’表演的舞台’
 
- 组件中所用到的:数据方法等等,均要配置在setup中
 
- setup函数的两种返回值
- 若返回一个对象,则对象中的属性,方法,在模板中均可以直接使用
 
若返回一个渲染函数:则可以自定义渲染内容(了解) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
   | <template> <h1>一个人的信息</h1> <h2>姓名{{ name }}</h2> <h2>年龄{{ age }}</h2> <button @click="sayHello">sayHello</button> </template>
  <script>
  import { h } from 'vue'
  export default { name: 'App', setup() {               let name = '张三'     let age = 18
           function sayHello() {     alert(`我叫${name},我${age}岁了,Hello`)     }
           
 
 
 
 
                return () => h('h1', 'ZZMR') } } </script>
   | 
 
 
- 注意点:
- 尽量不要与Vue2.x配置混用
- Vue2.x(data,methods,computed)中可以访问到setup中的属性
 
- 但在setup中不能访问到vue2.x的配置(data,methods,computed)
 
- 如果有重名,setup优先
 
 
- setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性
 
 
ref函数
引用实现的实例对象-引用对象
- 作用: 定义一个响应式数据
 
- 语法: 
const xxx = ref(initValue)
- 创建一个包含响应式数据的应用对象(reference)对象-简称ref对象
 
- JS中操作数据-xxx.value
 
- 模板中读取数据:不需要value,直接
<div>{{xxx}}</div> 
 
- 备注: 
- 接收的数据可以是:基本类型,也可以是对象类型
 
- 基本类型的数据,响应式依然是靠
Object.defineProperty()的get与set完成的 
- 对象类型的数据:内部使用了-reactive函数
 
 
setup配置项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   | setup() {               let name = ref('张三')     let age = ref(18)     let job = ref({       type: '全栈工程师',       salary: '20K'     })
           function changeInfo(){       name.value = '李四'       age.value = 20              job.value.type = '离职'       job.value.salary = '30k'       console.log(name,age)     }
 
           return {       name,       age,       changeInfo,       job     }
                   }
  | 
 
reactive函数
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
 
- 语法: const 代理对象 = reactive(源对象)接收一个对象(或数组),返回一个代理对象(proxy的实例对象)
 
- reactive定义的响应式数据是”深层次的”
 
- 内部基于ES6的proxy实现,通过代理对象操作源对象内部数据进行操作
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
   | <template>   <h1>一个人的信息</h1>   <h2>姓名{{ person.name }}</h2>   <h2>年龄{{ person.age }}</h2>   <h2>工作种类:{{ person.job.type }}</h2>   <h2>工资{{ person.job.salary }}</h2>   <h3>c的值为{{ person.job.a.b.c }}</h3>   <h3>爱好:{{ person.hobby }}</h3>   <button @click="changeInfo">修改人的信息</button> </template>
  <script>
  import { reactive } from 'vue'
  export default {   name: 'App',   setup() {
      let person = reactive({       name: '张三',       age: 18,       job: {         type: '全栈工程师',         salary: '20K',         a: {           b: {             c: 666           }         }       },       hobby: ['抽烟', '喝酒', '烫头']     })
           function changeInfo() {       person.name = '李四'       person.age = 20              person.job.type = '离职'       person.job.salary = '30k'              person.job.a.b.c = 888
               person.hobby[0] = '学习'
      }
 
           return {       person,       changeInfo,     }   } } </script>
   | 
 
Vue3响应式原理
Vue2中响应式存在的问题:
- 新增属性,删除属性,界面不会更新
 
- 直接通过下标修改数组,界面不会自动更新
 
Vue3的响应式
- 通过Proxy(代理):拦截对象中任意属性的变化:包括:属性值的读写,属性的添加,属性的删除等
 
- 通过Reflect(翻身):对被代理(源)对象的属性进行操作
 
- MDN文档中描述Proxy和Reflect
- Proxy
 
- Reflect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
   | <!DOCTYPE html> <html lang="en">
  <head>     <meta charset="UTF-8">     <meta http-equiv="X-UA-Compatible" content="IE=edge">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <title>Vue3响应式</title> </head>
  <body>     <script>         let person = {             name: '张三',             age: 18,         }
                   
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
                   const p = new Proxy(person,{             get(target,propName){                 console.log(`有人读取了p的${propName}属性`)                                  return Reflect.get(target,propName)             },                          set(target,propName,value){                 console.log(`有人修改了p的${propName}属性`)                                  return Reflect.set(target,propName,value)             },             defineProperty(target,propName){                 console.log(`有人删除了p的${propName}属性`)                                  return delete Reflect.deleteProperty(target,propName)             }         })
          let obj = {a:1,B:2}
          
 
 
 
 
 
 
 
 
 
 
          
 
 
 
 
 
 
 
 
 
 
      </script> </body>
  </html>
   | 
 
 
reactive对比ref
- 从定义数据角度对比
- ref用来定义:基本类型数据
 
- reactive用来定义:对象(或数组)类型数据
 
- 备注: ref也可以用来定义对象(或数组)类型数组,它内部会自动通过reactive转化为代理对象
 
 
- 从原理角度对比
- ref通过
Object.defineProperty()的get与set来实现响应式(数据劫持) 
- reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect来操作源对象内部的数据
 
 
- 从使用角度对比
- ref定义的数据,操作数据需要.value,读取数据时模板中直接读取不需要.value
 
- reactive定义的数据:操作数据预读取数据,均不需要.value
 
 
setup两个注意点
- setup的执行时机
- 在beforeCreate之前执行一次,this是undefined
 
 
- setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接受了的属性
 
- context:上下文对象
- attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于
this.$attrs 
- slots:收到的插槽内容,相当于
this.$slots 
- emit:分发自定义事件的函数:相当于
this.$emit 
 
 
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | <template>   <Demo @hello="showHelloMsg" msg="Hello" school="ZZMR">     <template v-slot:qwe>       <span>ZZMR</span>     </template>   </Demo> </template>
  <script>
  import Demo from './components/Demo.vue'
  export default {   name: 'App',   components: {     Demo,   },   setup() {     function showHelloMsg(value) {       alert(`Hello,触发了Hello事件,收到的参数是${value}`)     }
      return {       showHelloMsg     }   } } </script>
   | 
 
Demo.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
   | <template>     <h1>一个人的信息</h1>     <h2 v-show="person.name">姓名{{ person.name }}</h2>     <h2>年龄{{ person.age }}</h2>     <button @click="test">测试Demo组件的Hello事件</button> </template>
  <script>
  import { reactive } from 'vue'
  export default {     name: 'Demo',     
 
      props:['msg','school'],     emits:['hello'],     setup(props,context) {                                                               console.log(context.slots)
          let person = reactive({             name: '张三',             age: 18,         })
          function test(){             context.emit('hello',666)         }
                   return {             person,test         }     } } </script>
   | 
 
计算属性
computed函数
- 与Vue2中的computed配置功能一致
 
- 写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
   |      
 
 
 
      setup() {
          let person = reactive({             firstName: '张',             lastName: '三',         })
                   
 
 
                   person.fullName = computed(             {                 get(){                     return person.firstName + '-' + person.lastName                 },                 set(value){                     const nameArr = value.split('-')                     person.firstName = nameArr[0]                     person.lastName = nameArr[1]                 }             }         )
 
                   return {             person         }     }
 
  | 
 
监视属性
- 与Vue2中watch配置功能一致
 
- 两个小坑
- 监视reactive定义的响应式数据时,oldValue无法正确获取,强制开启了深度监视(deep配置失效)
 
- 监视reactive定义的响应式数据中某个属性时,deep配置有效
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
   | <template>     <h2>当前求和为: {{ sum }}</h2>     <button @click="sum++">点我加1</button>     <hr>     <h2>当前的信息为{{ msg }}</h2>     <button @click="msg += '!'">修改信息</button>     <hr>     <h2>姓名: {{ person.name }}</h2>     <h2>年龄: {{ person.age }}</h2>     <button @click="person.name += '@'">修改姓名</button>     <button @click="person.age++">增长年龄</button>     <hr>     <h2>薪资:{{ person.job.j1.salary }}</h2>     <button @click="person.job.j1.salary++">加薪</button> </template>
  <script>
  import { ref, watch, reactive } from 'vue'
  export default {     name: 'Demo',
           
 
 
 
 
 
 
 
 
 
 
 
      setup() {
          let sum = ref(0)         let msg = ref('Hello')
          let person = reactive({             name: '张三',             age: 18,             job: {                 j1: {                     salary: 20                 }             }         })
                   
 
 
 
 
                   
 
 
 
 
                            
 
 
                   
 
 
                   
 
 
                            watch(()=>person.job, (newValue, oldValue) => {             console.log('job变化了',newValue, oldValue)         },{             deep:true         })
                   return {             sum, msg, person         }     } } </script>
   | 
 
.value的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
   | <template>     <h2>当前求和为: {{ sum }}</h2>     <button @click="sum++">点我加1</button>     <hr>     <h2>当前的信息为{{ msg }}</h2>     <button @click="msg += '!'">修改信息</button>     <hr>     <h2>姓名: {{ person.name }}</h2>     <h2>年龄: {{ person.age }}</h2>     <button @click="person.name += '@'">修改姓名</button>     <button @click="person.age++">增长年龄</button>     <hr>     <h2>薪资:{{ person.job.j1.salary }}</h2>     <button @click="person.job.j1.salary++">加薪</button> </template>
  <script>
  import { ref, watch, reactive } from 'vue'
  export default {     name: 'Demo',
      setup() {
          let sum = ref(0)         let msg = ref('Hello')
          let person = ref({             name: '张三',             age: 18,             job: {                 j1: {                     salary: 20                 }             }         })
                   watch(sum,(newValue, oldValue)=>{             console.log('sum改变了',newValue, oldValue)         })
          
 
 
          watch(person,(newValue, oldValue)=>{             console.log('person改变了',newValue, oldValue)         },{             deep:true         })
                   return {             sum, msg, person         }     } } </script>
   | 
 
分清到底是ref还是reactive
watchEffect函数
- watch的套路是: 既要指明监视的属性,也要指明监视的回调
 
- watchEffect的套路是:不用指定监视哪个属性,监视的回调中用到那个属性,那就监视哪个属性
 
- watchEffect有点像computed
- 但computed注重的是计算出来的值(回调函数的返回值),所以必须要写返回值
 
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用谢返回值
1 2 3 4
   | watchEffect(() => {     const x1 = sum.value     console.log('watchEffect所指定得回调执行了') })
  | 
 
 
生命周期
- Vue3中可以继续使用Vue2中的生命周期钩子,但有两个要更改名字
- beforeDestroy改为beforeUnmount
 
- destroyed改为unmounted
 
 
- Vue3也提供了Composition API形式的生命周期钩子,与Vue2中钩子对应关系如下
beforeCreate===>setup() 
created=======>setup() 
beforeMount ===>onBeforeMount 
mounted=======>onMounted 
beforeUpdate===>onBeforeUpdate 
updated =======>onUpdated 
beforeUnmount ==>onBeforeUnmount 
unmounted =====>onUnmounted 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | setup() {
          let sum = ref(0)
                   onBeforeMount(()=> {             console.log('---onBeforeMount---')         })         onMounted(()=> {             console.log('---onMounted---')         })         onBeforeUpdate(()=> {             console.log('---onBeforeUpdate---')         })         onUpdated(()=> {             console.log('---onUpdated---')         })         onBeforeUnmount(()=> {             console.log('---onBeforeUnmount---')         })         onUnmounted(()=> {             console.log('---onUnmounted---')         })
                   return {             sum,         }     },
  | 
 
自定义hook函数
- 什么是hook?本质是一个函数,把setup函数中使用的CompositionAPI进行了封装
 
- 类似于vue2中的mixin
 
- 自定义hook的优势,复用代码,让setup中的逻辑更加清楚易懂
 
usePoint.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
   | import { reactive, onMounted, onBeforeUnmount } from 'vue' export default function () {
           let point = reactive({         x: 0,         y: 0,     })
           function savePoint(event) {         point.x = event.pageX         point.y = event.pageY         console.log(point.x, point.y)     }
           onMounted(() => {         window.addEventListener('click', savePoint)     })
      onBeforeUnmount(() => {         window.removeEventListener('click', savePoint)     })
      return point }
  | 
 
Demo.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | <template>     <h2>当前求和为: {{ sum }}</h2>     <button @click="sum++">点我加1</button>     <hr>     <h2>当前点击时鼠标的坐标为: x:{{ point.x }},y:{{ point.y }}</h2> </template>
  <script>
  import { ref } from 'vue' import usePoint from '../hooks/usePoint'
  export default {     name: 'Demo',     setup() {
          let sum = ref(0)
          let point = usePoint()
                            return {             sum,point,         }     }, } </script>
   | 
 
toRef
- 作用:创建一个ref对象,其value值指向另一个对象中的某个属性
 
- 语法: 
const name = toRef(person,'name') 
- 应用: 要将响应式对象中的某个属性单独提供给外部使用时
 
- 扩展: toRefs与toRef功能一致,但是可以批量创建多个ref对象:
toRefs(person) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
   | <template>     <h2>姓名: {{ name }}</h2>     <h2>年龄: {{ age }}</h2>     <button @click="name += '@'">修改姓名</button>     <button @click="age++">增长年龄</button>     <hr>     <h2>薪资:{{ job.j1.salary }}</h2>     <button @click="job.j1.salary++">加薪</button> </template>
  <script>
  import { reactive, toRef, toRefs } from 'vue'
  export default {     name: 'Demo',
      setup() {
          let person = reactive({             name: '张三',             age: 18,             job: {                 j1: {                     salary: 20                 }             }         })
 
 
                   return {             
 
                           ...toRefs(person)         }     } } </script>
   | 
 
其他Composition API
shallowReactive与shallowRef
- shallowReactive: 只处理对象最外层属性的响应式
 
- shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理
 
- 什么时候用?
- 如果有一个对象数据,结构比较深,但变化时只是外层属性变化===>shallowReactive
 
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换===>shallowRef
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
   | <template>     <h4>当前的x.y是{{ x.y }}</h4>     <button @click="x.y++">点我x+1</button>     <hr>     <h2>姓名: {{ name }}</h2>     <h2>年龄: {{ age }}</h2>     <button @click="name += '@'">修改姓名</button>     <button @click="age++">增长年龄</button>     <hr>     <h2>薪资:{{ job.j1.salary }}</h2>     <button @click="job.j1.salary++">加薪</button> </template>
  <script>
  import { reactive, shallowReactive, shallowRef, toRefs, ref } from 'vue'
  export default {     name: 'Demo',
      setup() {
                            let person = reactive({             name: '张三',             age: 18,             job: {                 j1: {                     salary: 20                 }             }         })
 
 
                                     let x = shallowRef({             y: 0         })
                   return {             x,             ...toRefs(person)         }     } } </script>
   | 
 
 
readonly与shallowReadonly
- readonly:让一个响应式数据变为只读(深只读)
 
- shallowReadonly:让一个响应式数据变为只读(浅只读)
 
- 应用场景:不希望数据被修改时
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
   | <template>     <h4>当前的求和为是{{ sum }}</h4>     <button @click="sum++">点我x+1</button>     <hr>     <h2>姓名: {{ name }}</h2>     <h2>年龄: {{ age }}</h2>     <button @click="name += '@'">修改姓名</button>     <button @click="age++">增长年龄</button>     <hr>     <h2>薪资:{{ job.j1.salary }}</h2>     <button @click="job.j1.salary++">加薪</button> </template>
  <script>
  import { reactive, shallowReactive, shallowRef, toRefs, ref ,readonly,shallowReadonly} from 'vue'
  export default {     name: 'Demo',
      setup() {
          let sum = ref(0)
                            let person = reactive({             name: '张三',             age: 18,             job: {                 j1: {                     salary: 20                 }             }         })
                            sum = shallowReadonly(sum)         
 
 
 
                   return {             sum,             ...toRefs(person)         }     } } </script>
   | 
 
toRaw与markRaw
- toRaw:
- 作用:将一个由reactive生成的响应式对象转为普通对象
 
- 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面刷新
 
 
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象
 
- 应用场景:
- 有些值不应设置为响应式,例如复杂的第三方类库等
 
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
 
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
   | <template>     <h4>当前的求和为是{{ sum }}</h4>     <button @click="sum++">点我x+1</button>     <hr>     <h2>姓名: {{ name }}</h2>     <h2>年龄: {{ age }}</h2>     <button @click="name += '@'">修改姓名</button>     <button @click="age++">增长年龄</button>     <hr>     <h2>薪资:{{ job.j1.salary }}</h2>     <h3 v-show="person.car">{{ person.car }}</h3>     <button @click="job.j1.salary++">加薪</button>     <hr>     <button @click="showRawPerson">输出最原始的person</button>     <button @click="addCar">给人添加一台车</button>     <button @click="person.car.name+='!'">换车名</button>     <button @click="person.car.price+='0'">换价格</button> </template>
  <script>
  import { reactive, toRaw, toRefs, ref, markRaw } from 'vue'
  export default {     name: 'Demo',
      setup() {
          let sum = ref(0)
                            let person = reactive({             name: '张三',             age: 18,             job: {                 j1: {                     salary: 20                 }             }         })
 
          function showRawPerson() {             
          }
          function addCar() {             let car = { name: 'bmw', price: '40W' }             person.car = markRaw(car)         }
                   return {             sum, showRawPerson,             person,             ...toRefs(person), addCar         }     } } </script>
   | 
 
customRef
- 作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制
 
- 实现防抖效果
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
   | <template>   <input type="text" v-model="keyWord">   <h3>{{ keyWord }}</h3> </template>
  <script>
  import { ref, customRef } from 'vue';
  export default {   name: 'App',   components: {
    },   setup() {          
           function myRef(value) {       let timer       console.log('---myRef---', value)       return customRef((track, trigger) => {         return {           get() {             console.log('有人从myRef容器中读取数据了,给它的值:', value)             track()               return value           },           set(newValue) {             console.log('有人把myRef容器中的数据改为了', newValue)             clearTimeout(timer)             timer = setTimeout(() => {               value = newValue               trigger()              },1000)           }         }       })     }
      let keyWord = myRef('hello')  
 
      return {       keyWord     }   } } </script>
   | 
 
也是终于知道防抖到底是什么样的了
provide与inject
- 作用: 实现祖孙组件间通信
 
- 套路: 父组件有一个provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   |      let car = reactive({       name: 'bmw',       price: '40W',     })
           provide('car',car)
 
      setup() {         let car = inject('car')
          return { car }     }
 
  | 
 
很简单,只是父子之间传递数据的话,还是建议使用简单的props,这种适合祖孙之间的通信(孙,重孙什么的)
响应式数据的判断
- isRef:检查一个值是否为一个ref对象
 
- isReactive:检查一个对象是否是由reactive创建的响应式代理
 
- isReadonly:检查一个对象是否由readonly创建的只读代理
 
- isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   | <template>   <h3>我是App组件(祖)</h3> </template>
  <script>
  import { ref, reactive, toRefs, readonly, isRef, isReactive, isReadonly, isProxy } from 'vue';
  export default {   name: 'App',   setup() {
      let car = reactive({       name: 'bmw',       price: '40W',     })
      let sum = ref(0)
      let car2 = readonly(car)
      console.log(isRef(sum))     console.log(isReactive(car))     console.log(isReadonly(car2))     console.log(isProxy(car))
      return {       ...toRefs(car)     }   } } </script>
   | 
 
Composition API的优势
没讲啥啊,但是能感觉到hook以后会用到很多
新的组件
Fragment
- 在Vue2中,组件必须有一个根标签
 
- 在Vue3中,组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
 
- 好处:减少标签层级,减少内存占用
 
Teleport
- 什么是Teleport:是一种能够将我们的组件html结构移动到指定位置的技术
 
1 2 3 4 5 6 7 8 9
   | <Teleport to="body">     <div v-if="isShow" class="mask">         <div class="dialog">             <h3>我是一个弹窗</h3>             <h3>一些内容</h3>             <button @click="isShow = false">关闭弹窗</button>         </div>     </div> </Teleport>
   | 
 
这个真只能用多了才能熟悉
Suspense
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
异步引入组件
1 2
   | import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13
   | <template> 	<div class="app"> 		<h3>我是App组件</h3> 		<Suspense> 			<template v-slot:default> 				<Child/> 			</template> 			<template v-slot:fallback> 				<h3>加载中.....</h3> 			</template> 		</Suspense> 	</div> </template>
   | 
 
其他
全局API的转移
Vue2有许多全局API和配置
其他改变
data选项应始终被声明为一个函数
 
过度类名的更改
 
Vue2.x写法
 
1 2 3 4 5 6 7 8
   | .v-enter, .v-leave-to {   opacity: 0; } .v-leave, .v-enter-to {   opacity: 1; }
   | 
 
1 2 3 4 5 6 7 8 9
   | .v-enter-from, .v-leave-to {   opacity: 0; }
  .v-leave-from, .v-enter-to {   opacity: 1; }
   | 
 
1 2 3 4
   | <my-component   v-on:close="handleComponentEvent"   v-on:click="handleNativeClickEvent" />
   | 
 
1 2 3 4 5
   | <script>   export default {     emits: ['close']   } </script>
   | 
 
- 移除了过滤器
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器
 
愿所有的汗水都有收获,愿所有的努力不被辜负
2023年1月18日 21点07分