Vue介绍

  • 作者: 尤雨溪
  • Vue是一款渐进式JavaScript框架,通过简洁的API提供高效的数据绑定和灵活的组件系统。
    • 渐进式:声明式渲染 — 组件系统 — 客户端路由 — 集中式状态管理 — 项目构建
  • Vue 的核心库只关注视图层
  • 官方文档

Vue安装

1
2
3
4
5
6
7
<!-- 兼容性:  Vue 不支持 IE8 及以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性。但它支持所有兼容 ECMAScript 5 的浏览器。 -->

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

声明式渲染

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 1、需要提供标签用于填充数据 --> 
<div id="app">
<!-- 在标签中我们使用插值语法的形式 即 {{}} 进行填充数据 -->
<div>{{msg}}</div>
<!-- 插值表达式支持表达式运算 -->
<div>{{1 + 2}}</div>
<div>{{msg + '---' + 123}}</div>
</div>
<!-- 2、引入vue.js库文件 -->
<script type="text/javascript" src="js/vue.js"></script>
<!-- 3、使用vue的语法做功能 -->
<script type="text/javascript">
// new Vue() 创建一个Vue的实例
// 在构造函数中以对象的形式做一些配置
new Vue({
// el: 元素的挂载位置(值可以是CSS选择器或者DOM元素)
el: '#app',
// data:模型数据(值是一个对象)
data: {
msg: 'HelloWorld'
}
})
</script>

Vue模板语法

  • 插值表达式
  • 指令
  • 事件绑定
  • 属性绑定
  • 样式绑定
  • 分支循环结构

插值表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
插值表达式是Vuejs中实现数据渲染到页面方式,不用进行dom操作可以直接将数据从模型到视图。

插值表达式就是{{ }},括号里面可以执行简单的js代码 。将模型变量中的属性直接放到插值表达式中可以实现数据渲染到页面的效果。

// 插值表达式支持简单的语法
常规变量 number,string,boolean等
四则运 +,-,*,/,%
逻辑运算 或与非
三目运算
全局函数 {{ Math.random() }}
对象 {{ {name:“abc”,age:13} }}
数组 {{ [,5,6,7,8,9] }}

// 为什么插值表达式只支持简单的支持简单的js语法
防止xss攻击
xss:Cross Site Scripting,跨域脚本攻击;
危害:盗取cookie,破坏页面结构;
防止csrf攻击
csrf:Cross-site request forgery,跨站请求伪造
危害:在用户不知情的情况下,模拟用户的操作,购买商品,发邮件,甚至转账

指令

  • 指令的本质就是 Vue 提供的特殊自定义属性
  • 指令的格式:以v-开头

常用基本指令

v-cloak

插值表达式存在的问题:“闪烁”
代码加载的时候先加载HTML 把插值语法当做HTML内容加载到页面上 当加载完js后才把插值语法替换掉 所以我们会看到闪烁问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<style type="text/css">
/* 隐藏带有v-cloak属性的标签 */
[v-cloak]{
display: none;
}
</style>

<div id="app">
<!-- 给标签添加 v-cloak 属性,
在数据渲染完之后,v-cloak 属性会被自动移除,
v-cloak一旦移除也就没有这个属性了,属性选择器就选择不到该标签,也就是对应的标签会变为可见
-->
<div v-cloak >{{msg}}</div>
</div>
v-text
  • 作用与插值表达式类似,但是没有闪烁问题
  • 相比插值表达式更加简洁
  • 如果数据中有HTML标签会将html标签一并输出
1
<p v-text="text"></p>
v-html
  • 用法和v-text 相似 但是他可以将HTML片段填充到标签中
  • 可能有安全问题, 一般只在可信任内容上使用 v-html,永不用在用户提交的内容上
  • 它与v-text区别在于v-text输出的是纯文本,浏览器不会对其再进行html解析,但v-html会将其当html标签解析后输出。
1
<p v-html="html"></p>
v-pre
  • 显示原始信息跳过编译过程
  • 跳过这个元素和它的子元素的编译过程。
  • 一些静态的内容不需要编译加这个指令可以加快渲染
1
2
3
<span v-pre>{{ msg }}</span>
<!-- 页面直接显示 -->
{{ msg }}
v-once
  • 只编译一次
  • 显示内容之后不再具有响应式功能
