一、Vue基础
基于Vue官方文档进行二次巩固学习时所做的笔记,大部分摘抄官方文档,部分是对于不理解或不清晰的地方,借鉴了网上的经验
1.简介
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
2.绑定数据
方式一:模板消息
<div id="app">
{{ message }}
</div>
方式二:v-bind
<span v-bind:title="message">
鼠标悬停几秒钟查看此处动态绑定的提示信息!
</span>
对应变量应定义与data部分中。
3.条件与循环
if条件判断:
<div id="app-3">
<p v-if="seen">现在你看到我了</p>
</div>
条件为true时显示这个标签。
for循环:
<div id="app-4">
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>
todo是每个子项的名称,todos则是集合名称。可以以类似json的格式去组织每个子项内部的元素,如:
data: {
todos: [
{ text: '学习 JavaScript' },
{ text: '学习 Vue' },
{ text: '整个牛项目' }
]
}
4.处理用户输入
使用v-on指令,来监听某一个事件,如click点击事件:
<button v-on:click="xxx">点击事件</button>
使用v-module实现双向绑定:
<input v-model="message">
可以将input输入框中的内容双向绑定到message变量中。
5.Vue实例
5.1 创建Vue实例
通过Vue函数创建新的Vue实例:
var vm = new Vue({
// 选项
})
根据这里传入的选项可以达到目标效果。
5.2 数据与方法
Vue实例被创建时就已经存在于data中的property 会加入到Vue的响应式系统中,当这些property的值发生变化时,视图讲产生响应,更新为新的值。
// 创建一个数据对象,里面有一个property: a=1
var data = { a: 1}
// 把这个数据对象加入到vue实例
var vm = new Vue({
data: data
})
此时property a与源数据中的a是绑定的,修改其中一个会影响到另一个的值。后续添加的property不是响应式的,因此如果需要用到某个property,就一定要在创建前事先定义它,并为其设置一些初始值。
唯一例外: 使用Object.freeze()
方法会阻止修改现有的property。响应系统将无法追踪变化。
Vue实例暴露的一些原始property和方法会加上前缀$,以此与用户定义的property区分开。
5.3 实例生命周期钩子
生命周期钩子函数:created/mounted/updated/destroyed
等函数用于在Vue实例被创建的过程中的某些特殊时间段执行开发者想要的函数,如:
new Vue({
created: function () {
...
}
}
})
代码可以在实例被创建的时候执行一个函数。在生命周期钩子中,this上下文可以指向调用它的vue实例。注意这里不能使用箭头函数() => {}
,因为箭头函数里没有this上下文。
5.4 Vue实例的生命周期
- beforeCreate 组件实例被创建之初:在该生命周期函数执行时,data和methods中的数据都没有初始化。
- created 组件实例已经完全创建:该生命周期函数执行时,是最早可以访问data和methods中的方法的地方。
- beforeMount 组件挂载之前:到此之前,Vue会开始编辑模板,执行代码中的指令,最终在内存中生成一个编译好的最终模板字符串,并渲染为内存中的DOM,当这个生命周期函数执行时,模板在内存中编译好了,但是还没有挂载进页面,此时页面还是旧的。
- mounted 组件挂载到实例上去:在这里会将内存中编译好的模板替换到浏览器的页面中,到这个生命周期函数执行完,整个Vue实例已经初始化完毕了,组件脱离创建阶段,进入运行阶段。
- beforeUpdate 组件数据发生变化,更新之前:执行到此时页面中的数据还是旧的,data数据是新的,还没有完成同步。
- updated 数据数据更新之后:在这一步之前完成的操作是根据data中最新的数据,在内存中渲染出一份最新的DOM树,当最新的内存DOM树被更新之后,把最新的DOM树渲染到页面中去,到此完成了Model到试图的更新。这一步执行完后,页面和data数据已经保持同步了,并且都是最新的数据。
- beforeDestroy 组件实例销毁之前:执行到这个钩子函数时,Vue实例已经从运行阶段进入了销毁阶段,此时的实例中的所有data、methods以及过滤器、指令等都还处于可用状态。
- destroyed 组件实例销毁之后:执行到这个函数时,组件已经完全销毁,所有内容都已经不可用。此时DOM元素还存在,但是不受Vue控制。
- activated keep-alive 缓存的组件激活时
- deactivated keep-alive 缓存的组件停用时调用
- errorCaptured 捕获一个来自子孙组件的错误时被调用
6.模板语法
Vue使用了给予HTML的模板语法,允许开发者声明式地将DOM绑定至底层Vue实例的数据。
6.1 插值
数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值:
<span> {{ message }} </span>
这里会将数据解释为普通文本,如果想要输出真正的html,则需要使用v-html
指令:
<span v-html="rawHtml"> {{ message }} </span>
这个span的内容会替换微rawHtml,并作为html被渲染到页面上。
使用这种渲染方式容易受到XSS攻击,如在插入的rawHtml中包含一些点击函数,来触发某些攻击。
如果需要在HTML的attribute上绑定某个变量,不能使用双大括号语法,此时需要使用v-bind
指令,如:
<div v-bind:id="myId"></div>
以上绑定的形式,包括双大括号和v-bind
指令,都可以用于单个的JavaScript表达式,其他语句、分支、循环等均不能被解析。模板表达式会被放在一个沙盒中,只能访问全局变量的一个白名单。
白名单内容:
Infinity, undefined, NaN, isFinite, isNaN, parseFloat, parseInt, decodeURI, decodeURIComponent, encodeURI, encodeURIComponent,Math, Number, Date, Array, Object, Boolean, String, RegExp, Map, Set, JSON, Intl, require
6.2 指令
指令指的是带有v-
前缀的特殊attribute,指令attribute的值预期是单JavaScript表达式(v-for
是特殊情况)
如:v-if
指令根据参数的true或false决定元素的可见性
<p v-if="seen">现在你看到我了</p>
有些指令可以接收一个参数,如:
<a v-bind:href="url">...</a>
该指令会把元素的href attribute与表达式url的值绑定。
指令的参数从2.6.0版本后可以是一个用方括号括起来的JavaScript表达式,称为动态参数:
<a v-on:[eventName]="doSomething"> ... </a>
这里的eventName会作为一个表达式动态求值,把最终的值作为指令的参数。如果这里的eventName最终值为focus,则该指令相当于v-on:focus
。
动态参数预期会求出一个字符串,异常情况下值为 null
。这个特殊的 null
值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。同时还有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。如果一定需要它们,则可选的方法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式。
指令可以通过.
来指定修饰符,用于指出一个指令应该以特殊方式绑定,如:
<form v-on:submit.prevent="onSubmit">...</form>
prevent修饰符告诉指令对于触发的事件调用event.preventDefault()
方法
6.3 缩写
对于常用的两个指令:v-bind
和 v-on
,Vue提供了相应的缩写:
v-bind:
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>
v-on:
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>
:
与 @
对于 attribute 名来说都是合法字符,在所有支持 Vue 的浏览器都能被正确地解析。并且它们不会出现在最终渲染的结果里。
7.计算属性和侦听器
7.1 计算属性
用于替代过于复杂的模板内的表达式,声明在Vue实例中的computed段内,如:
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
之后在标签中或其他地方使用reversedMessage
即可快速访问翻转过来的message字符串。
计算属性与方法达到的效果是一致的,不同的是计算属性是基于响应式依赖进行缓存的,只会在相关的响应式依赖发生变化,如这段代码中的message变化时,才会重新求值,否则,多次访问reversedMessage
得到的结果都是相同的。如果声明时return的结果就不是响应式依赖,那么这个计算属性的值将不会再改变。
如果必要的话,可以通过如下方式添加一个setter:
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
当fullname变化时,会为firstname和lastname赋值。
7.2 侦听器
Vue 通过 watch
选项提供了一个更通用的方法,来响应数据的变化。一般用于需要在数据变化时执行异步或开销较大的操作时。
8.Class和Style的绑定
8.1 绑定HTML Class
对象语法
通过v-bind:class
传递一个对象,可以动态切换class。如:
<div v-bind:class="{ active: isActive }"></div>
如果变量是true(包含其他可转换为真值的表达式,详见:MDN Truthy(真值)的定义),则这个div标签的class属性将是active,否则没有该属性。这个字段可以装入更多的变量来切换多个class,多个class之间是并存的,并且还可以与其他class attribute共存。
数组语法
以上是通过bool值控制是否有某个class attribute,也可以通过一个数组,将一系列class attribute传递给v-bind
指令,如:
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
以上代码会渲染为:
<div class="active text-danger"></div>
数组语法中也可以嵌套对象语法,用来精确控制某一个attribute是否被添加。
用于组件
组件中声明了某个class property时,这些property会被添加到该组件的根元素上,如:
Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})
<my-component class="baz boo"></my-component>
此时HTML将被渲染为:
<p class="foo bar baz boo">Hi</p>
v-bind:class
指令绑定的class attribute也适用。
8.2 绑定内联样式
对象语法
与绑定class类似的,将样式以对象语法的形式写到v-bind
指令下:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名.
或者直接绑定到一个样式对象,然后在data中为样式对象赋值,达到设置样式的效果:
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
数组语法
同上,使用v-bind:style
,将多个样式对象以数组形式绑定
9.条件渲染
9.1 v-if
v-if
指令用于条件性渲染某一块内容,只有在表达式返回值为真值时渲染,使用v-else
可以加上一个else块:
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
v-else
元素必须紧跟在带 v-if
或者 v-else-if
的元素的后面,否则它将不会被识别。
用于template
在<template>
元素上使用v-if
可以达到条件渲染分组的效果,如:
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
条件满足时可以渲染多个标签。
用key管理可复用元素
某些标签是可以复用的,在不同条件下如果没有区分,条件变化时可能会保留它们。如果不同条件下的标签是不同的,那么可以添加key attribute来区分它们:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
以此区分两个input是不同的input,在切换时将完全重新渲染。对于没有通过key加以区分的标签,则仍然会复用。
9.2 v-show
同样可以用于根据条件展示元素:
<h1 v-show="ok">Hello!</h1>
这些元素始终会被渲染并保留在DOM中,但是它只是简单的切换元素的display属性,并且不能用于template中。
9.3 两者的区别
v-if
是条件性的渲染,他会保证在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建,同时也是惰性的,在初期渲染条件为非真值的时候,不会做任何渲染,直到条件变为真。
v-show
无论初始条件是什么,元素总是被渲染,并且只是简单地进行样式切换。
一般来说,v-if
有更高的切换开销,而v-show
则会有更大的初始渲染开销,频繁发生条件变化时,v-show
要更优一些。
v-if
的优先级会低于v-for
,不建议两者一起使用。
10.列表渲染
10.1 v-for
可以用来基于一个数组渲染一个列表,该指令使用item in collection 或(item, index) in collection
形式的语法,item为元素名,index为索引,collection为元素数组的名字。这里的分隔符in也可以用of代替。
<ul id="example-1">
<li v-for="item in items" :key="item.message">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
该代码会将两条message渲染到列表中。v-for
块下可以访问到所有父作用于property。
遍历一个对象的property
<ul id="v-for-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>
则对象object中的各个值会以value为别名,渲染到各个li
标签中,也可以使用第二个参数作为property的名:
<div v-for="(value, name) in object">
{{ name }}: {{ value }}
</div>
对象的值的遍历是按Object.keys()
的结果进行遍历的,不能保证在所有JS引擎下都一致。
使用范围值
v-for
还可以接收整数,作为重复次数:
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
该模板将渲染10次。
用于<template>
类似于v-if
,可以使用带有v-for
的template来循环渲染一段包含多个元素的内容
10.2 维护状态
v-for
对数据的维护策略是就地更新,即当某项数据发生变化时,即使顺序发生了变化,Vue也不会移动DOM元素去匹配数据项的顺序,而是在它的原始位置上去更新它。它只适用于不依赖子组件状态或临时DOM状态的列表渲染输出。
为了让Vue能够追踪每个节点的位置,保证它们能按顺序正确排列,需要为每项提供一个唯一的key attribute:
<div v-for="item in items" v-bind:key="item.id">
<!-- 内容 -->
</div>
如果期望列表成员有序,则使用v-for
时应提供这个key attribute。
10.3 数组更新检测
变更方法
所有被侦听的数组的变更方法都进行了包裹,这些方法会变更调用了这些方法的原始数组,可用的方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
替换数组
某些方法如filter()
、concat()
和 slice()
,它们不会变更原始的数组,而是返回一个新的数组。对于Vue来说,因为采用了一些智能的启发式方法,所以使用一个含有相同元素的数组去替换原数组是非常高效的。
10.4 显示过滤或排序后的结果
方法一:使用计算属性
<li v-for="n in evenNumbers">{{ n }}</li>
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
将计算属性作为遍历的集合对象。
方法二:使用一个函数处理集合(当第一种不适用时)
<ul v-for="set in sets">
<li v-for="n in even(set)">{{ n }}</li>
</ul>
data: {
sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
10.5 在组件上使用v-for
在组件上使用v-for
与在其他元素上一致,2.2.0+的版本里,在组件上使用v-for
时必须提供key attribute,但是组件有自己的作用于,所有数据都不会被自动传递到组件里,需要迭代的数据要通过prop绑定到组件里。
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>
这样可以使得同一个带v-for
的组件能够用于更广泛的场合。
11.事件处理
11.1 监听事件
通过v-on
指令可以监听DOM事件,并在触发时执行相应的操作:
<div id="example-1">
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
以上代码监听button的click事件,并为count累加。
这里的操作可以替换为定义在methods中的方法,或者以内联形式调用方法,来完成更多复杂操作。如果在内联语句处理器中需要访问原始的DOM事件,则可以通过特殊变量$event
传递到方法中
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
11.2 事件修饰符
事件修饰符是跟在v-on
指令的事件后面的后缀:
-
.stop
阻止事件继续传播,如果有嵌套的事件会到此停止,不会继续向上传递: -
.prevent
阻止事件发生后的默认行为,如跳转、刷新等 -
.capture
监听事件时, 使用事件捕获模式(从外到内) -
.self
绑定该事件的元素本身才能触发事件(本元素的子元素不能触发) -
.once
只绑定一次 -
.passive
能够提升移动端的性能
修饰符可以复合使用,但是要注意顺序,前面的修饰符会影响后方的修饰符。
11.3 按键修饰符
Vue 允许为 v-on
在监听键盘事件时添加按键修饰符:
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
可以直接将 KeyboardEvent.key
暴露的任意有效按键名转换为 kebab-case 来作为修饰符。
11.4 系统修饰键
以下按键用来对应系统按键,可以限定仅当这些按键被按下时才会触发相应的事件监听器。
.ctrl
.alt
.shift
.meta
.exact
修饰符
限制当且仅当某个按键按下时才触发事件,组合按键等不会触发。用于精确控制组合触发的事件。
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>
鼠标按钮修饰符
.left
.right
.middle
这些修饰符会限制处理函数仅响应特定的鼠标按钮。
12.表单输入绑定
12.1 基础用法
可以用 v-model
指令在表单 <input>
、<textarea>
及 <select>
元素上创建双向数据绑定,v-model
中不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用
value
property 和input
事件; - checkbox 和 radio 使用
checked
property 和change
事件; - select 字段将
value
作为 prop 并将change
作为事件。
文本
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
多行文本
直接在textarea中插值不会生效,可以使用v-model
来代替
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
复选框
Vue可以将单个复选框的值绑定到某个对象,或者将多个复选框的值绑定到一个数组中:
1.绑定到对象
<input type="checkbox" id="checkbox" v-model="checked">
2.绑定到数组
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
new Vue({
data: {
checked: false,
checkedNames: []
}
})
如果没有赋予标签value属性,则会按照bool类型传递该选项框是否选中。
单选框
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
选择框
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
与复选框相同,如果选择框是多选的(具有multiple属性),则可以绑定到一个数组中。
选项可以定义在data中的数组里,通过v-for
来进行渲染。
12.2 值绑定
对于单选、复选和选择框的选项,v-model
绑定的值通常是静态字符串,对于复选框也可以是bool值。同时,也可以通过v-bind
将值绑定到一个动态property上,此时它可以是其他类型的值。
复选框
<input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no"
>
单选按钮
<input type="radio" v-model="pick" v-bind:value="a">
选择框的选项
<select v-model="selected">
<!-- 内联对象字面量 -->
<option v-bind:value="{ number: 123 }">123</option>
</select>
当选中时
typeof vm.selected // => 'object'
vm.selected.number // => 123
13.组件基础
13.1 组件声明
通过Vue.component()
函数自定义一个组件:
Vue.component('组件名', {
data: function () {
//方法
},
template: 'html模板标签'
})
也可以通过这种方式实现:
// 构造器 声明
const comp = Vue.extend({
template: `
<h2>这是一个组件</h2>
`
});
// vue实例中挂载注册为局部组件
components: {
comp
}
// 全局组件
Vue.component('comp', comp);
组件本质上是可复用的 Vue 实例,并且有一个名字,在创建一个实例时,可以通过el选项,指明某个元素的id,将其作为这个组件的挂载目标。
new Vue({ el: '#app' }) // id选择器
new Vue({ el: '.app'}) // class选择器
new Vue({ el: 'div'}) // html类型选择器
Vue会管理el选项命中的元素及其内部的后代元素,可以使用其他选择器,但是建议使用id选择器。如果 render 函数和 template 属性都不存在,挂载 DOM 元素的 HTML 会被提取出来用作模板,此时,必须使用 Runtime + Compiler 构建的 Vue 库。
组件与 new Vue
接收相同的选项,例如 data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像 el
这样根实例特有的选项。
data选项
一个组件的data选项必须是一个函数,从而每个实例可以维护一份被返回对象的独立的拷贝。
13.2 组件复用
一个组件可以进行任意次数的复用,且每一次复用都会有一个独立的实例被创建,它们相互之间不会干扰。
13.3 组件的组织
组件通常以树形结构来组织,为了能在模板中使用组件,组件必须先进行注册,注册有两种形式:
全局注册
Vue.component('my-component', {
// options
})
全局注册后组件可以在项目的任意位置使用
局部注册
如果只在某些模块中局部使用,则可以在这些模块的export部分中引入这些组件:
export default {
components: {
ComponentA,
ComponentC
},
// ...
}
13.4 Prop的使用
Prop的作用是在组件上注册一些自定义的attribute,当一个值传递给一个prop attribute的时候,就成为了这个组件实例的一个property。组件可以有多个prop,放在一个props数组中:
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
使用时为title attribute赋予不同的值,可以使实例中的title替换为对应的值。prop也可以使用v-bind
指令来动态传递。
13.5 单个根元素
每个Vue组件都必须只有一个根元素,即结构上必须有一个元素能够包含其他所有元素。如果组件会随着进度变得越来越复杂,那么应该让组件接收一个数组,具体的内容需要时再去拓展。
13.6 监听子组件事件
父级组件可以通过v-on
指令,像处理native DOM事件一样,监听子组件实例的任意事件,同时子组件可以通过调用内建的$emit
方法并传入事件名称来触发某个事件。
使用事件抛出一个值
$emit
的第二个参数可以用来提供这个值:
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
在父组件监听这个事件的时候,可以通过$event
来访问这个被抛出的值:
<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>
如果处理这个事件的是一个函数,那么这个值可以直接通过函数的参数传入。
在组件上使用v-model
自定义时间也可以用于创建支持v-model
的自定义输入组件:
<input v-model="searchText">
等价于
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
或者是
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
为了保证它的正常工作,这个组件内的必须:
- 将value绑定到一个交value的prop上
- 在input事件被触发时,将新的值通过自定义的input事件抛出
即是这样的:
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
13.7 插槽
插槽通过<slot name=''></slot>
的形式声明,每个插槽会有一个名字,如果省略了name属性,则会有一个默认名称交default。插槽用来预留内容占位符,未来可能会有内容插入到这个部分而不会影响其他组件。
13.8 动态组件
动态组件即动态的切换组件的显示与隐藏,vue通过is属性动态指定要渲染的组件。因为要渲染的组件是不确定的,所以要通过data申明一个变量用来接收组件的名称,用 :is 动态绑定这个变量到**<component>**组件中,通过按钮添加事件改变变量的值来动态切换组件。
默认情况下,切换动态组件时无法保持组件的状态,此时可以使用vue内置的<keep-alive>组件保持动态组件的状态。keep-alive
有两个对应的生命周期函数:
- 当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。
- 当组件被激活时,会自动触发组件的 activated 生命周期函数。
13.9 解析DOM模板时的注意事项
有些 HTML 元素,诸如 <ul>
、<ol>
、<table>
和 <select>
,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>
、<tr>
和 <option>
,只能出现在其它某些特定的元素内部。
例如在table中使用自定义元素时,会因为它不属于table的内部元素而报错,Vue的解决方案是使用is属性,声明table中允许的内部元素,并使用is attribute将其指定为我们自定义的元素。
二、深入组件
1.组件注册
1.1 组件名
组件注册时需要在Vue.component
函数的第一个参数这里指定一个组件名,组件名有两种方式:
kebab-case
Vue.component('my-component-name', { /* ... */ })
PascalCase
Vue.component('MyComponentName', { /* ... */ })
使用第一种形式时,引用自定义元素的时候也要使用这种形式,而使用PascalCase时,两种命名方法均可使用。
1.2 组件注册的两种形式
全局注册:
通过Vue.component
函数创建的组件时全局注册的,在注册之后可以用在任何新创建的Vue根实例的模板中。包括子组件内部。
局部注册:
局部注册用于只在特定模块使用某个组件时,通过普通的JavaScript对象来定义组件,之后通过新建实例的components部分来引入希望使用的组件:
var ComponentA = { /* ... */ }
new Vue({
el: '#app',
components: {
'component-a': ComponentA
}
})
局部注册的组件在其子组件中是不可用的,
1.3 模块系统
在模块系统中局部注册:
创建一个 components
目录,并将每个组件放置在其各自的文件中。在局部注册之前,导入想要使用的每个组件,就可以使组件在指定的系统中使用。
基础组件的自动化全局注册:
基础组件一般只包含简单的输入框或按钮之类的元素,它们可能会被频繁引用,为了避免出现冗长的基础组件的导入列表,可以使用 require.context
只全局注册这些非常通用的基础组件。(依赖Vue CLI 3+)
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
// 其组件目录的相对路径
'./components',
// 是否查询其子目录
false,
// 匹配基础组件文件名的正则表达式
/Base[A-Z]\w+\.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
// 获取组件配置
const componentConfig = requireComponent(fileName)
// 获取组件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 获取和目录深度无关的文件名
fileName
.split('/')
.pop()
.replace(/\.\w+$/, '')
)
)
// 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})
全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生
2.Prop
2.1 Prop的大小写
HTML中的attribute名是大小写不敏感的,使用DOM模板时,所有camelCase的prop名需要使用等价的kebab-case名:
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>
使用字符模板则不存在这个限制。
2.2 Prop 类型
可以在声明时以对象的形式列出这些prop:
props: {
title: String,
isPublished: Boolean
}
2.3 静态或动态prop
prop可以直接在元素标签内赋静态值,或者通过v-bind
绑定动态变量的值。通过v-bind
可以明确告诉Vue我们期望传递的是一个表达式的值,而不是一个静态的字符串。任何类型都可以传递给prop。
传入一个对象所有的property:
post: {
id: 1,
title: 'My Journey with Vue'
}
使用:
<blog-post v-bind="post"></blog-post>
相当于:
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>
该对象的property将自动分别绑定到元素的id和title上。
2.4 单向数据流
Vue多级prop之间是单行向下绑定的,父级prop的更新会向下流动到子组件中,反之则不流通。每次父级组件发生变更时,子组件中所有的prop都将会刷新称为最新的值,子组件内部不应该改变prop。
2.5 Prop验证
Prop的验证方式可以在定义时,提供一个带有验证需求的对象,例如:
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].includes(value)
}
}
}
})
类型检查:
type可以是以下原生构造函数中的一个:
String
Number
Boolean
Array
Object
Date
Function
Symbol
或者,可以是某个自定义类型的构造函数,并且通过instanceof
来进行检查。
2.6 非Prop的Attribute
非prop的attribute指的是传向一个组件,但是给组件并没有相应prop定义的attribute。常见的包括class,style和id属性。
因为显式定义的 prop 适用于向一个子组件传入信息,然而组件库的作者并不总能预见组件会被用于怎样的场景。这也是为什么组件可以接受任意的 attribute,而这些 attribute 会被添加到这个组件的根元素上。
替换/合并已有的attribute
对于绝大部分的attribute来说,外部提供给组件的值会替换掉组件内部设置好的值,而对于class和style attribute,外部的值和原始值将会合并成为一个最终值。
禁用attribute继承
如果不希望组件的根元素继承attribute,可以在组件选项中设置inheritAttrs : false
,然后通过指令v-bind="$attrs"
的方式把外部传入的非prop属性设置到目标标签上。这个操作不会影响到class和style的行为。
这个模式允许你在使用基础组件的时候更像是使用原始的 HTML 元素,而不会担心哪个元素是真正的根元素。
3.自定义事件
3.1 事件名
触发某个事件时,事件名需要完全匹配监听器所监听的事件名。
this.$emit('myEvent')
完全匹配事件名才能监听到事件:
<my-component v-on:myEvent="doSomething"></my-componen
事件应始终使用kebab-case形式命名
3.2 自定义组件的v-model
一个组件上的v-model
会默认利用名为value的prop和名为input的事件,使用
<custom-input v-model="something"></custom-input>
在自定义组件中相当于
<custom-input :value="something" @input="something = $event.target.value"></custom-input>
但是某些输入控件的value attribute可能会用于其他目的,比如作为接收的数据,则可以在组件声明中使用model选项来避免冲突。
3.3 绑定原生事件到组件
想要在一个组件的根元素上直接监听一个原生事件,可以使用 v-on
的 .native
修饰符:
<base-input v-on:focus.native="onFocus"></base-input>
Vue 提供了一个 $listeners
property,它是一个对象,里面包含了作用在这个组件上的所有监听器。例如:
{
focus: function (event) { /* ... */ }
input: function (value) { /* ... */ },
}
可以通过使用v-on:"$listeners"
的方式将所有事件监听器指向这个组件的某个特定子元素,从而使某些封装的组件像基础的组件一样使用,所有相同的attribute和监听器都可以工作,不需要使用.native
监听器了。
3.4 .sync
修饰符
有些情况下,我们需要对一个prop进行双向绑定,有时候子组件可以改变父组件,但是在父子组件两侧都看不到明显的变更来源,因此推荐使用update: propName
的模式触发事件来代替双向绑定,在触发事件时可以使用如下形式:
this.$emit('update:title', newTitle)
然后父组件来监听这个事件并根据需要更新本地数据的property:
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
以上模式可以使用sync修饰符缩写为:
<text-document v-bind:title.sync="doc.title"></text-document>
当我们使用一个对象同时设置多个prop的时候,也可以使用这种组合形式。
4.插槽
4.1 插槽内容
在定义某个模板时,可以使用<slot></slot>
来留下一个插槽,这里未来可能会插入某些内容,如将一个组件定义为类似这样:
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
然后这样去使用它:
<navigation-link url="/profile">
Your Profile
</navigation-link>
则在渲染时,<slot></slot>
会被替换为标签内的值:Your Profile,插槽内可以包含任意模板代码,包括html甚至组件。如果这个组件没有包含slot元素,那么该标签中间的任何内容都会被抛弃。
4.2 编译作用域
插槽内的内容可以跟模板的其他地方访问到相同的实例property,但是不能访问组件的作用域,如上方代码的url在插槽中就不可见。父级模板里的所有内容是在父级作用域中编译的,子模板则在自己的作用域中编译。
4.3 后备内容
插槽标签内可以添加内容,作为这个插槽默认渲染的内容,如果在使用组件时没有提供任何插槽内容,那么默认的内容将始终被渲染,反之则默认内容被提供的内容覆盖。
<button type="submit">
<slot>Submit</slot>
</button>
4.4 具名插槽
插槽可以添加name属性来指定名称,默认会有一个名字为default。对于具名插槽,向其提供内容时,可以在<template>元素上使用v-slot
指令,并以v-slot
的参数的形式提供名称。
定义时:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
使用时:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
<template>
元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot
的 <template>
中的内容都会被视为默认插槽的内容。中间部分也可以使用一个插入default插槽的template标签来包裹。
4.5 作用域插槽
有些情况下,需要让插槽内容能够访问子组件中才有的数据,但是子组件内部的数据只有它本身可以访问,内容是在父级渲染的,为了让这些数据在父级插槽中可用,可以将这些数据以attribute的形式绑定到插槽中:
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
这些attribute被称为插槽prop,这样就可以在父作用域中使用这些数据:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
5.动态组件、一部组件
5.1 使用keepalive
动态组件,以多标签页面为例,可以使用is attribute来切换不同的组件:
<component v-bind:is="currentTabComponent"></component>
当这些组件相互切换时,每一次切换标签都会重新创建一个组件实例,如果希望第一次创建某个组件时,就将其内容缓存下来,以避免反复重新渲染导致性能问题,就可以使用\<keep-alive>
元素将其动态组件包裹起来。
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
5.2 异步组件
在大型应用中,某些模块会被分割成很多小代码块,并且只在需要的时候从服务器加载对应模块。Vue允许通过工厂函数的方式定义一个组件,这个工厂函数会异步解析组件的定义,只有在这个组件需要被渲染的时候才会触发该工厂函数,并把结果缓存起来供未来重新渲染。
三、响应式的原理
3.1 如何追踪变化
把一个普通的JavaScript对象传入Vue实例作为data选项时,Vue将会遍历这个对象所有的property,并使用Object.defineProperty
方法为这些property设置setter和getter,该特性在ES5中无法使用。这些getter和setter对于用户是不可见的,只能在Vue内部使用。
每个组件实例都对应一个wathcer实例,它会在组件渲染的过程中把接触过的数据property记录为依赖,之后当依赖项的setter触发时,会通知watcher,而使它关联的组件重新渲染。
3.2 检测变化的注意事项
由于JavaScript的限制,Vue不能检测数组和对象的变化。因此,针对这两种情况,需要使用特殊手段。
对象
Vue无法检测到property的添加或移除,由于Vue在初始化时会对property进行getter和setter的转换,所以property必须定义在data对象上才能被转换为响应式的。对于已创建的实例,Vue不允许动态添加根级别的响应式property,但是可以使用Vue.set(object, property, value)
方法,向嵌套对象添加响应式property。也可以使用这个方法的别名$set
,如:
Vue.set(vm.obj, 'b', 2)
this.$set(this.obj, 'b', 2)
数组
Vue不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
为了解决第一类问题,可以使用以下两种方式:
Vue.set(vm.items, indexOfItem, newValue)
或
vm.items.aplice(indexOfItem, 1, newValue)
对于第一种方式,同样可以使用别名$set
。
为了解决第二类问题,可以使用``splice`:
vm.items.splice(newLength)
3.3 声明响应式property
Vue不允许动态添加根级响应式property,所以你必须在初始化实例前声明所有property,如果暂时用不到,可以赋空值。如果未在data中声明message,Vue将警告你渲染函数正在试图访问不存在的property。
3.4 异步更新队列
Vue在更新DOM时是异步执行的,侦听到数据变化时,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更,如果同一个watcher被触发多次,只会被推入队列一次。这种在缓冲时去除重复数据可以避免不必要的计算和DOM操作。在进入到下一个事件循环tick中,Vue刷新队列并执行实际的工作。Vue在内部对异步队列尝试使用原生的方法:Promise.then
, MutationObserver
, setImmediate
,如果执行环境不支持,则会采用setTimeout(fn, 0)
代替。
了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)
。这样回调函数将在 DOM 更新完成后被调用。例如:
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
因为 $nextTick()
返回一个 Promise
对象,可以使用新的 ES2017 async/await 语法完成相同的事情:
methods: {
updateMessage: async function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
await this.$nextTick()
console.log(this.$el.textContent) // => '已更新'
}
}