一、Vue
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
二、Vue实例创建
2.1 Vue.js代码引用
Vue的官网提供了开发版本和生产版本两个版本的源码下载,这里需要注意的是生产版本去掉了相关错误的警告信息,对于调试程序会造成不便。所以在开发环境中需要引入开发版本的Vue.js。
同时也可以使用CDN引用Vue.js
1
2<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
2.2 Vue Devtools调试工具
为了能够更好的观察Vue在页面中的作用,Vue官方推荐了Vue Devtools的调试工具。该工具为Chrome插件,可以在Chrome官方插件平台下载,也可以下载GitHub源码通过编译添加至Chrome浏览器中。
2.2.1 下载
在本地下载Vue Devtools的源码
1
2git clone https://github.com/vuejs/devtools.git
2.2.2 编译
进入项目路径,打开cmd,执行npm install
进行安装。完成后执行npm run bulid
命令编译项目。完成后进入./shells/chrome/
打开文件mainifest.json
修改persistent
属性为true
。
1
2
3
4
5
6
7"background": { "scripts": [ "build/background.js" ], "persistent": true }
2.2.3 安装
打开谷歌浏览器,打开【扩展程序】,打开【开发者模式】点击【加载已解压的扩展程序】,选择Vue Devtools项目中的shells/chrome
文件夹,点击添加即可将Vue Devtools插件部署至Chrome浏览器中。
使用node编译项目比较费时也可以从网上获取编译好的内容,例如:其他博主提供的资源https://github.com/arcliang/Vue-Devtools-
2.3 创建实例
按照官网提供的示例,编写一个简单的Vue示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<div id="root"> <!-- 插值语法用{{}}获取Vue对象中的数据,可以使用JS表达式作为值 --> <h1>Hello,{{name}}</h1> </div> <script src="../js/vue.js"></script> <script type="text/javascript"> // 设置Vue全局属性,禁用生产环境提示信息 Vue.config.productionTip = false; // 创建Vue对象,Vue对象与容器一一对应,不能一对多或多对一 // 在真实开发过程中只有一个Vue对象,并且会会伴随着多组件使用 new Vue({ // 为当前Vue对象绑定对应的容器,值通常使用选择器 el: '#root', // 为该容器设置所需要的数据,值一般使用对象存储数据 data: { name: 'Tom' } }); </script>
三、Vue基础语法
3.1 模板语法
根据2.3的示例,我们可以看出Vue可以通过特定的语法将Vue实例中的数据代入到页面中,这种操作符合HTML的模板基础,可以在Vue渲染中对特别的语法位置进行解析,在Vue中被称为模板语法。
3.1.1 插值
使用{{ propertyName }}
的形式可以将Vue实例中的data的属性直接注入到HTML中。例如2.3中的示例。
在Vue中对于插值语法插入的数据是可以动态刷新的,当data中的数据发生变化时可以进行自动刷新。
可以在Vue Devtools中进行验证。
示例:
在插件中修改属性的值,可以立刻反馈到页面上。
3.1.2 指令
指令语法通常用于为DOM绑定事件或赋予属性。示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<div id="root"> <h1>插值语法</h1> <p>hello, {{name}}</p> <hr> <h1>指令语法</h1> <a v-bind:href=urlTemp.url>链接1</a> <a :href=urlTemp.url>{{urlTemp.name}}</a> </div> <script src="../js/vue.js"></script> <script type="text/javascript"> Vue.config.productionTip = false; new Vue({ el:'#root', data:{ name:'Jack', urlTemp:{ url:'http://www.baidu.com', name:'百度' } } }) </script>
在Vue中的指令语法通常都以v-
开头,常用的指令有v-bind
,v-on
等,也可以使用简写方式。v-bind
可以直接省略,在属性前使用:
即可,v-on
可以简写为@
。
插值语法主要作用于标签体的内容填充,使用js表达式
指令语法主要作用于标签(标签属性,标签体内容,绑定事件),可以调用Vue中的众多指令
3.2 数据绑定
在Vue实例中创建的数据可以通过插值语法与v-bind
在页面进行渲染,这里需要注意的是v-bind
只能将data
中的数据赋予页面中。如果需要实现页面上修改的值同步至Vue实例的data
中,可以使用v-model
指令,因此v-model
是可以为DOM的数据进行双向绑定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<div id="root"> 单向绑定:<input type="text" :value=name /><br> 双向绑定:<input type="text" v-model=name /> </div> <script src="../js/vue.js"></script> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el:'#root', data:{ name:'Tom' } }) </script>
只有带有
value
值的表单元素才能够使用双向绑定
3.3 el和data的两种写法
在Vue实例中,示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56<div id="root1"> <h1>Hello, {{name}}</h1> </div> <div id="root2"> <h1>Hello, {{name}}</h1> </div> <div id="root3"> <h1>Hello, {{name}}</h1> </div> <script src="../js/vue.js"></script> <script> Vue.config.productionTip = false; // 绑定容器 // 1. el直接在创建Vue实例时绑定 new Vue({ el: '#root1', data: { name: 'Tom' } }); // 2. 将Vue实例挂载在容器上 const vm = new Vue({ data: { name: 'Jack' } }); vm.$mount('#root2'); // 绑定数据 // 1. 对象式(直接使用对象,示例略) /* 2. 函数式(只能使用普通函数形式,箭头式函数形式获取的对象是全局对象不能够使用 data: () => { console.log(this) return { name: 'Mary' } } */ new Vue({ el: '#root3', data: function () { console.log(this) return { name: 'Mary' } } }) </script>
在Vue实例中
this
是指定的当前Vue对象,如果使用箭头函数,此时的this
为全局对象,不再特指当前Vue对象
四、数据代理
Vue对数据的绑定核心就是数据代理,我们虽然在Vue对象中创建了属性data
,但是在页面获取data
中的属性值时是以代理的形式获取。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<div id="root"> <h1>Hello, {{ name }}</h1> <h1>Hello, {{ address }}</h1> </div> <script src="../js/vue.js"></script> <script> /** * 1. Vue实例中的data属性,会处理为_data,使用数据劫持实现当数据发生改变时动态刷新页面的功能 * 2. Vue实例中的data属性,Vue会自动对其进行代理,当调用vm.name时则调用_data的getter方法 */ const vm = new Vue({ el: '#root', data: { name:'Tom', address:'三里屯' } }) </script>
可以在页面控制台中输出vm
对象,可以直接看到data
中的name
和address
并不是直接显示值,而是需要点击省略号才能获取它的值,这就是数据代理。只有在需要时才会去获取属性值。
在vm
对象的下方也可以看到对name
和address
两个属性的getter
和setter
方法。
五、事件
5.1 事件
对DOM元素绑定事件时一般使用v-on:[event]='fun'
也可以简写为@[event]='fun'
,对DOM中绑定的事件方法可以写在Vue的methods
属性中。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31<div id="root"> <!-- 1. 点击事件,使用v-on绑定事件:v-on:click="show",也可以简写为@click="show" 2. show1为Vue实例中的方法,这里注意做调用的函数需要写在methods属性中,否则无效 3. 当函数不存在参数时,默认拥有一个事件参数(Event) 4. methods中的函数要使用普通函数的写法,不能写成箭头函数,否则this的值则会指向windows而不是vm --> <button @click="show1">Click me 1</button> <button @click="show2($event, 'Jack')">Click me 2</button> </div> <script src="../js/vue.js"></script> <script> const vm = new Vue({ el:'#root', data: { name:'Tom' }, methods:{ show1(event) { console.log(event) alert('Hello, Tom') }, show2(event, name) { console.log(event) alert('Hello ' + name) } } }) </script>
这里需要注意的是调用函数的参数,如果函数没有参数可以直接在事件绑定中直接写函数名即可。如果存在参数那么默认约定第一个参数为当前事件对象。
5.2 修饰符
修饰符 | 描述 |
---|---|
prevent | 在事件中添加修饰符prevent可以阻止默认事件,对于链接则阻止链接的跳转 |
stop | 当嵌套结构都有事件时,可以通过添加stop属性阻止事件的冒泡 |
once | 当只想当前事件执行一次时,可以添加once属性,当执行一次后就不会再执行 |
capture | 在嵌套结构中,每层的事件会由外而内依次捕获,之后再由内而外的依次冒泡,使用capture可在捕获时执行事件 |
self | 在嵌套结构中,使用self作用于事件上,只有操作当前元素时才会执行该元素上的事件 |
事件的修饰符可以叠加使用
stop和self都可以有效阻住嵌套结构中的事件冒泡
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81<div id="root"> <h1>事件修饰符</h1> <hr> <!-- 01.在事件中添加修饰符prevent可以阻止默认事件,对于链接则阻止链接的跳转 --> <a :href=url @click.prevent="showInfo">点我跳转至百度</a> <hr> <!-- 02.当嵌套结构都有事件时,可以通过添加stop属性阻止事件的冒泡 --> <div class="demo" @click="showMsg(1)"> <button @click.stop="showMsg(2)">点击我实现冒泡</button> </div> <hr> <!-- 03.当只想当前事件执行一次时,可以添加once属性,当执行一次后就不会再执行 --> <button @click.once="showInfo">点我打开弹窗</button> <hr> <!-- 04.在嵌套结构中,每层的事件会由外而内依次捕获,之后再由内而外的依次冒泡,使用capture可在捕获时执行事件 --> <div class="box1" @click.capture="showMsg(1)"> div1 <div class="box2" @click="showMsg(2)"> div2 </div> </div> <hr> <!-- 05.在嵌套结构中,使用self作用于事件上,只有操作当前元素时才会执行该元素上的事件 --> <!-- 注意:stop和self都可以有效阻住嵌套结构中的事件冒泡 --> <div class="box1" @click.self="showMsg(1)"> div1 <div class="box2" @click="showMsg(2)"> div2 </div> </div> <hr> <!-- 06. passive属性事件默认执行,无需等待事件的回调执行完毕(已失效) --> <div class="scroll" @scroll="scroll"> <ul> <li>1</li> <li>1</li> <li>1</li> <li>1</li> <li>1</li> <li>1</li> <li>1</li> <li>1</li> <li>1</li> <li>1</li> </ul> </div> <!-- 注意:事件修饰符可以叠加使用 --> </div> <script src="../js/vue.js"></script> <script> const vm = new Vue({ el:'#root', data:{ url:'http://www.baidu.com' }, methods:{ showInfo(event) { alert('正在打开百度搜索') }, showMsg(msg) { console.log(msg) alert('正在执行点击操作') }, scroll() { for(let i = 1; i < 10000; i++) { console.log('#') } console.log("over") } } }) </script>
5.3 键盘事件
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40<!-- 1. 键盘捕获事件主要分为两类:keyup和keydown,分别为键盘弹起事件和键盘按下事件 2. 如果需要通过某个按键触发事件,可以使用特定的键值修饰符 回车=>enter 删除=>delete(包括delete键和Backspace键) 退出=>esc 空格=>space 制表符=>tab 上=>up 下=>down 左=>left 右=>right 3. 对于Vue未提供别名的按键,可以使用键的原始Key值进行绑定,需要注意多单词需要使用kebab-case 4. 系统修饰键,ctrl,shift,alt,meta这些系统按键需要特殊使用 4.1 使用keyup时,系统修饰键可以搭配其他按键触发 4.2 使用keydown,可以直接触发 5. 也可以直接使用键值替代(不推荐) 6. 使用Vue.config.keyCodes.自定义键名 = 键值可以实现自定义键值 注意:可以使用组合键限制条件 --> <div id="root"> <h1>键盘捕获事件</h1> <hr> <input type="text" name="" id="" @keyup.enter="outputInfo"/> </div> <script src="../js/vue.js"></script> <script> Vue.config.keyCodes.huiche = 13 const vm = new Vue({ el:'#root', methods:{ outputInfo(event) { console.log(event.target.value) } } }) </script>
六、计算属性与侦听
6.1 计算属性
如果需要根据其他数据,经过逻辑处理后获取最后的结果,一是可以使用插值语法,使用JS表达式,这样做会使得页面臃肿,不好维护与复用。二是使用方法,在每次渲染页面的时候就会加载该方法,如果处理逻辑复杂的话会影响加载速度。以上两种方法虽然可以实现预期但是还并不简洁。
Vue提供了computed
属性用于对复杂的属性进行计算。例如:
1
2
3
4
5
6
7
8
9<div id="example"> <p> <input type="text" v-model=message /> </p> <p> result:{{ reversedMessage }} </p> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const vm = new Vue({ el: '#example', data: { message: 'Hello' }, computed: { reversedMessage: { get() { console.log('调用getter方法') return this.message.split('').reverse().join('') }, set(val) { console.log('调用setter方法', val) this.message = val } } } })
通过附加reversedMessage
属性,并添加该属性的getter
和setter
方法在,属性被调用或修改时进行调用。
getter
方法在属性调用时执行,执行后的结果会被保留在缓存中,再次调用时并不会再访问该方法,除了初次访问会调用外,当该属性所依赖的数据发生变化时也会触发该方法。
setter
方法在该属性发生变化时执行。
在控制台输出vm.reversedMessage
时,可以看到并没有再调用getter
方法。
一般情况下,用到计算属性时更多的使用获取该属性的结果,此时就可以省略setter
方法,作一种简写模式,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13const vm = new Vue({ el: '#example', data: { message: 'Hello' }, computed: { reversedMessage: function() { console.log('调用getter方法') return this.message.split('').reverse().join('') } } })
简写模式要在不需要
setter
方法的前提下使用,而且要注意reversedMessage
是vm
对象的一个属性,而不是方法
6.2 侦听
Vue提供了watch
属性用于侦听数据的变化,例如:
1
2
3
4
5<div id="example"> <h1>天气:{{ info }}</h1> <button @click="changeWeather">切换天气</button> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28const vm = new Vue({ el: '#example', data: { isHot: true }, methods: { changeWeather() { console.log("切换isHot的值") return this.isHot = !this.isHot } }, computed: { info: function() { return this.isHot ? '晴天' : '阴天' } }, watch: { isHot: { // 页面渲染时,加载handler的方法 immediate: true, // 当被监视的属性发生变化时,就会调用该函数,计算属性也可被监视 handler(newValue, oldValue) { console.log('切换isHot的值', newValue, oldValue) } } }, })
通过点击按钮,调用changeWeather
函数,改变isHot
的值。当isHot
的值发生改变时,就会触发watch
中的逻辑操作。其中watch
属性中还拥有许多属性,例如immediate
当页面渲染时,就加载handler
方法。
6.2.1 深度侦听
对于多级结构数据,如果需要实现对结构中的数据进行监听,可以对该数据对象添加deep
属性,开启深度侦听,例如:
1
2
3
4
5
6
7<div id="example"> <h1>a:{{nums.a}}</h1> <button @click="nums.a++">a+1</button> <h1>b:{{nums.b}}</h1> <button @click="nums.b++">b+1</button> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const vm = new Vue({ el: '#example', data: { nums: { a: 1, b: 2 } }, watch: { // 监听nums中多级属性的变化 // 若只需要监听多级数据中的某个属性的变化,可以使用'.'连接,例如:'nums.a',需要加引号 nums: { // deep属性可以开启Vue的watch属性对数据中多级属性变化的监听 deep: true, handler() { console.log('nums发生变化') } } }, })
6.2.2 侦听的简写
如果在watch
中没有多余的配置时,可以通过直接构建函数简写侦听过程,例如:
1
2
3
4
5<div id="example"> <h1>天气:{{ info }}</h1> <button @click="changeWeather">切换天气</button> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23const vm = new Vue({ el: '#example', data: { isHot: true }, methods: { changeWeather() { return this.isHot = !this.isHot } }, computed: { info: function() { return this.isHot ? '晴天' : '阴天' } }, watch: { // 如果被监听的对象没有其他的属性附加,可以使用简写的方法,即将处理逻辑写成一个函数 isHot: function(newValue, oldValue) { console.log('切换isHot的值', newValue, oldValue, this) } } })
6.2.3 绑定侦听
除了在Vue实例中创建watch
属性外,还可以使用$watch
为Vue对象绑定侦听。
1
2
3
4
5
6
7
8
9vm.$watch('isHot', { // 页面渲染时,加载handler的方法 immediate: true, // 当被监视的属性发生变化时,就会调用该函数 handler(newValue, oldValue) { console.log('切换isHot的值', newValue, oldValue) } })
在不需要其他配置时,可以使用简写
1
2
3
4vm.$watch('isHot', function(newValue, oldValue) { console.log('切换isHot的值', newValue, oldValue, this) })
6.3 计算属性与侦听
计算属性能够实现的,侦听也可以实现,但是侦听能够实现的,计算属性不一定能够实现,而且侦听能够实现异步操作。
当需要创建非Vue管理的函数时,一般创建箭头函数及
()=>{}
,因为箭头函数没有自己的this
可方便的访问Vue本身的内容。
computed能实现的功能,watch也能实现。watch能够实现的功能,computed却不一定能实现,watch能够实现异步操作,computed需要一个返回值,而watch只是需要实现一个方法,所以watch更加灵活。
七、样式绑定
7.1 绑定class样式
Vue除了可以对数据进行绑定,还可以为元素绑定样式。同样是使用v-bind:class
可以简写为:class
。
7.1.1 对象语法
Vue可以通过布尔值控制元素的样式是否展示,示例:
1
2
3
4<div id="root"> <div class="basic" :class="classObj">Hello World</div> </div>
1
2
3
4
5
6
7
8
9
10
11const vm = new Vue({ el: '#root', data: { classObj: { fontSize: true, bgBisque: false, textAlign: true } } })
结果渲染为:
1
2
3
4<div id="root"> <div class="basic fontSize textAlign">Hello World</div> </div>
当我们修改classObj
中的元素的值时,可以控制样式是否启用。
7.1.2 数组语法
除了可以为元素绑定对象外,还可以以数组的形式为元素添加样式。示例:
1
2
3
4
5
6<div id="root"> <div class="basic" :class="arr"> Hello World </div> </div>
1
2
3
4
5
6
7const vm = new Vue({ el: '#root', data: { arr: ['fontSize', 'textAlign'] } })
经过渲染后的结果为
1
2
3
4
5
6<div id="root"> <div class="basic fontSize textAlign"> Hello World </div> </div>
7.1.3 字符串语法
也可以直接使用字符串,示例:
1
2
3
4
5
6<div id="root"> <div class="basic" :class="fontSize"> Hello World </div> </div>
1
2
3
4
5
6
7const vm = new Vue({ el: '#root', data: { fontSize: 'fontSize' } })
经过渲染后的结果为
1
2
3
4
5
6<div id="root"> <div class="basic fontSize"> Hello World </div> </div>
7.2 绑定style样式
在Vue中我们也可以使用内联样式,通过在data中定义对象,可以将对象的值渲染为内联的style样式。这里使用v-bind:style
可以简写为:style
。示例:
1
2
3
4
5
6<div id="root"> <div class="basic" :style="styleObj"> Hello World </div> </div>
1
2
3
4
5
6
7
8
9
10const vm = new Vue({ el: '#root', data: { styleObj: { fontSize: '24px', backgroundColor: 'red' } } })
渲染后的结果为
1
2
3
4
5
6<div id="root"> <div class="basic" style="font-size:24px;background-color:red;"> Hello World </div> </div>
一般对于确定样式,需要灵活控制是否使用时,可以使用绑定class的对象语法;
若不确定样式的内容,需要添加删减时,可以使用绑定class的数组语法;
一般若样式名称不确定,可以使用绑定class的字符串语法;
八、条件渲染
8.1 v-if
Vue提供了v-if
属性可以控制元素是否展示,如果是多个结果判断的,可以使用v-if
和 v-else-if
和v-else
的组合,该组合可以理解为if-else if-else
,它的值为布尔值或可以得出布尔值的表达式。示例:
1
2
3
4
5
6
7
8
9
10<div id="root"> <h2>n: {{n}}</h2> <button @click="n++">n++</button> <h2 v-if="n === 1">Angular</h2> <h2 v-else-if="n === 2">React</h2> <h2 v-else-if="n === 3">Vue</h2> <h2 v-else>其他</h2> </div>
1
2
3
4
5
6
7const vm = new Vue({ el: '#root', data: { n: 1 } })
可以通过修改n的值来显示不同的li
标签。
如果需要控制是否显示某一段代码,还可以使用<template></template
标签进行包裹。示例:
1
2
3
4
5
6<template v-if="result"> <h2>Angular</h2> <h2>React</h2> <h2>Vue</h2> </template>
1
2
3
4
5
6
7const vm = new Vue({ el: '#root', data: { result: false } })
8.2 v-show
v-show
与v-if
有着相同的效果,也可以根据值的布尔值来控制元素是否显示,但不同的是v-show
一般只是简单的切换元素的display
属性,同时也不支持多结果判断和组件。示例:
1
2
3
4<div id="root"> <h2 v-show="result">{{desc}}</h2> </div>
1
2
3
4
5
6
7
8const vm = new Vue({ el: '#root', data: { result: false, desc: '标题1' } })
九、列表渲染
9.1 v-for
Vue提供了v-for
属性控制元素的循环输出,该元素可用于渲染对象、数组以及字符串,也可以控制循环的次数。
9.1.1 渲染数组
1
2
3
4
5
6
7<div id="root"> <h2>个人信息</h2> <ul> <li v-for="(person, index) in persons">{{person.name}}-{{person.age}}-{{index}}</li> </ul> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23const vm = new Vue({ el: '#root', data: { persons: [ { id: 'S001', name: 'Tom', age: 18 }, { id: 'S002', name: 'Jack', age: 20 }, { id: 'S003', name: 'Mark', age: 23 } ], } })
渲染结果为
- Tom-18-0
- Jack-20-1
- Mark-23-2
v-for
可以携带2个参数,第一个参数为对象的值,对于数组来说,第二个参数默认为该项的索引值。
9.1.2 渲染对象
1
2
3
4
5
6
7<div id="root"> <h2>学生信息</h2> <ul> <li v-for="(v, k) in student">{{k}}-{{v}}</li> </ul> </div>
1
2
3
4
5
6
7
8
9
10
11const vm = new Vue({ el: '#root', data: { student: { grade: '高三', class: '一班', name: '张三' }, } })
渲染效果为
- grade-高三
- class-一班
- name-张三
其中k代表了对象中的属性值也就是键值,针对于对象,还可以携带第三个参数,及对象的索引值,例如v-for="(v, k, i) in student"
,其中i
为索引值。
9.1.3 渲染数组
1
2
3
4
5
6
7<div id="root"> <h2>字符信息</h2> <ul> <li v-for="(c, i) in desc">{{c}}-{{i}}</li> </ul> </div>
1
2
3
4
5
6
7const vm = new Vue({ el: '#root', data: { desc: 'Hello World' } })
渲染结果为
- H-0
- e-1
- l-2
- l-3
- o-4
- -5
- W-6
- o-7
- r-8
- l-9
- d-10
其中i
的值为该项的索引值。
9.2 v-for的key
在v-for
属性中还伴随着另一个属性,为每一次循环的元素绑定一个key值,及v-bind:key="key"
可以简写为:key
。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。因此如果是循环一个对象最好使用一个唯一主键作为key值,这样可以将key值与数据进行绑定,避免拥有子元素时更新发生错位的情况。
例如,我们在每次循环时为循环内容添加一个input
标签,并创建一个点击按钮可以新增一个元素。
1
2
3
4
5
6
7
8
9
10
11<div id="root"> <h2>个人信息</h2> <button @click="add">点击添加人员</button> <ul> <li v-for="(person, index) in persons" :key="index"> {{person.name}}-{{person.age}}-{{index}} <input type="text"> </li> </ul> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34const vm = new Vue({ el: '#root', data: { persons: [ { id: 'S001', name: 'Tom', age: 18 }, { id: 'S002', name: 'Jack', age: 20 }, { id: 'S003', name: 'Mark', age: 23 } ] }, methods: { add() { const p = { id: 'S004', name: 'Jary', age: 40 } this.persons.unshift(p) } } })
使用默认索引作为key值,当在input
标签中输入内容后,再添加新的元素,会发现input
标签并没有与前边的元素绑定会出现错位。
这里就引入了Vue在更新元素内容时的虚拟DOM对比原理,根据Vue的虚拟DOM对比算法,对于新增的元素,会根据KEY值找到原有的对象,进而对比元素的内容,对于不同的地方进行更新。因此如果使用索引作为key值,在已有数据中插入数据时可能会对界面显示造成错位。
所以可以使用对象的唯一主键作为key值,例如:<li v-for="(person, index) in persons" :key="person.id">
便可以避免这个问题,具体原理如下图
渲染效果如下
9.3 列表过滤
当我们想将一个数组过滤并保持源数组不变,可以通过计算属性和侦听来实现。
9.3.1 侦听
1
2
3
4
5
6
7
8
9
10<div id="root"> <h2>个人信息</h2> <input type="text" placeholder="请输入关键词" v-model="keyWord"> <ul> <li v-for="(person, index) in filPersons" :key="person.id"> {{person.name}}-{{person.age}}-{{person.sex}} </li> </ul> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40const vm = new Vue({ el: '#root', data: { keyWord: '', persons: [ { id: 'S001', name: '马冬梅', age: 18, sex: '女' }, { id: 'S002', name: '周冬雨', age: 20, sex: '女' }, { id: 'S003', name: '周杰伦', age: 23, sex: '男' } ], filPersons: [] }, watch: { keyWord: { // 页面渲染时,加载handler的方法 immediate: true, // 当被监视的属性发生变化时,就会调用该函数,计算属性也可被监视 handler(val) { this.filPersons = this.persons.filter((p)=>{ return p.name.indexOf(val) !== -1; }); } } }, })
使用侦听时,我们可以监控输入框的变化,因此定义keyWord
属性用于绑定输入内容,当开启侦听后,调用filter
方法筛选符合的内容。
这里需要注意
indexOf
方法,当参数为空值时,返回值为0
为了不影响原有数组的内容,可以重新定义一个数组,用于接收筛选后的结果。
immediate
属性是必须要添加的,当页面进行渲染时,若还没有开始侦听,那么filPersons
则为空,无法显示结果。
9.3.2 计算属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35new Vue({ el: '#root', data: { keyWord: '', persons: [ { id: 'S001', name: '马冬梅', age: 18, sex: '女' }, { id: 'S002', name: '周冬雨', age: 20, sex: '女' }, { id: 'S003', name: '周杰伦', age: 23, sex: '男' } ] }, computed: { filPersons() { return this.persons.filter((p)=>{ return p.name.indexOf(this.keyWord) !== -1 }) } } })
使用计算属性则要相对简单些,我们可以直接计算出来filPersons
的值,这样就省略了对输入框的监听。
9.4 列表排序
同样的,我们如果需要多数组的某一个值进行排序,也可以使用计算属性进行处理。示例:
1
2
3
4
5
6
7
8
9
10
11
12
13<div id="root"> <h2>个人信息</h2> <input type="text" placeholder="请输入关键词" v-model="keyWord"> <button @click="sortType = 2">升序排序</button> <button @click="sortType = 1">降序排序</button> <button @click="sortType = 0">原顺序</button> <ul> <li v-for="(person, index) in filPersons" :key="person.id"> {{person.name}}-{{person.age}}-{{person.sex}} </li> </ul> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46const vm = new Vue({ el: '#root', data: { keyWord: '', sortType: 0, persons: [ { id: 'S001', name: '马冬梅', age: 18, sex: '女' }, { id: 'S002', name: '周冬雨', age: 25, sex: '女' }, { id: 'S003', name: '周杰伦', age: 23, sex: '男' } ] }, computed: { filPersons() { // 对数组进行筛选 const arr = this.persons.filter((p)=>{ return p.name.indexOf(this.keyWord) !== -1 }) // 对数组进行排序 if (this.sortType) { arr.sort((p1, p2)=>{ return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age }) } return arr; } } })
在上一个案例的基础上,我们增加了对筛选后的结果对年龄进行排序,将结果作为返回值。
9.5 数据监测原理
通过在控制台打印Vue对象,可以看到通过数据代理形成的_data
对象中的每个属性都具有Getter/Setter方法,而且每个属性的子属性也都具有Getter/Setter方法,这是Vue实现数据监测的根本,每个层级的属性变化时都都会触发setter方法,换而言之,只有数据被_data
代理才能实现动态更新。
实现一个一层的数据监测,示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25let arr = [{name: 'Tom', age: 18}]; // 模拟Vue的封装 const vm = {}; vm._data = new Observer(arr); function Observer(obj) { // 获取对象的key值 const keys = Object.keys(obj); // 遍历key值 keys.forEach((k)=>{ Object.defineProperty(this, k, { get() { console.log('获取对象值'); return obj[k]; }, set(val) { console.log('设置对象值'); obj[k] = val; } }); }); }
Vue底层也是通过这种方法为所有的属性绑定Getter/Setter方法,这种过程也被称为数据劫持,只是Vue底层实现了多级的数据劫持。
因此针对数组,如果需要变更其内容,就不能直接操作元素,示例:
1
2
3
4
5
6
7<div id="root"> <button @click="modify">修改元素内容</button> <ul> <li v-for="(v, k) in students" :key="v.id">{{ v.name }}</li> </ul> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31const vm = new Vue({ el: '#root', data: { students: [ { id: 'S001', name: '马冬梅', age: 18, sex: '女' }, { id: 'S002', name: '周冬雨', age: 25, sex: '女' }, { id: 'S003', name: '周杰伦', age: 23, sex: '男' } ] }, methods: { modify() { this.persons[0] = {id: 'S001', name: '周迅', age: 40, sex: '女'} } } })
点击按钮后,数组的第一个元素并没有被修改,就是因为上述的原因,修改后的数据无法触发元素的setter方法所以无法被Vue代理,因此数据无法更新。
这里Vue提供了数组的变更方法,使用这些方法就可以使Vue更新他的数据并实现代理。这些方法包含
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
我们在控制台对students
对象调用这些方法,观察结果。
除了上述的方法,还可以使用非变更的方法,及filter()
,concat()
,slice()
方法,这些方法不会变更原始数组,只是后返回一个新的数组,就可以用新的数组直接替换旧数组,一样可以触发动态更新数组。
9.5 Vue.set方法
根据上一节所阐述的内容,单纯的操作data对象是无法实现数据的动态更新的,因为直接操作的内容无法被Vue代理。因此Vue提供了Vue.set
方法,也可以使用全局方法vm.$set
该方法可以为某一个属性添加新的属性。示例:
1
2
3
4
5
6
7
8<div id="root"> <h2>姓名:{{student.name}}</h2> <h2>年级:{{student.grade}}</h2> <h2>班级:{{student.class}}</h2> <h2 v-if="student.age">年龄:{{student.age}}</h2> <button @click="addAge">点击添加年龄</button> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const vm = new Vue({ el: '#root', data: { student: { grade: '高三', class: '一班', name: '张三' } }, methods: { addAge() { // Vue.set(vm.student, 'age', 18); vm.$set(this.student, 'age', 18); } } })
set方法使用三个参数,第一个参数为需要绑定的对象,第二个对象为待绑定的属性,第三个参数为绑定的值。
需要注意的是该方法不能为vm的根对象新增属性,例如this.$set(vm, ‘age’, 18)
十、表单数据的收集
表单数据可以使用v-model
进行绑定,获取每个表单的value
值。示例:
已阅读条款
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34<div id="root"> <form> 账号:<input type="text" v-model.trim="personInfo.account"> <br> 密码:<input type="password" v-model="personInfo.password"> <br> 性别: <input type="radio" name="sex" value="male" v-model="personInfo.sex">男 <input type="radio" name="sex" value="female" v-model="personInfo.sex">女 <br> 年龄:<input type="number" v-model.number="personInfo.age"> <br> 爱好: <input type="checkbox" value="study" v-model="personInfo.hobby">学习 <input type="checkbox" value="game" v-model="personInfo.hobby">游戏 <input type="checkbox" value="run" v-model="personInfo.hobby">跑步 <br> 所在城市: <select v-model="personInfo.city"> <option value="">请选择城市</option> <option value="beijing">北京</option> <option value="shanghai">上海</option> <option value="guangzhou">广州</option> </select> <br> 简介: <textarea v-model.lazy="personInfo.info"></textarea> <br> <input type="checkbox" v-model="personInfo.checked">已阅读条款 <br> <button>提交</button> </form> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const vm = new Vue({ el: '#root', data: { personInfo: { account: '', password: '', sex: '', age: '', hobby: [], city: '', info: '', checked: '' } }, })
文本输入框可根据输入的值进行双向绑定。单选按钮可绑定value
的值,及勾选’男‘时,sex的值为male
。多选框则会将value
值添加到数组中,这里要在data
初始化为数组。
这里需要说明的上述案例的修饰符
lazy
:当输入框失去焦点后再进行数据绑定number
:将输入的数据转为数字类型,常与type="number"
同时使用,确保输入的数据为数字类型trim
:去除字符串前后的空格,在输入框中输入空格将会自动删除空格
十一、指令
11.1 内置指令
Vue除了上述章节介绍的v-bind
,v-model
,v-on
等指令,还提供了v-text
,v-html
,v-once
,v-clock
,v-pre
指令。
11.1.1 v-text
v-text
用于将对象的内容绑定在元素中,与插值语法类似,但是该指令会替换元素原有的值,无法拼接字符串,不如插值语法使用灵活。
1
2
3
4
5<div id="root"> <h2>name:{{ name }}</h2> <h2 v-text="name">name:Jack</h2> </div>
1
2
3
4
5
6
7new Vue({ el: '#root', data: { name: 'Tom' } })
使用插值语法会显示name:Tom
的效果,但是使用v-text
指令,会直接覆盖原有的内容,及只会显示Tom
。
11.1.2 v-html
v-html
用于将对象的内容绑定在元素中,可以解析标签,但是该指令会替换元素原有的值,无法拼接字符串。
1
2
3
4<div id="root"> <h2 v-html="html"></h2> </div>
1
2
3
4
5
6
7new Vue({ el: '#root', data: { html: '<a href>点击跳转</a>' } })
在对象中使用的a标签会被解析到DOM中。
11.1.3 v-cloak
v-cloak
常与CSS搭配使用,用于当Vue加载缓慢时避免未经渲染的模板直接呈现在页面上。
1
2
3
4<div id="root"> <h2 v-cloak>{{ name }}</h2> </div>
1
2
3
4
5
6<style> [v-cloak] { display: none; } </style>
1
2
3
4
5
6
7new Vue({ el: '#root', data: { name: 'Tom' } })
v-cloak
指令会在Vue加载完毕后,把该属性删除,因此在Vue加载前,可以使用CSS将该元素隐藏,等Vue渲染后,就可以直接最后的结果。
11.1.4 v-once
v-once
绑定的元素包含的对象只会渲染一次,若修改该对象的值,不会引起该指令绑定元素的结果。
1
2
3
4
5
6<div id="root"> <h2 v-once>n初始化值为:{{ n }}</h2> <h2>n:{{ n }}</h2> <button @click="n++">点击n+1</button> </div>
1
2
3
4
5
6
7new Vue({ el: '#root', data: { n: 1 } })
当修改n值时,第一个h2标签的值不会发生改变,第二个h2标签会根据点击时间逐次加1。
11.1.5 v-pre
v-pre
指令会禁用对DOM元素的渲染。
1
2
3
4
5
6<div id="root"> <h2 v-once>n初始化值为:{{ n }}</h2> <h2 v-pre>n:{{ n }}</h2> <button @click="n++">点击n+1</button> </div>
第二个h2标签,将无法再被Vue渲染,会保留原始的状态。
11.2 自定义指令
除了上述的Vue内置的指令外,还可以通过自定义指令来实现一些特殊的操作,基础语法为v-[指令名称]
。例如:
该示例实现了两个功能,点击按钮后将n*10,第二个输入框实现在页面刷新时自动获取焦点,并将n值填充在输入框中。
1
2
3
4
5
6
7
8<div id="root"> <h2>n={{ n }}</h2> <h2>n*10=<span v-big="n"></span></h2> <button @click="n++">点击n+1</button> <hr> <input type="text" v-focus:value="n"> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26const vm = new Vue({ el: '#root', data: { n: 1 }, directives: { big: function (element, binding) { element.innerText = binding.value * 10 }, focus: { bind(element, binding) { console.log('DOM元素与指令绑定时执行') element.value = binding.value; }, inserted(element, binding) { console.log('DOM元素开始渲染时执行') element.focus() }, update(element, binding) { console.log('当指令所在模板更新时执行') element.value = binding.value; } } } })
使用directives
属性定义自定义指令,自定义指令有两种写法一种是函数方法,另一种是对象写法。
-
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 -
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 -
update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 。
每个方法都拥有两个参数:
element
:指令所绑定的元素,可以用来直接操作 DOM。binding
:一个对象,包含以下 property:name
:指令名,不包括v-
前缀。value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
函数方式简化了三个方法,但是对于一些需要操作DOM的操作,就需要使用到
inserted
方法。
将自定义指令写在模板之中后,只能服务于该模板,其他模板就无法调用该指令,所以Vue提供了全局指令的用法。示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Vue.directive('focus', { bind(element, binding) { console.log('DOM元素与指令绑定时执行') element.value = binding.value; }, inserted(element, binding) { console.log('DOM元素开始渲染时执行') element.focus() }, update(element, binding) { console.log('当指令所在模板更新时执行') element.value = binding.value; } })
如果需要简化使用函数写法,将对象替换为函数即可。
这里需要注意的是,全局指令需要定义在创建实例之前,该指令可以被所有的模板调用
十二、过滤器
过滤器可以对绑定数据进行处理,一般使用在插值语法中,示例:{{ message | filter }}
,也可以用在v-bind
中,示例: <div v-bind:id="message | filter"></div>
。
示例:
1
2
3
4
5
6
7<div id="root"> <h2>当前时间:{{ time }}</h2> <h2>当前时间:{{ time | timeFormat }}</h2> <h2>当前时间:{{ time | timeFormat('YYYY年MM月DD日') }}</h2> <h2>当前时间:{{ time | timeFormat('YYYY年MM月DD日') | slice }}</h2> </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const vm = new Vue({ el: '#root', data: { time: 1665395277123 }, filters: { timeFormat: function(value, format) { return dayjs(value).format(format); }, slice: function (value) { return value.slice(0, 4); } } });
定义过滤器的第一个参数固定为绑定的值,且可以传递参数,第二个参数开始就为过滤器所定义的参数,且过滤器可以串联,后一个过滤器的参数是前一个过滤器的返回值。
在多组件开发中,一个组件的过滤器无法被另一个组件调用,但Vue提供了全局的过滤器,这种过滤器就可以被所有的组件调用。
示例:
1
2
3
4Vue.filter('timeFormat', function(value, format) { return dayjs(value).format(format); })
这里要注意的是,全局的过滤器需要被写在初始化实例前。
十三、生命周期
Vue的实例从创建到销毁,有自己的生命周期,Vue的生命周期分为4个阶段,8个方法,如下图
13.1 实例初始化
在初始化阶段,拥有两个方法,实例加载前和实例加载后,在此期间会进行数据代理和数据监测,及产生_data
。
在Vue中提供了两个方法,beforeCreate()
用于在初始化前调用,此时的_data
属性还没有进行加载,created()
用于初始化之后调用,此时就可以调用Vue实例中的属性和方法。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const vm = new Vue({ el: '#root', data: { }, methods: { }, beforeCreate() { console.log('beforeCreate:初始化生命周期,在初始化前执行', this); debugger }, created() { console.log('created:完成初始化步骤,实现了数据代理和方法', this); debugger }, })
加入debugger断点,查看Vue的实例对象
通过两个阶段输出的this对象,可以看到created
阶段所有的数据和方法就已经被代理完毕。
13.2 挂载阶段
挂载阶段Vue会生成虚拟DOM,并将虚拟DOM渲染到页面上。该阶段也提供了两个方法,beforeMount()
方法执行时,Vue产生了虚拟DOM但还未在页面进行渲染,所以会得到最原始的界面,mounted()
方法执行时,就会将虚拟DOM渲染在页面上,在此期间就可以进行开启定时器、发送网络请求等操作,而且此时操作DOM是有效的。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const vm = new Vue({ el: '#root', data: { }, methods: { }, beforeMount() { console.log('beforeMount:完成虚拟DOM的创建,但未开始渲染页面,在此期间操作的DOM皆无效', this) debugger }, mounted() { console.log('mounted:完成页面的渲染,可以在此阶段进行DOM的操作(不建议),启动定时器和发送请求等操作', this) debugger }, })
在
beforeMount
方法中操作DOM是无效的,因为会被虚拟DOM直接覆盖。
效果如图
13.3 更新阶段
当数据发生了变更,就会触发该阶段,主要分为计算属性和重新渲染模板两步。该阶段也包含两个函数,beforeUpdate()
调用该函数时,Vue完成了重新计算属性的工作,但是页面还未重新渲染,updated()
调用该函数时,Vue已完成了页面的渲染,保持了属性和页面的一致性。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const vm = new Vue({ el: '#root', data: { }, methods: { }, beforeUpdate() { console.log('beforeUpdate:更新数据时触发,此时数据已被更新,但是页面还未渲染', this) debugger }, updated() { console.log('updated:更新后页面渲染结束', this) debugger }, })
13.4 销毁阶段
当用户调用了vm.$destroy()
方法后,就会进入销毁阶段。该阶段包含两个方法,beforeDestroy()
该方法在Vue实例开始销毁前执行,一般在此阶段停止定时器,取消订阅和解除自定义时间的绑定,destroy()
方法调用时Vue实例就已被完全销毁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const vm = new Vue({ el: '#root', data: { }, methods: { }, beforeDestroy() { console.log('beforeDestroy:销毁实例对象前触发,用于在销毁前关闭定时器、取消订阅消息、解绑自定义事件等操作', this) debugger }, destroyed() { console.log('destoryed:销毁实例对象', this) debugger }, })
两个阶段输出的this对象,可以看到created
阶段所有的数据和方法就已经被代理完毕。
最后
以上就是任性犀牛最近收集整理的关于01.【Vue】Vue2基础操作一、Vue二、Vue实例创建三、Vue基础语法四、数据代理五、事件六、计算属性与侦听七、样式绑定八、条件渲染九、列表渲染十、表单数据的收集十一、指令十二、过滤器十三、生命周期的全部内容,更多相关01.【Vue】Vue2基础操作一、Vue二、Vue实例创建三、Vue基础语法四、数据代理五、事件六、计算属性与侦听七、样式绑定八、条件渲染九、列表渲染十、表单数据内容请搜索靠谱客的其他文章。
发表评论 取消回复