1
2
<!-- 即使data里面定义了msg 后期我们修改了 仍然显示的是第一次data里面存储的数据  -->
<span v-once>{{ msg }}</span>

数据的响应式:数据的变化导致页面内容的变化(数据驱动视图

v-model(双向数据绑定)

使用场景:限制在 <input><select><textarea>components中使用

1
2
3
4
5
6
7
<div id="app">
<div>{{msg}}</div>
<div>
<!-- 当输入框中内容改变的时候, 页面上的msg 会自动更新 -->
<input type="text" v-model='msg'>
</div>
</div>

双向数据绑定

  • 当数据发生变化的时候,视图也就发生变化
  • 当视图发生变化的时候,数据也会跟着同步变化
  • 双向绑定底层原理: Object.defineProperty() 的用法

可以通过v-bind和v-on实现v-model的功能

1
<input v-bind:value="msg" v-on:input="msg=$event.target.value">

事件绑定

v-on指令(事件绑定)
  • 事件函数的调用方式

  • 事件函数写法

  • 事件传参

1
2
3
4
5
6
7
8
<input type='button' v-on:click="fn($event)"/>  // 如果事件绑定函数调用,那么可以显式传递$event事件对象
<input type='button' @click="fn"/>
// 简写 @ 如果事件直接绑定函数名称,那么默认会传递事件对象作为事件函数的第一个参数
methods: {
fn(e){}
}

.native // 监听根元素的原生事件
事件修饰符

在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求。
Vue 不推荐我们操作DOM 为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 阻止事件冒泡 -->
<a v-on:click.stop="handle">跳转</a>
<!-- 阻止默认事件 -->
<a v-on:click.prevent="handle">跳转</a>

<!-- 修饰符可以串联 即阻止冒泡也阻止默认事件 -->
<a v-on:click.stop.prevent="handle"></a>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="handle">...</div>

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止 所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。
按键修饰符

在做项目中有时会用到键盘事件,在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符

1
<input v-on:keyup.13="submit">
按键别名
1
2
3
4
5
6
7
8
9
10
11
.enter =>    enter键
.tab => tab键
.delete (捕获“删除”和“退格”按键) => 删除键
.esc => 取消键
.space => 空格键
.up => 上
.down => 下
.left => 左
.right => 右

<input @keyup.enter="submit"> <!--缩写形式-->
修饰键
1
2
3
4
5
6
7
8
9
.ctrl
.alt
.shift
.meta

<!-- Alt + C -->
<input @keyup.alt.67="clear">
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>

与按键别名不同的是,修饰键和 keyup 事件一起用时,事件引发时必须按下正常的按键。换一种说法:如果要引发 keyup.ctrl,必须按下 ctrl 时释放其他的按键;单单释放 ctrl 不会引发事件。

1
2
3
4
5
<!-- 按Alt + C -->
<input @keyup.alt.67="clear">

<!-- 按Alt + 任意键 -->
<input @keyup.alt="other">
自定义按键修饰符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<!-- 预先定义了keycode 116(即F5)的别名为f5,因此在文字输入框中按下F5,会触发prompt方法 -->
<input type="text" v-on:keydown.f5="prompt()">
</div>

<script>
// 定义keycode 116(即F5)的别名为f5
Vue.config.keyCodes.f5 = 116;

let app = new Vue({
el: '#app',
methods: {
prompt: function() {
alert('我是 F5!');
}
}
});
</script>

属性绑定

v-bind指令(绑定指令)

v-bind指令被用来响应地更新 HTML 属性

1
2
3
4
5
6
7
<!-- 绑定href属性 -->
<a v-bind:href='url'>跳转</a>
<!-- 绑定src属性 -->
<img v-bind:src="imgUrl">

<!-- 简写 -->
<a :href='url'>跳转</a>
class类名绑定
对象语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<style type="text/css">
.red {
color:red;
}
.blue {
color:blue;
}
.yellow {
color:yellow;
}
</style>

<div v-bind:class="{red: isActive,blue: isError}">测试样式</div>
......
data: {
isActive: true,
isError: true,
isFlag: true
}
......
数组语法
1
<div v-bind:class="['red', 'blue']">测试样式</div>
对象绑定和数组绑定的区别
  • 绑定对象的时候 对象的属性 即要渲染的类名 对象的属性值对应的是 data 中的数据
  • 绑定数组的时候数组里面存的是data 中的数据
混合使用
1
<div v-bind:class="['red', 'blue', {yellow: isFlag}]">测试样式</div>
默认样式
1
2
<!--- class是默认样式,不会被:class里面覆盖 -->
<div class="red blue" :class="['yellow']"></div>

样式绑定

对象语法
1
2
3
4
5
6
7
<!-- 
CSS 属性名如果是类似'font-size'这种属性,需要加引号,或者使用驼峰式 (camelCase)
-->
<div v-bind:style='{
"font-size": "12px",
backgroundColor: "gray"
}'></div>
数组语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 最终的样式是objStyles和overrideStyles进行对象合并后的值 -->
<div v-bind:style='[objStyles, overrideStyles]'></div>
<script>
......
data: {
objStyles: {
border: '1px solid green',
width: '200px',
height: '100px'
},
overrideStyles: {
border: '5px solid orange',
backgroundColor: 'blue'
}
}
......
</script>

