一、介绍
Vue(读音 /vjuː/,类似于 view)是一套用于构建用户界面的渐进式JavaScript框架。
渐进式:可以自底向上逐层的应用。比如,只需要一个轻量小巧的核心库就可以构建一个简单应用或内嵌到自己的应用中,也可以引入各式各样的Vue插件构建一个复杂应用。
1.1. 特点
- 采用组件化模式,提高代码复用率,且让代码更好维护。
- 比如,一个
.vue
文件就可以是一个组件,因为.vue
文件中包含html、css、js等内容。
- 比如,一个
- 声明式编码,让编码人员无需直接操作DOM,提高开发效率。
- 命令式:以往直接操作DOM的开发方式是命令式编程,按照顺序依次给出后指令执行一段代码。
- 声明式:只需要声明一个指令或者代码,框架会自动解析执行,比如
v-if、v-for
。
- 使用虚拟DOM和优秀的Diff算法,尽量复用DOM节点。
- 比如,一个
ul
列表,如果是静态元素,虚拟DOM和真实DOM几乎无差别,甚至真实DOM比虚拟DOM的渲染速度更快,因为虚拟DOM有资源开销。但如果是动态元素,由于Diff算法的存在,会把即将渲染的数据和旧数据进行Diff操作以形成虚拟DOM,让真实DOM只渲染新增加的元素。
- 比如,一个
1.2. MVVM
Vue采用的是MVVM架构模式。
MVVM的维基百科:https://zh.wikipedia.org/zh-cn/MVVM
Vue中的MVVM模型:
1.3. 生命周期
生命周期又名生命周期回调函数、生命周期函数、生命周期钩子。
生命周期函数中的this
指向的是vm或组件实例对象。
二、安装
2.1. CDN
1 | <!-- 开发环境版本,包含了有帮助的命令行警告 --> |
2.2. 本地引入
把文件下载到本地进行引用。
2.3. npm
通过webpack或者CLI进行安装。
三、Vue实例
3.1. 挂载
new Vue()
创建的实例中有很多$
开头的方法,这些方法其实是在Vue
的原型对象中,主要是提供给开发者使用。因此可以直接使用Vue
实例进行手动挂载。
1 | <div id="root"> |
应用场景:在合适的时间(例如延时)把Vue实例挂载到指定容器中。
3.2. 数据
对象式:正常情况下,Vue属性data
的写法如下
1 | <script> |
函数式:其实data
还可以写成函数的形式,如下所示
1 | <script> |
两种写法有什么区别呢?
其实真实开发中,函数式写法使用频率最高。因为组件式开发时data
必须使用函数式。
data
使用函数式时一定不要使用箭头函数,应使用普通函数,因为涉及到data
函数内部的this
指向问题。
1 | <!-- 箭头函数 --> |
data中的所有数据Vue会自动做数据代理操作,这也是实现双向绑定的最核心做法。
3.3. 数据代理
Vue中的数据代理是通过vm对象(Vue实例对象)来代理data对象中属性的操作(读/写)。
使用数据代理的好处就是更加方便的操作data中的数据。
基本原理:
- 通过
Object.defineProperty()
把data对象中所有属性添加到vm上。 - 为每一个添加到vm上的属性,都指定一个getter/setter。
- 在getter/setter内部去操作(读/写)data中对应的属性。
四、指令和语法
1 | <div id="app"> |
4.1. 内容显示
插值语法(双大括号)把数据插入到html中。双大括号内可以写js表达式。
1
2
3
4
5
6<h2>Hello {{name}}</h2>
<!-- JS表达式 -->
<h2>Hello {{a + b}}</h2>
<h2>Hello {{name.toUpperCase()}}</h2>
<h2>Hello {{a > b : '正确' : '错误'}}</h2>v-once:该指令后面不需要跟任何表达式,表示元素和组件只渲染一次,不会随着数据的改变而改变。
1
<h2 v-once>{{message}}</h2>
v-html:向指定节点中渲染包含html结构的内容。
- 和插值语法的区别:v-html会替换掉节点中所有的内容,并且可以识别html结构。
- v-html有安全性问题:在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。一定要在可信的内容上使用v-html,永远不要用在用户提交的内容上。
1
<h2 v-html="link"></h2>
v-text:和插值语法类似,都是用来显示文本(如果内容包含标签,也不会自动解析)。和插值语法不一样的是,它会整体替换标签中的内容。
1
<h2 v-text="message"></h2>
v-pre:用于跳过这个元素和它子元素的编译过程,Vue不再解析指令所在节点。
1
2
3
4<!-- 页面显示:Hello {{name}} -->
<div v-pre>
<h2>Hello {{name}}</h2>
</div>v-cloak:编译前不进行渲染(一般用于加载动画,在数据加载前不渲染元素)。
1
<h2 v-cloak>content</h2>
4.2. 数据绑定
4.2.1. 单向绑定
v-bind(语法糖::
):用于绑定一个或多个属性值(单向绑定),或者向另一个组件传递props值。
1 | <!-- 完整写法 --> |
绑定class
比如:当数据为某个状态时,字体显示红色;当数据另一个状态时,字体显示黑色。
绑定class有两种方式:对象语法和数组语法。
对象语法:class后面跟的是一个对象、字符串、数组。
- 字符串写法适用于:类名不确定,要动态获取(data中取值)
- 对象写法适用于:要绑定多个样式,个数不确定,名字不确定
- 数组写法适用于:要绑定多个样式,个数确定,名字确定,但不确定用不用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!-- 用法一:从data中读取normal属性 -->
<h2 class="basic" :class="normal">Hello World</h2>
<!-- 用法二:直接通过{}绑定一个类 -->
<h2 :class="{'active': isActive}">Hello World</h2>
<!-- 用法三:也可以通过判断,传入多个值 -->
<h2 :class="{'active': isActive, 'line': isLine}">Hello World</h2>
<!-- 用法四:和普通的类同时存在,并不冲突 -->
<!-- 注:如果isActive和isLine都为true,那么会有title/active/line三个类 -->
<h2 class="title" :class="{'active': isActive, 'line': isLine}">Hello World</h2>
<!-- 用法五:如果过于复杂,可以放在一个methods或者computed中 -->
<!-- 注:classes是一个计算属性 -->
<h2 class="title" :class="classes">Hello World</h2>数组语法:class后面跟的是一个字符串、数组、对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!-- 用法一:从data中读取arr属性(数组) 适用于:要绑定的样式个数不确定,名字也不确定 -->
<h2 class="basic" :class="arr">Hello World</h2>
<!-- 用法一:直接通过{}绑定一个类 -->
<h2 :class="['active']">Hello World</h2>
<!-- 用法二:也可以传入多个值 -->
<h2 :class="['active', 'line']">Hello World</h2>
<!-- 用法三:和普通的类同时存在,并不冲突 -->
<!-- 注:会有title/active/line三个类 -->
<h2 class="title" :class="['active', 'line']">Hello World</h2>
<!-- 用法四:如果过于复杂,可以放在一个methods或者computed中 -->
<!-- 注:classes是一个计算属性 -->
<h2 class="title" :class="classes">Hello World</h2>
绑定style
用来绑定一些CSS内联样式,在写CSS属性名的时候,比如
font-size
,我们可以使用驼峰式 (camelCase)fontSize
或短横线分隔 (kebab-case,记得用单引号括起来)'font-size'
。绑定style有两种方式:对象语法和数组语法。
对象语法:style后面跟的是一个对象类型(对象的key是CSS属性名称,对象的value是具体赋的值,值可以来自于data中的属性)
1
<h2 :style="{color: currentColor, fontSize: fontSize + 'px'}">Hello World</h2>
数组语法:style后面跟的是一个数组类型,多个值以英文
,
分割即可1
<div :style="[baseStyles, overridingStyles]"></div>
4.2.2. 双向绑定
v-model:元素和数据双向绑定。v-model
完整写法是v-model:value
,因为v-model:value
默认收集的是value
值,所以可以简写为v-model
。
1 | <input type="text" v-model="message"> |
当我们在输入框输入内容时,因为input
中的v-model
绑定了message
,所以会实时将输入的内容传递给message
(message
发生改变)。
当message
发生改变时,因为上面我们使用Mustache语法,将message
的值插入到DOM中,所以DOM会发生响应的改变。
所以,通过v-model
实现了双向的绑定。
注意:v-model只能应用在表单类元素(如
input
、select
等)。因为v-model主要是影响value和data数据源。例如<h2 v-model:x="123"></h2>
就不能使用v-model
,因为没有输入源影响数据源。
原理
v-model
其实是一个语法糖,它本质上是包含两个操作(原理):v-bind
绑定一个value
属性v-on
指令给当前元素绑定input
事件
1
2
3<input type="text" v-model="message">
// 等同于
<input type="text" v-bind:value="message" v-on:input="message = $event.target.value">修饰符
lazy:默认情况下,v-model默认是在input事件中同步输入框的数据的。也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变。lazy修饰符可以让数据在失去焦点或者回车时才会更新。
number:默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。number修饰符可以让在输入框中输入的内容自动转成数字类型:
trim:如果输入的内容首尾有很多空格,通常我们希望将其去除。trim修饰符可以过滤内容左右两边的空格。
1
2
3
4
5
6
7
8
9
10<div>
<input type="text" v-model.lazy="message">
<p>当前内容(lazy):{{message}}</p>
<input type="number" v-model.number="age">
<p>当前年龄(number):{{age}} 类型:{{typeof age}}</p>
<input type="number" v-model.trim="message">
<p>当前内容(trim):{{message}}</p>
</div>
4.3. 事件处理
v-on(语法糖:@
):事件监听。
- 事件的回调需要配置在methods对象中,最终会在vm上;
- methods中配置的函数,不要用箭头函数,否则this就不是vm;
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象;
@click="demo"
和@click="demo($event)"
效果一致,但后者可以传参。
函数写在data中也是可以进行调用的,只不过函数没必要做数据代理,所以函数尽量放在methods中。
1 | <!-- v-on不仅可以直接对属性进行操作,还可以传入一个methods中定义的函数 --> |
4.3.1. 参数
- 如果methods中定义的方法不需要额外参数,那么方法后的
()
可以不添加。 - 如果方法本身中有一个参数,那么会默认将原生事件
event
作为参数传递进去。 - 如果需要同时传入某个参数,同时需要
event
时,可以通过$event
传入事件。
1 | <button @click="btnClick(10, $event)">按钮</button> |
4.3.2. 修饰符
1 | <!-- 阻止默认行为 --> |
如果使用Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为短横线命名(比如,CapsLock(大小写)使用的时候需要转换为@keyup.caps-lock=””)
系统修饰键(ctrl、alt、shift、meta(Windows的win键或Mac的command键))用法比较特殊:
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
- 比如需要触发ctrl键,需要先按下ctrl不松,再按下其他键才能触发。
- 如果需要指定组合键,可以连写,比如ctrl和y键触发:
@keyup.ctrl.y="onCtrl"
- 配合keydown使用:正常触发事件。
也可以使用keyCode去指定具体的按键(比如:@keyup.13="onEnter"
),但是不推荐这样做。
定制按键别名:Vue.config.keyCodes.自定义键名 = 键码
。
4.4. 条件渲染
v-if、v-else-if、v-else、v-show
v-if
:后面的条件为false
时,对应的元素以及其子元素不会渲染。也就是根本没有不会有对应的标签出现在DOM中。v-show
:用法和v-if
非常相似,也用于决定一个元素是否渲染。v-if
和v-show
都可以决定一个元素是否渲染,那么开发中我们如何选择呢?两者的区别:
v-if
当条件为false
时,压根不会有对应的元素在DOM中。v-show
当条件为false
时,仅仅是将元素的display
属性设置为none
而已(占位)。
两者的选择:
当需要在显示与隐藏之间切片很频繁时,使用
v-show
,避免资源开销当只有一次切换时,通过使用
v-if
v-if
和template
进行组合使用:
1 | <!-- 原始代码 --> |
注意:
template
不能和v-show
进行组合。
4.5. 列表渲染
v-for
4.5.1. 数组
1 | <ul> |
检测数组更新:因为Vue是响应式的,所以当数据发生变化时,Vue会自动检测数据变化,视图会发生对应的更新。Vue中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新。
1 | methods: { |
只要不会触发原数组变化的API都不会触发响应式,比如filter,[index],slice
。
4.5.2. 对象
1 | <ul> |
如果需要动态给对象添加/删除属性,需要使用特殊的API才能触发响应式:
1 | // 使用组件实例修改 |
删除属性不要使用JS的API(delete this.person.name
),因为不会触发响应式。
1 | // 删除对象指定属性 |
4.5.3. key
官方推荐我们在使用v-for
时,给对应的元素或组件添加上一个:key
属性。主要用来Diff算法时,让程序不要重用元素。key的作用主要是为了高效的更新虚拟DOM。
index作为key:
id作为key:
面试题:Vue/React中的key有什么作用(key的内部原理)?
- 虚拟DOM中key的作用:key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新虚拟DOM】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中内容没变,直接使用之前的真实DOM(复用)
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同的key:
- 创建新的真实DOM,随后渲染到页面
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 用index作为key可能会引发的问题:
- 若对数据进行逆序添加、逆序删除等破坏顺序的操作,会产生没有必要的真实DOM更新(界面效果没问题,但效率低)
- 如果结构中还包含输入类的DOM,会产生错误DOM更新(界面有问题)
- 开发中如何选择key?
- 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
4.6. 计算属性
computed:要用的属性不存在,需通过已有属性计算得出。
原理:底层借助了Object.defineproperty
方法提供的getter和setter。
get函数什么时候执行?
- 初次读取时会执行一次
- 当依赖的数据发生改变时会被再次调用
计算属性最终会出现在vm上,直接读取使用即可。如果计算属性要被修改,那必须写set函数去响应修改,且set函数中要引起计算时依赖的数据发生变化。
1 | <div id="app"> |
methods
和computed
都可以实现类似的功能,那么为什么还要计算属性呢?
原因:计算属性会进行缓存(复用),如果多次使用时,计算属性只会调用一次。效率更高,节省资源。
4.7. 监视
watch可以检测属性和计算属性的值变化。
- 当被监视的属性变化时,回调函数
handler
自动调用 - 监视的属性必须存在才能进行监视
- 监视有两种写法:
new Vue
时传入watch
配置- 通过
vm.$watch
进行监视
1 | <script> |
immediate
:是否从程序运行时就开始监控,默认是false
。
- 如果
immediate: true
,如上程序首次运行时输出:isHot由false改变为undefined
- 如果
immediate: false
,只有监听的属性值改变时才会监听到
deep
:是否开启深度监视,监视多级结构中所有属性的变化。默认false
。
使用Vue实例上的$watch
也可以监听属性值的变化:
1 | <script> |
如果要监听属性中多级结构对象里面的某个key的值变化:
1 | <script> |
注意:Vue默认情况下是可以监视到多层级结构属性值变化的,但
watch
默认不能检测对象内部值的变化(只能检测一层)。给watch
配置deep:true
才可以检测对象内部值的改变(可以检测多层)。
当被监视的属性,只需要handler
时,可以简写为:
1 | watch: { |
watch
和computed
的区别?
computed
能完成的功能,watch
都可以完成watch
能完成的功能,computed
不一定能完成,例如:watch
可以进行异步操作。
使用过程中,只要遵循以下两个小原则就可以:
- 所有被Vue管理的函数,最好写成普通函数,这样
this
的指向才是vm或组件实例对象。 - 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等),最好写成箭头函数,这样
this
的指向才是vm或组件实例对象。
原理:
- Vue会监视data中所有层次的数据。
- 如何检测对象中的数据?通过
setter
实现监视,且要在new Vue
时就传入要监测的数据。- 对象中后追加的属性,Vue默认不做响应式处理。如果需要给后添加的属性做响应式,请使用如下API:
Vue.set(target, propertyName/index, value)
或vm.$set(target, propertyName/index, value)
- 对象中后追加的属性,Vue默认不做响应式处理。如果需要给后添加的属性做响应式,请使用如下API:
- 如何监测数组中的数据?通过包裹数组更新元素的方法实现,本质就是做了两件事:
- 调用原生对应的方法对数组进行更新
- 重新解析模板,进而更新页面
- 在Vue修改数组中的某个元素一定要用如下方法:
push(),pop(),shift(),unshift(),splice(),sort(),reverse()
(都会改变原数组)Vue.set()
或vm.$set()
特别注意:
Vue.set()
和vm.$set()
不能给vm或vm的根数据对象添加属性。
4.7. 过滤器
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
- 注册过滤器
- 全局:
Vue.filter(name, callback)
- 局部:
new Vue(filters:{})
- 全局:
- 使用过滤器
{{ xxx | 过滤器名 }}
(经常使用)v-bind:属性 = "xxx | 过滤器名"
(很少使用)
过滤器也可以接收额外参数、多个过滤器也可以串联。过滤器并没有改变原来的数据,是产生新的数据。
1 | <!-- 默认传参:前面的参数会作为形参传给后面的过滤器 --> |
五、自定义指令
使用Vue提供的directives
可以自定义指令。
指令函数调用时机:
- 与元素绑定成功时调用时(第一次,此时并没有渲染到页面)
- 指令所在模板被重新解析时
1 | <body> |
上面的代码执行没有问题。但是下面的代码就不能使input聚焦,因为在执行element.focus()
时,input还没有被Vue渲染到DOM上(只有渲染到DOM上,input才能执行聚焦方法)。此时就需要编写成键值对形式了。
1 | <body> |
上面代码改进:
1 | <script> |
其实fbind(){}
就是bind
和update
的合体写法。
和watch类似,上面的写法是局部自定义指令,自定义全局指令写法如下:
1 | <script> |
命名注意:指令定义时不加
v-
,但使用时必须要加v-
。指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。