分支循环结构

条件绑定

使用场景

  • 通过条件判断展示或者隐藏某个元素或者多个元素
  • 进行两个视图之间的切换
v-if
1
2
3
4
5
<!-- v-if可以和v-else-if、v-else配合使用,类似js中的if、else if、else -->
<div v-if='score>=90'>优秀</div>
<div v-else-if='score<90&&score>=80'>良好</div>
<div v-else-if='score<80&&score>60'>一般</div>
<div v-else>比较差</div>
v-show
1
2
<!-- 用法跟v-if类似,但是只能单独使用,不能跟v-else-if和v-else配合使用 -->
<div v-show='flag'>测试v-show</div>
v-if和v-show的区别
  • v-if是动态的向DOM树内添加或者删除DOM元素

    • 惰性渲染:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块
    • v-if切换有一个局部编译/卸载的过程,切换过程中条件块内的事件监听器和子组件适当地被销毁和重建
  • v-show控制元素是否显示(控制css的display属性)

    • 不管初始条件是什么,元素总是会被渲染
    • v-show只编译一次,后面其实就是控制css
  • v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

循环绑定
v-for
  • 遍历数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<ul>
<!--
fruits是要遍历的数组
item是遍历的每一条数据,这是个变量,名字随便取
in是关键字,可以用of代替
-->
<li v-for='item in fruits'>{{item}}</li>
<!--
可以添加第二个遍历的参数:index 索引值,名字也是随便取
-->
<li v-for='(item, index) in fruits'>{{item + '---' + index}}</li>

<!-- 遍历对象数组 -->
<li v-for='(item, index) in myFruits'>
<span>{{item.ename}}</span>
<span>-----</span>
<span>{{item.cname}}</span>
</li>

</ul>
  • key的作用:帮助Vue区分不同的元素,从而提高性能

    • key用来给每个节点做一个唯一标识 有id用id,没有id用索引(万不得已) 重复会报警告
    • key的作用主要是为了高效的更新虚拟DOM
1
2
3
4
5
6
7
8
9
<!-- 使用索引作key -->
<li v-for='(item, index) in fruits' :key='index'>{{item + '---' + index}}</li>

<!-- 使用id作key -->
<li :key='item.id' v-for='(item, index) in myFruits'>
<span>{{item.ename}}</span>
<span>-----</span>
<span>{{item.cname}}</span>
</li>
  • 遍历对象
1
2
3
4
5
6
<!-- 变量v,k,i代表什么:
v 代表对象的value
k 代表对象的key
i 代表索引值
-->
<div v-for='(v,k,i) in obj'>{{v + '---' + k + '---' + i}}</div>
v-for和v-if一起使用
  • 不推荐同时使用 v-ifv-for
  • v-ifv-for 一起使用时,v-for 具有比 v-if 更高的优先级。如果每一次都需要遍历整个数组,将会影响速度,尤其是当只需要渲染很小一部分的时候。

表单输入绑定

  • 常见表单元素:
    • input 输入框
    • textarea 文本域输入框
    • radio 单选框
    • checkbox 复选框
    • select 下拉菜单
  • textarea和input用法一致

radio

  1. 每个单选框必须要有各自的value
  2. 每个单选框都需要通过v-model 绑定同一个值(data中的属性)
  • v-model 绑定的数据默认可以是空,也可以是其中一个单选框的value
    • 如果默认是其中一个单选框的value,则该单选框默认选中
  1. 当点击选中某一个单选框的时候, v-model绑定的数据就会变成当前的 value值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<input type="radio" id="male" value="1" v-model='gender'>
<label for="male"></label>

<input type="radio" id="female" value="2" v-model='gender'>
<label for="female"></label>

<script>
new Vue({
data: {
// 默认会让当前的 value 值为 2 的单选框选中
gender: 2,
},
})
</script>

checkbox

  • 用法跟radio类似,区别是checkbox中v-model必须绑定一个数组
  1. 每个复选框必须要有各自的value
  2. 每个复选框都需要通过v-model 绑定同一个值(data中的属性)
  • v-model 绑定的数据默认可以是空,也可以是其中某些复选框的value
    • 如果默认有值,则默认选中对应value的复选框
  1. 当切换复选框的时候, v-model绑定的数据(该数组)就会添加或删除当前的 value值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div>
<span>爱好:</span>
<input type="checkbox" id="ball" value="1" v-model='hobby'>
<label for="ball">篮球</label>
<input type="checkbox" id="sing" value="2" v-model='hobby'>
<label for="sing">唱歌</label>
<input type="checkbox" id="code" value="3" v-model='hobby'>
<label for="code">写代码</label>
</div>
<script>
new Vue({
data: {
// 默认会让当前的 value 值为 2 和 3 的复选框选中
hobby: ['2', '3'],
},
})
</script>

select

  1. 每个option必须要有各自的value
  2. 给select通过v-model 绑定一个值
  • v-model 绑定的数据默认可以是空,也可以是其中一个option的value
    • 如果默认是其中一个option的value,则该option默认选中
  1. 当点击选中某一个option的时候, v-model绑定的数据就会变成当前的value值
  • 如果select是多选下拉框,则v-model需要绑定一个数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<select v-model='occupation'>
<option value="0">请选择职业...</option>
<option value="1">教师</option>
<option value="2">软件工程师</option>
<option value="3">律师</option>
</select>

<!-- multiple属性表示下拉框是多选 -->
<select v-model='occupation2' multiple>
<option value="0">请选择职业...</option>
<option value="1">教师</option>
<option value="2">软件工程师</option>
<option value="3">律师</option>
</select>

<script>
new Vue({
data: {
occupation: '2',
// 默认会让当前的 value 值为 2 和 3 的下拉框选中
occupation2: ['2', '3']
},
})
</script>

表单修饰符

  • .number 转换为数值

    • 注意点:
      • 当开始输入非数字的字符串时,因为Vue无法将字符串转换成数值,
      • 所以属性值将实时更新成相同的字符串。即使后面输入数字,也将被视作字符串。
  • .trim 自动过滤用户输入的首尾空白字符

    • 只能去掉首尾的 不能去除中间的空格
  • .lazy 将input事件切换成change事件

    • .lazy 修饰符延迟了同步更新属性值的时机。即将原本绑定在 input 事件的同步逻辑转变为绑定在 change 事件上
1
2
3
4
5
6
7
8
<!-- 自动将用户的输入值转为数值类型 -->
<input v-model.number="age" type="number">

<!--自动过滤用户输入的首尾空白字符 -->
<input v-model.trim="msg">

<!-- 在"change"时而非"input"时更新 -->
<input v-model.lazy="msg" >

表单验证

1
2
3
4
5
<!-- 用表单提交事件 -->
<form @submit="checkForm"></form>

<!-- 禁用表单默认的校验规则 -->
<from novalidate="true"></from>

自定义指令 directive

第一个参数:指令名称
第二个参数:配置项

全局指令

1
2
3
4
5
6
7
8
9
10
11
<input type="text" v-focus>

<script>
Vue.directive('focus', {
// 当绑定元素插入到 DOM 中。 其中 el为dom元素
inserted: function (el) {
// 聚焦元素
el.focus();
}
});
</script>

局部指令

  • 局部指令只能在当前组件里面使用
  • 当全局指令和局部指令同名时以局部指令为准
1
2
3
4
5
6
7
8
9
10
11
new Vue({
directives: {
focus: {
// 当绑定元素插入到 DOM 中。 其中 el为dom元素
inserted: function (el) {
// 聚焦元素
el.focus();
}
}
}
});

自定义指令的钩子函数

  • bind
  • inserted
  • update
  • componentUpdated
  • unbind
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
Vue.directive('color', {
// 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
bind: function(){},

// 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
inserted: function(){},

// 所在组件的 VNode 更新时调用
update: function(){},

// 指令所在组件的 VNode 及其子 VNode 全部更新后调用
componentUpdated: function(){},

// 只调用一次,指令与元素解绑时调用
unbind: function(){},
});
</script>

带参数的自定义指令

  • 钩子函数接收el、binding、vnode等参数
  • el:绑定的dom元素。真实的dom,可对el进行dom操作
  • binding:一个对象,包含指令的数据
  • name:指令名,不包括 v‐ 前缀
  • value:指令的绑定值,例如:v‐color=”myColor” 中,绑定值为myColor对应的值
  • vnode:虚拟dom节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<input type="text" v-color="myColor">

<script>
Vue.directive('color', {
bind: function(el, binding){
console.log(binding.name)
console.log(binding.value)
el.style.backgroundColor = binding.value;
}
});

new Vue({
el: '#app',
data: {
myColor: 'blue'
}
})
</script>

计算属性 computed

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。

计算属性用法

  • 计算属性通过computed配置
  • 每个计算属性是一个方法
  • 方法里面需要renturn数据
    • 例如:return this.msg.split('').reverse().join('')
    • return的数据依赖data中的值,上面的数据依赖this.msg
  • return的数据就是计算属性的值
  • 计算属性可以当成data中属性一样使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- reversedMessage可以跟data中的属性一样使用 -->
<div>{{reversedMessage}}</div>
<div v-text="reversedMessage"></div>

<script>
new Vue({
el: '#app',
data: {
msg: 'Nihao'
},
// computed配置计算属性
// reversedMessage就是一个计算属性,它是一个方法,里面写js逻辑返回需要的数据
// 返回的数据就是计算属性的值
computed: {
reversedMessage: function () {
return this.msg.split('').reverse().join('')
}
}
})
</script>

计算属性和methods方法对比

相同点:可以达到同样的效果

不同点:

  • 计算属性可以缓存 只有计算属性依赖的data发生变化时才会重新计算
  • 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
32
33
34
35
36
37
38
39
40
41
42
<div id="app">
<!-- 当多次调用reverseString计算属性的时候,只会打印一次computed -->
<div>{{reverseString}}</div>
<div>{{reverseString}}</div>
<!-- 调用reverseMessage()方法的时候,每次调用都会打印methods -->
<div>{{reverseMessage()}}</div>
<div>{{reverseMessage()}}</div>
</div>
<script type="text/javascript">
/*
计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存
*/
var vm = new Vue({
el: '#app',
data: {
msg: 'Nihao',
num: 100
},
methods: {
reverseMessage: function(){
console.log('methods')
return this.msg.split('').reverse().join('');
}
},
computed: {
reverseString: function(){
console.log('computed')
var total = 0;
for(var i=0;i<=this.num;i++){
total += i;
}
// reverseString是依赖this.num的,
// 只有this.num发生改变时,才会重新计算
return total;
}
}
});

// 当修改num时,reverseString的依赖发生改变,会重新计算reverseString
// 并且,由于页面发生了重新渲染,reverseString()方法也会重新调用
vm.num = 101;
</script>

计算属性的setter

计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 完整形式(支持model)
reversedMsg: {
// getter
// 获取值时触发
get: function (){
return this.message.split('').reverse().join('');
},
// setter
// 设置值时触发
set: function (){
this.message = value.split('').reverse().join('');
}
}

计算属性传参

1
2
3
4
5
6
7
computed: {
myfilter() {
return function(index){
return this.arr[index].username.match(this.name)!==null;
}
}
}

侦听器 watch

侦听器用法

  • 监听data变化执行异步(定时器、ajax等)操作或开销较大(复杂运算等耗时操作)的操作
  • 一定是监听data中已经存在的数据
  • 侦听器只有数据变化时才触发,如果想要默认触发一次侦听器,就要使用对象写法,并设置immediate
  • immediate: true 立即执行
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
// 监听数据的变化做出响应,函数名字和数据一样
// 计算属性是依赖某些数据 同步 计算出一个结果
// 而侦听器是监听某个数据的变化而做相应的操作
data: {
firstName: 'zhang',
lastName: 'san',
fullName: ''
},
watch: {
/** 监听firstName的变化:
* ‐ newValue: 变化后的值
* ‐ oldValue:变化前的值
*/
firstName: {
handler: function(newValue, oldValue) {
this.fullName = newValue + ' ' + this.lastName;
},
// 会默认先触发一次handler,修改fullName
immediate: true,
},
lastName: {
handler: function(newValue, oldValue) {
this.fullName = this.firstName + ' ' + newValue;
},
immediate: true,
},
}
// 监听不到复杂数据类型的变化

深度侦听

  • 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听

  • deep: true进行深度侦听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
data: {
obj: {
a: 123
}
},
watch: {
obj: {
deep: true,
// deep默认为false,无法监听obj.a的变化,
// 如果deep为true,当修改this.obj.a时会触发handler方法
handler(newName, oldName) {
console.log('obj.a changed');
},
}
}

// deep监听开销较大,可以使用字符串形式监听
watch: {
'obj.a': {
handler(newName, oldName) {
console.log('obj.a changed');
}
}
}

侦听器和计算属性的区别

  • 侦听器适用于:当一个数据变化时,要修改多个数据或者要进行异步操作
  • 计算属性适用于:某个数据的值是一个或多个其它数据运算的结果

过滤器 filters

基本使用

  • 过滤器可以用在两个地方:双花括号插值和v-bind表达式
  • 过滤器应该被添加在表达式的尾部,由管道符|隔开
  • 支持级联操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 在双花括号中 -->
<div>{{msg | upper}}</div>
<!-- 多个过滤器级联操作 -->
<div>{{msg | upper | lower}}</div>


<!-- 在 `v-bind` 中 -->
<div :abc='msg | upper'>测试数据</div>

<script>
// ...
data: {
msg: 'abc'
},
// ...
</script>

局部过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div>{{msg | upper}}</div>

<script>
new Vue({
// ...
data: {
msg: 'abc'
},
filters: {
// val就是管道符`|`前面的表达式的值。此处就是`msg`
upper: function(val) {
return val.charAt(0).toUpperCase() + val.slice(1);
}
}
})
</script>

全局过滤器

1
2
3
4
5
<script>
Vue.filter('lower', function(val) {
return val.charAt(0).toLowerCase() + val.slice(1);
});
</script>

过滤器的参数

1
2
3
4
5
6
7
8
9
<div>{{message | filterA('a', 'b')}}</div>

<script>
Vue.filter('filterA', function(value, arg1, arg2){
// value - message对应的值
// arg1 - 'a'
// arg2 - 'b'
})
</script>

生命周期

  • 事物从出生到死亡的过程

  • Vue实例从创建 到销毁的过程

    • 每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数。

生命周期经历的主要阶段

  1. 挂载 - 初始化相关属性(data、methods等)
    ① beforeCreate
    ② created
    ③ beforeMount
    ④ mounted

  2. 更新 - DOM元素或组件的更新
    ① beforeUpdate
    ② updated

  3. 销毁(销毁相关属性)
    ① beforeDestroy
    ② destroyed

常用的钩子函数

beforeCreate 在实例初始化之后,数据观测和事件配置之前被调用 此时data 和 methods 以及页面的DOM结构都没有初始化 什么都做不了
created 在实例创建完成后被立即调用此时data 和 methods已经可以使用 但是还没有编译模板
beforeMount 在挂载开始之前被调用 此时模板编译完成,但是页面还没有渲染,页面上还看不到真实数据 只是一个模板页面而已
mounted el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。 数据已经真实渲染到页面上 在这个钩子函数里面我们可以使用一些第三方的插件或者请求后台数据
beforeUpdate 数据更新时调用,发生在虚拟DOM打补丁之前。 data已经更新,但是页面上数据还是旧的
updated 由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。 页面上数据已经替换成最新的
beforeDestroy 实例销毁之前调用
destroyed 实例销毁后调用

其他钩子函数

activated 被 keep-alive 缓存的组件激活时调用。

deactivated 被 keep-alive 缓存的组件停用时调用。

errorCaptured 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

响应式

变更方法(变异方法)

更改了原始数组

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

替换数组(非变异方法)

不更改原始数组,返回一个新数组

  • filter
  • concat
  • slice
  • map()

$set修改响应式数据

对于数组

Vue不能监听数组的变化,但是我们调用push、pop等变异方法都是响应式的,因为以上方法都是被Vue重写之后的方法

通过Vue.set修改响应式数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})

// vm.items[1] = 'x' // 不是响应性的
Vue.set(vm.items, 1, 'x') // 是响应式的
// 或者用splice
// vm.items.splice(1, 1, 'x') // 是响应式的

// vm.items.length = 2 // 不是响应性的
vm.items.splice(2) // 是响应式的

// 也可以使用 vm.$set,在钩子函数中可以使用this.$set,因为this指向vm实例
</script>
对于对象
  • Vue 无法检测 property 的添加或移除
    由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
var vm = new Vue({
data:{
a:1,
obj: {
name: 'zhang san'
}
}
})

// `vm.a` 是响应式的

vm.b = 2
// `vm.b` 是非响应式的

vm.obj.age = 18
// `vm.obj.age` 是非响应式的
</script>
  • 对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
var vm = new Vue({
data:{
a:1,
obj: {
name: 'zhang san'
}
}
})

Vue.set(vm.obj, 'age', 18) // 是响应式的

// 也可以通过Object.assign重新赋值
this.obj = Object.assign({}, this.obj, { age: 18 })
// 也可以使用es6扩展运算符
this.obj = {
...this.obj,
age: 18
}

</script>

Vue组件化开发

组件注册

全局组件

全局组件就是可以在任何vue实例中使用的组件

1
2
3
4
5
6
7
8
9
<script>
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
// 第1个参数是组件名称
// 第2个参数是选项对象
// 第2个参数中的template用来配置组件的html模板
// 也可以配置跟new Vue相同的选项,例如 data、computed、watch、methods以及生命周期钩子等
</script>
通过html标签使用组件
  • 组件注册后是通过html标签来使用,标签名称就是组件名称
1
2
3
4
<div id="app">
<!-- 通过html标签使用组件,`my-component`就是我们注册的组件 -->
<my-component></my-component>
</div>
组件名称命名方式
  • kebab-case命名方式:例如my-component

  • PascalCase命名方式:例如MyComponent

    • 使用此种方式命名组件时,使用组件时注意:
      • 在template中使用时PascalCase方式和kebab-case都可以使用:<MyComponent></MyComponent> <my-component></my-component>
      • 直接在DOM中使用要写成kebab-case的方式:<my-component></my-component>
  • 总之建议使用kebab-case方式命名和使用组件

template使用注意
  • template模板必须包含在一个根元素中
  • template模板可以是模板字符串
添加data数据
1
2
3
4
5
6
7
8
9
10
<script>
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">点击了{{ count }}</button>'
})
</script>
  • 注意: data必须是一个函数,并且返回一个对象
    • 因为组件可以被复用,如果data是个对象,那每个组件实例都共享该data,就会导致在一个实例里面修改data影响所有的实例。
    • 如果data是个函数,每个实例可以维护一份被返回对象的独立的拷贝
    • 根组件的data不需要配置成函数,因为根组件不会被复用
局部组件

局部组件就是只能在当前实例中使用的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="app">
<my-component></my-component>
</div>

<script>
new Vue({
//注册组件
components: {
'my-component': {
template: '<div>A custom component!</div>'
}
}
})
</script>
template的其他使用方式
  • 使用template模板标签
1
2
3
4
5
6
7
8
9
10
11
<template id="temp">
<div>
123
</div>
</template>

<script>
Vue.component('my-component', {
template: '#temp'
})
</script>
  • 使用