这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于 Vue 1.0 ,可以参考我的另一篇文章 Vue.js 1.0 Official Guide Notes。
Vue 实例
属性与方法
-
不要在实例属性或者回调函数中(如
vm.$watch('a', newVal => this.myMethod())
)使用箭头函数。因为箭头函数绑定父上下文,所以this
不会像预想的一样是Vue
实例,而是this.myMethod
未被定义
实例生命周期
-
生命周期钩子
var vm = new Vue({ data: { a: 1 }, created: function () { // `this` 指向 vm 实例 console.log('a is: ' + this.a) } }) // -> "a is: 1"
也有一些其它的钩子,在实例生命周期的不同阶段调用,如
mounted
、updated
、destroyed
。钩子的this
指向调用它的 Vue 实例。Vue 没有 Controller ,组件的自定义逻辑可以分布在这些钩子中。
生命周期图示
模板语法
- 在 Vue 2 中,提供了支持 JSX 语法的
render
函数。
插值
文本
-
使用
v-once
指令执行一次性插值<span v-once>This will never change: {{ msg }}</span>
纯 HTML
-
使用
v-html
指令输出纯 HTML<div v-html="rawHtml"></div>
- 被插入的内容都会被当做 HTML ,数据绑定会被忽略。
- 不能使用
v-html
来复合局部模板。 - 只对可信内容使用 HTML 插值,绝不要对用户提供的内容插值。(避免 XSS )
属性
-
Mustache 不能在 HTML 属性中使用,应使用
v-bind
指令(包括布尔值)。<div v-bind:id="dynamicId"></div>
使用 JavaScript 表达式
- Vue.js 对于所有的数据绑定都提供了完全的 JavaScript 表达式支持。
- 每个绑定只能包含单个表达式,不能是语句(比如:赋值语句)也不能是流程控制(使用三元表达式替代)。
- 模板表达式被放在沙盒中,只能访问全局变量的一个白名单,如
Math
和Date
。不能模板表达式中访问用户定义的全局变量。
指令
- 指令(Directives)是带有
v-
前缀的特殊属性。其属性的值预期是单一 JavaScript 表达式(除了v-for
)。 - 指令的职责就是当其表达式的值改变时相应地将某些行为应用到 DOM 上。
参数
- 一些指令能接受一个“参数”,在指令后以冒号指明。
-
v-bind
指令被用来响应地更新 HTML 属性:<a v-bind:href="url"></a>
-
v-on
指令,它用于监听 DOM 事件:<a v-on:click="doSomething">
-
修饰符
- 修饰符(Modifiers)是以半角句号
.
指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。-
.prevent
修饰符告诉v-on
指令对于触发的事件调用event.preventDefault()
:<form v-on:submit.prevent="onSubmit"></form>
-
过滤器
过滤器被用作一些常见的文本格式化。
过滤器可以用在两个地方:mustache 插值和
v-bind
表达式。-
过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符指示。
<!-- in mustaches --> {{ message | capitalize }} <!-- in v-bind --> <div v-bind:id="rawId | formatId"></div>
在 Vue 2 中,过滤器只能在 mustache 绑定和
v-bind
表达式中使用,对于更复杂的数据变换应当使用计算属性。-
过滤器函数总接受表达式的值作为第一个参数。
new Vue({ // ... filters: { capitalize: function (value) { if (!value) return '' value = value.toString() return value.charAt(0).toUpperCase() + value.slice(1) } } })
-
过滤器可以串联。
{{ message | filterA | filterB }}
-
过滤器是 JavaScript 函数,因此可以接受参数。
{{ message | filterA('arg1', arg2) }} // 字符串 'arg1' 将传给过滤器作为第二个参数, arg2 表达式的值将被求值然后传给过滤器作为第三个参数
计算属性
计算属性
- 在模板中放入太多的逻辑会让模板过重且难以维护。
计算属性 vs Methods
对于最终结果,计算属性与 Method 是相同的。
-
计算属性是基于它们的依赖进行缓存的,其只有在它的相关依赖发生改变时才会重新求值。
-
当然这也意味着有些计算属性求值后将不会更新,比如
Date.now()
,因为它不是响应式依赖。computed: { now: function () { return Date.now() } }
-
相比而言,只要发生重新渲染,method 调用总会执行该函数。
Computed 属性 vs Watched 属性
- Computed 属性能更好的处理多个数据的变动,而不需要为每一个数据单独书写 watch 函数。
计算 setter
-
计算属性默认只有 getter ,但允许提供 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] } } } // ...
现在在运行
vm.fullName = 'John Doe'
时, setter 会被调用,vm.firstName
和vm.lastName
也相应地会被更新。
观察 Watchers
当想要在数据变化响应时,执行异步操作或开销较大的操作,
watch
会很实用。-
举个栗子:
<div id="watch-example"> <p> Ask a yes/no question: <input v-model="question"> </p> <p>{{ answer }}</p> </div>
<!-- Since there is already a rich ecosystem of ajax libraries --> <!-- and collections of general-purpose utility methods, Vue core --> <!-- is able to remain small by not reinventing them. This also --> <!-- gives you the freedom to just use what you're familiar with. --> <script src="https://unpkg.com/axios@0.12.0/dist/axios.min.js"></script> <script src="https://unpkg.com/lodash@4.13.1/lodash.min.js"></script> <script> var watchExampleVM = new Vue({ el: '#watch-example', data: { question: '', answer: 'I cannot give you an answer until you ask a question!' }, watch: { // 如果 question 发生改变,这个函数就会运行 question: function (newQuestion) { this.answer = 'Waiting for you to stop typing...' this.getAnswer() } }, methods: { // _.debounce 是一个通过 lodash 限制操作频率的函数。 // 在这个例子中,我们希望限制访问yesno.wtf/api的频率 // ajax请求直到用户输入完毕才会发出 // 学习更多关于 _.debounce function (and its cousin // _.throttle), 参考: https://lodash.com/docs#debounce getAnswer: _.debounce( function () { var vm = this if (this.question.indexOf('?') === -1) { vm.answer = 'Questions usually contain a question mark. ;-)' return } vm.answer = 'Thinking...' axios.get('https://yesno.wtf/api') .then(function (response) { vm.answer = _.capitalize(response.data.answer) }) .catch(function (error) { vm.answer = 'Error! Could not reach the API. ' + error }) }, // 这是我们为用户停止输入等待的毫秒数 500 ) } }) </script>
在这个示例中,使用
watch
选项允许我们执行异步操作(访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这是计算属性无法做到的。除了 watch 选项之外,还有
vm.$watch
命令。
Class 与 Style 绑定
绑定 HTML Class
对象语法
v-bind:class
指令可以与普通的 class 属性共存。-
可以直接绑定数据里的一个对象。
<div v-bind:class="classObject"></div>
data: { classObject: { active: true, 'text-danger': false } }
-
也可以在这里绑定返回对象的计算属性。
<div v-bind:class="classObject"></div>
data: { isActive: true, error: null }, computed: { classObject: function () { return { active: this.isActive && !this.error, 'text-danger': this.error && this.error.type === 'fatal', } } }
数组语法
可以把一个数组传给
v-bind:class
,以应用一个 class 列表。-
如果需要根据条件切换列表中的 class ,可以用三元表达式。
<div v-bind:class="[isActive ? activeClass : '', errorClass]"> // 始终添加 errorClass ,但只有在 isActive 是 true 时添加 activeClass
-
可以在数组语法中使用对象语法。
<div v-bind:class="[{ active: isActive }, errorClass]">
用在组件上
- 在一个定制的组件上用到
class
属性的时候,这些类将被添加到根元素上面,这个元素上已经存在的类不会被覆盖。
绑定内联样式
- 和 Class 类似。
条件渲染
v-if
- 使用
v-if
选择性的渲染(不是显示)元素。
v-else-if
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
- 类似于
v-else
,v-else-if
必须紧跟在v-if
或者v-else-if
元素之后。
用 key
管理可复用的元素
Vue 通常会复用已有元素而不是从头开始渲染,以提升运行效率。
-
例如:如果允许用户在不同的登录方式之间切换
<template v-if="loginType === 'username'"> <label>Username</label> <input placeholder="Enter your username"> </template> <template v-else> <label>Email</label> <input placeholder="Enter your email address"> </template>
- 上面的代码中切换
loginType
将不会清除用户已经输入的内容。因为两个模版使用了相同的元素,<input>
不会被替换掉——仅仅是替换了它的的placeholder
。
- 上面的代码中切换
-
有些情况下这样做不符合需求。Vue 提供了一种方式来声明两个元素的独立性:添加一个具有唯一值的
key
属性。<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>
- 注意,
<label>
元素仍然会被高效地复用,因为它们没有添加key
属性。
- 注意,
v-show
- 使用
v-show
根据条件展示元素。带有v-show
的元素始终会被渲染并保留在 DOM 中。v-show
是简单地切换元素的 CSS 属性display
。 -
v-show
不支持<template>
语法,也不支持v-else
。
v-if
vs v-show
- 一般来说,
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show
较好;如果在运行时条件不太可能改变,则使用v-if
较好。
v-if
与 v-for
一起使用
- 当
v-if
与v-for
一起使用时,v-for
具有比v-if
更高的优先级。
列表渲染
v-for
基本用法
-
在
v-for
块中,我们拥有对父作用域属性的完全访问权限。v-for
还支持一个可选的第二个参数为当前项的索引。<ul id="example-2"> <li v-for="(item, index) in items"> {{ parentMessage }} - {{ index }} - {{ item.message }} </li> </ul>
var example2 = new Vue({ el: '#example-2', data: { parentMessage: 'Parent', items: [ { message: 'Foo' }, { message: 'Bar' } ] } })
可以用
of
替代in
作为分隔符。
Template v-for
-
可以用带有
v-for
的<template>
标签来渲染多个元素块。<ul> <template v-for="item in items"> <li>{{ item.msg }}</li> <li class="divider"></li> </template> </ul>
对象迭代 v-for
-
可以用
v-for
通过一个对象的属性来迭代。<ul id="repeat-object" class="demo"> <li v-for="value in object"> {{ value }} </li> </ul>
new Vue({ el: '#repeat-object', data: { object: { FirstName: 'John', LastName: 'Doe', Age: 30 } } })
-
也可以提供第二个的参数为键名,第三个参数作为索引。
<div v-for="(value, key) in object"> {{ key }} : {{ value }} </div>
<div v-for="(value, key, index) in object"> {{ index }}. {{ key }} : {{ value }} </div>
在遍历对象时,是按
Object.keys()
的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下是一致的。
整数迭代 v-for
<div>
<span v-for="n in 10">{{ n }}</span>
</div>
组件 和 v-for
在自定义组件中,可以像普通元素一样用
v-for
。然而他不能自动传递数据到组件里,因为组件有自己独立的作用域。-
使用
props
传递迭代数据到组件里。<my-component v-for="(item, index) in items" v-bind:item="item" v-bind:index="index"> </my-component>
不自动注入
item
到组件里的原因是,因为这使得组件会紧密耦合到v-for
如何运作。在一些情况下,明确数据的来源可以使组件可重用。
key
当 Vue.js 用
v-for
正在更新已渲染过的元素列表时,它默认用 “就地复用“ 策略。如果数据项的顺序被改变,Vue 将不是移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。这个类似 Vue 1.x 的track-by="$index"
。这个默认的模式是有效的,但是只适用于不依赖子组件状态或临时 DOM 状态(例如:表单输入值)的列表渲染输出。
-
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,要为每项提供一个唯一
key
属性。理想的key
值是每项都有唯一id
。这个特殊的属性相当于 Vue 1.x 的track-by
,但它的工作方式类似于一个属性,所以要用v-bind
来绑定动态值。<div v-for="item in items" :key="item.id"> <!-- 内容 --> </div>
建议尽可能使用
v-for
来提供key
,除非迭代 DOM 内容足够简单,或者你是故意要依赖于默认行为来获得性能提升。key
并不特别与v-for
关联,key
还具有其他用途。
数组更新检测
变异方法
- 修改当前数组的方法,查阅 API。
重塑数组
- 使用一个新的数组代替原始数组。
- Vue 实现了一些智能启发式方法来最大化 DOM 元素重用,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
注意事项
- 由于 JavaScript 的限制, Vue 不能检测以下变动的数组:
- 当你利用索引直接设置一个项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
- 当你利用索引直接设置一个项时,例如:
显示过滤 / 排序结果
-
通过创建返回过滤或排序数组的计算属性来显示一个数组的过滤或排序副本,而不实际改变或重置原始数据。
<li v-for="n in evenNumbers">{{ n }}</li>
data: { numbers: [ 1, 2, 3, 4, 5 ] }, computed: { evenNumbers: function () { return this.numbers.filter(function (number) { return number % 2 === 0 }) } }
-
在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中) 使用 method 方法实现。
<li v-for="n in even(numbers)">{{ n }}</li>
data: { numbers: [ 1, 2, 3, 4, 5 ] }, methods: { even: function (numbers) { return numbers.filter(function (number) { return number % 2 === 0 }) } }
事件处理器
内联处理器方法
-
有时也需要在内联语句处理器中访问原生 DOM 事件。可以用特殊变量
$event
把它传入方法:(使用内联语句的时候因为包含自定参数,默认不传递event
)<button v-on:click="warn('Form cannot be submitted yet.', $event)">Submit</button>
// ... methods: { warn: function (message, event) { // 现在我们可以访问原生事件对象 if (event) event.preventDefault() alert(message) } }
事件修饰符
-
Vue.js 为
v-on
提供了 事件修饰符。通过由点(.
)表示的指令后缀来调用修饰符。<!-- 阻止单击事件冒泡 --> <a v-on:click.stop="doThis"></a> <!-- 提交事件不再重载页面 --> <form v-on:submit.prevent="onSubmit"></form> <!-- 修饰符可以串联 --> <a v-on:click.stop.prevent="doThat"></a> <!-- 只有修饰符 --> <form v-on:submit.prevent></form> <!-- 添加事件侦听器时使用事件捕获模式 --> <div v-on:click.capture="doThis">...</div> <!-- 只当事件在该元素本身(而不是子元素)触发时触发回调 --> <div v-on:click.self="doThat">...</div> <!-- 2.14 新增:点击事件将只会触发一次 --> <a v-on:click.once="doThis"></a>
.once
修饰符还能被用到自定义的组件事件上。如果你还没有阅读关于组件的文档,现在大可不必担心。
按键修饰符
-
Vue 允许为
v-on
在监听键盘事件时添加按键修饰符。<!-- 只有在 keyCode 是 13 时调用 vm.submit() --> <input v-on:keyup.13="submit">
-
记住所有的 keyCode 比较困难,所以 Vue 为最常用的按键提供了别名。
<!-- 同上 --> <input v-on:keyup.enter="submit"> <!-- 缩写语法 --> <input @keyup.enter="submit">
-
可以通过全局
config.keyCodes
对象自定义按键修饰符别名。// 可以使用 v-on:keyup.f1 Vue.config.keyCodes.f1 = 112
注意:在Mac系统键盘上,meta对应命令键 (⌘)。在Windows系统键盘meta对应windows徽标键(⊞)。在Sun操作系统键盘上,meta对应实心宝石键 (◆)。在其他特定键盘上,尤其在MIT和Lisp键盘及其后续,比如Knight键盘,space-cadet键盘,meta被标记为“META”。在Symbolics键盘上,meta被标记为“META” 或者 “Meta”。
为什么在 HTML 中监听事件?
- 当一个 ViewModel 被销毁时,所有的事件处理器都会自动被删除。你无须担心如何自己清理它们。
表单控件绑定
基础用法
- 对于要求 IME (如中文、 日语、 韩语等) 的语言,你会发现
v-model
不会在 ime 构成中得到更新。如果你也想实现更新,请使用input
事件。
绑定 value
- 对于单选按钮,勾选框及选择列表选项,
v-model
绑定的value
通常是静态字符串(对于勾选框是逻辑值)。 - 可以用
v-bind
绑定 value 到 Vue 实例的一个动态属性上,这个属性的值可以不是字符串。
复选框
<input
type="checkbox"
v-model="toggle"
v-bind:true-value="a"
v-bind:false-value="b"
>
// 当选中时
vm.toggle === vm.a
// 当没有选中时
vm.toggle === vm.b
单选按钮
<input type="radio" v-model="pick" v-bind:value="a">
// 当选中时
vm.pick === vm.a
选择列表设置
<select v-model="selected">
<!-- 内联对象字面量 -->
<option v-bind:value="{ number: 123 }">123</option>
</select>
// 当选中时
typeof vm.selected // -> 'object'
vm.selected.number // -> 123
修饰符
.lazy
-
在默认情况下,
v-model
在input
事件中同步输入框的值与数据 (除了 上述 IME 部分),但你可以添加一个修饰符lazy
,从而转变为在change
事件中同步。<!-- 在 "change" 而不是 "input" 事件中更新 --> <input v-model.lazy="msg" >
.number
- 如果想自动将用户的输入值转为 Number 类型(如果原值的转换结果为 NaN 则返回原值),可以添加一个修饰符
number
给v-model
来处理输入值。- 这通常很有用,因为在
type="number"
时 HTML 中输入的值也总是会返回字符串类型。
- 这通常很有用,因为在
.trim
-
如果要自动过滤用户输入的首尾空格,可以添加
trim
修饰符到v-model
上过滤输入。<input v-model.trim="msg">
v-model
与组件
- Vue 的组件系统允许你创建一个具有自定义行为可复用的
input
类型,这些input
类型甚至可以和v-model
一起使用!要了解更多,请参阅自定义input
类型
组件
使用组件
DOM 模板解析说明
当使用 DOM 作为模版时(例如,将
el
选项挂载到一个已存在的元素上), 你会受到 HTML 的一些限制,因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模版内容。尤其像这些元素<ul>
,<ol>
,<table>
,<select>
限制了能被它包裹的元素,<option>
只能出现在其它元素内部。-
在自定义组件中使用这些受限制的元素时会导致一些问题,例如:
<table> <my-row>...</my-row> </table>
自定义组件
<my-row>
被认为是无效的内容,因此在渲染的时候会导致错误。变通的方案是使用特殊的is
属性:<table> <tr is="my-row"></tr> </table>
-
应当注意,如果您使用来自以下来源之一的字符串模板,这些限制将不适用:
<script type="text/x-template">
- JavaScript内联模版字符串
-
.vue
组件
data
必须是函数
- 通过 Vue 构造器传入的各种选项大多数都可以在组件里用。
data
是一个例外,它必须是函数。
构成组件
- 在 Vue.js 中,父子组件的关系可以总结为 props down, events up 。父组件通过
props
向下传递数据给子组件,子组件通过events
给父组件发送消息。
Prop
camelCase vs kebab-case
- 使用字符串模板时,不需要将 camelCase 转换成 kebab-case 。
字面量语法 vs 动态语法
-
使用字面量语法传递数值会得到字符串。
<!-- 传递了一个字符串"1" --> <comp some-prop="1"></comp>
-
如果想传递一个实际的
number
,需要使用v-bind
,从而让它的值被当作 JavaScript 表达式计算。<!-- 传递实际的mumber --> <comp v-bind:some-prop="1"></comp>
单向数据流
不应该在子组件内部改变
prop
,Vue 会在控制台给出警告。-
prop 作为初始值传入后,子组件想把它当作局部数据来用:定义一个局部变量,并用 prop 的值初始化它。
props: ['initialCounter'], data: function () { return { counter: this.initialCounter } }
-
prop 作为初始值传入,由子组件处理成其它数据输出:定义一个计算属性,处理 prop 的值并返回。
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果
prop
是一个对象或数组,在子组件内部改变它会影响父组件的状态。
Prop 验证
-
我们可以为组件的
props
指定验证规格。如果传入的数据不符合规格,Vue 会发出警告。要指定验证规格,需要用对象的形式,而不能用字符串数组。Vue.component('example', { props: { // 基础类型检测 (`null` 意思是任何类型都可以) 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 value > 10 } } } })
type
可以是String
Number
Boolean
Function
Object
Array
,type
也可以是一个自定义构造器函数,使用instanceof
检测。当 prop 验证失败,Vue会在抛出警告 (如果使用的是开发版本)。
自定义事件
子组件通过自定义事件向父组件回传数据。
-
每个 Vue 实例都实现了事件接口:
- 使用
$on(eventName)
监听事件。 - 使用
$emit(eventName)
触发事件。
- 使用
Vue 的事件系统分离自浏览器的 EventTarget API 。尽管它们的运行类似,但是
$on
和$emit
不是addEventListener
和dispatchEvent
的别名。-
父组件可以在使用子组件的地方直接用
v-on
来监听子组件触发的事件。-
不能用
$on
侦听子组件抛出的事件,而必须在模板里直接用v-on
绑定。
<div id="counter-event-example"> <p>{{ total }}</p> <button-counter v-on:increment="incrementTotal"></button-counter> <button-counter v-on:increment="incrementTotal"></button-counter> </div>
Vue.component('button-counter', { template: '<button v-on:click="increment">{{ counter }}</button>', data: function () { return { counter: 0 } }, methods: { increment: function () { this.counter += 1 this.$emit('increment') } }, }) new Vue({ el: '#counter-event-example', data: { total: 0 }, methods: { incrementTotal: function () { this.total += 1 } } })
子组件已经和它外部完全解耦了。它所做的只是报告自己的内部事件,至于父组件是否关心则与它无关。留意到这一点很重要。
-
不能用
-
在某个组件的根元素上监听一个原生事件。可以使用
.native
修饰v-on
。<my-component v-on:click.native="doTheThing"></my-component>
使用自定义事件处理表单输入组件
-
自定义事件可以用来创建自定义的表单输入组件,使用
v-model
来进行数据双向绑定。<input v-model="something">
等价于:
<input v-bind:value="something" v-on:input="something = $event.target.value">
在组件中使用时,它相当于下面的简写:
<custom-input v-bind:value="something" v-on:input="something = arguments[0]"></custom-input>
-
要让组件的
v-model
生效,它必须:- 接受一个
value
属性。 - 在有新的
value
时触发 input 事件。
- 接受一个
非父子组件通信
-
在简单的场景下,可以使用一个空的 Vue 实例作为中央事件总线。
var bus = new Vue() s = new Vue()
// 触发组件 A 中的事件 bus.$emit('id-selected', 1)
// 在组件 B 创建的钩子中监听事件 bus.$on('id-selected', function (id) { // ... })
在复杂的情况下,考虑使用专门的状态管理模式。
使用 slot 分发内容
作用域插槽
作用域插槽是一种特殊类型的插槽,用作使用一个(能够传递数据到)可重用模板替换已渲染元素。
-
在子组件中,只需将数据传递到插槽,就像你将
prop
传递给组件一样:<div class="child"> <slot text="hello from child"></slot> </div>
-
在父级中,具有特殊属性
scope
的<template>
元素,表示它是作用域插槽的模板。scope
的值对应一个临时变量名,此变量接收从子组件中传递的prop
对象:<div class="parent"> <child> <template scope="props"> <span>hello from parent</span> <span>{{ props.text }}</span> </template> </child> </div>
-
渲染结果:
<div class="parent"> <div class="child"> <span>hello from parent</span> <span>hello from child</span> </div> </div>
-
作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项:
<my-awesome-list :items="items"> <!-- 作用域插槽也可以在这里命名 --> <template slot="item" scope="props"> <li class="my-fancy-item">{{ props.text }}</li> </template> </my-awesome-list>
列表组件模板:
<ul> <slot name="item" v-for="item in items" :text="item.text"> <!-- fallback content here --> </slot> </ul>
动态组件
-
通过使用保留的
<component>
元素,动态地绑定到它的is
特性,我们让多个组件可以使用同一个挂载点,并动态切换:var vm = new Vue({ el: '#example', data: { currentView: 'home' }, components: { home: { /* ... */ }, posts: { /* ... */ }, archive: { /* ... */ } } })
<component v-bind:is="currentView"> <!-- 组件在 vm.currentview 变化时改变! --> </component>
也可以直接绑定到组件对象上:
var Home = { template: '<p>Welcome home!</p>' } var vm = new Vue({ el: '#example', data: { currentView: Home } })
keep-alive
-
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个
keep-alive
指令参数:<keep-alive> <component :is="currentView"> <!-- 非活动组件将被缓存! --> </component> </keep-alive>
- 详见 API 参考。
杂项
子组件索引
-
可以使用
ref
为子组件指定一个索引 ID 以在 JavaScript 中直接访问子组件。<div id="parent"> <user-profile ref="profile"></user-profile> </div>
var parent = new Vue({ el: '#parent' }) // 访问子组件 var child = parent.$refs.profile
当 ref 和 v-for 一起使用时, ref 是一个数组或对象,包含相应的子组件。
$refs
只在组件渲染完成后才填充,并且它是非响应式的。它仅仅作为一个直接访问子组件的应急方案——应当避免在模版或计算属性中使用$refs
。
异步组件
Vue.js 允许将组件定义为一个工厂函数,动态地解析组件的定义。Vue.js 只在组件需要渲染时触发工厂函数,并且把结果缓存起来,用于后面的再次渲染。
-
一个例子
Vue.component('async-example', function (resolve, reject) { setTimeout(function () { resolve({ template: '<div>I am async!</div>' }) }, 1000) })
工厂函数接收一个
resolve
回调,在收到从服务器下载的组件定义时调用。也可以调用reject(reason)
指示加载失败。这里setTimeout
只是为了演示。怎么获取组件完全由你决定。推荐配合使用 Webpack 的代码分割功能Vue.component('async-webpack-example', function (resolve) { // 这个特殊的 require 语法告诉 webpack // 自动将编译后的代码分割成不同的块, // 这些块将通过 ajax 请求自动下载。 require(['./my-async-component'], resolve) })
递归组件
-
有
name
选项的组件可以在模板内部递归调用自己。var StackOverflow = Vue.extend({ name: 'stack-overflow', template: '<div>' + // 递归地调用它自己 '<stack-overflow></stack-overflow>' + '</div>' })
上面组件会导致一个错误 “max stack size exceeded”,所以要确保递归调用有终止条件。
组件间的循环引用(Circular References Between Components)
内联模板
-
如果子组件有
inline-template
特性,组件将把它的内容当做它的模板,而不是分发内容。<my-component inline-template> <p>These are compiled as the component's own template</p> <p>Not parent's transclusion content.</p> </my-component>
inline-template
让模板的作用域难以理解,并且不能缓存模板编译结果。最佳实践是使用template
选项在组件内定义模板或者在.vue
文件中使用template
元素。
X-Templates
-
另一种定义模版的方式是在 JavaScript 标签里使用 text/x-template 类型,并且指定一个id。
<script type="text/x-template" id="hello-world-template"> <p>Hello hello hello</p> </script>
Vue.component('hello-world', { template: '#hello-world-template' })
这在有很多模版或者小的应用中有用,否则应该避免使用,因为它将模版和组件的其他定义隔离了。
对低开销的静态组件使用 v-once
-
尽管在 Vue 中渲染 HTML 很快,不过当组件中包含大量静态内容时,可以考虑使用 v-once 将渲染结果缓存起来。
Vue.component('terms-of-service', { template: '\ <div v-once>\ <h1>Terms of Service</h1>\ ... a lot of static content ...\ </div>\ ' })
深入响应式原理
- Vue 使用
Object.defineProperty
把 Vue 实例的data
中的属性全部转换为 getter / setter 。 - 在内部,Vue 会追踪依赖,在属性被访问和修改时通知变化。
- 每个组件实例都有相应的
watcher
实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter
被调用时,会通知watcher
重新计算,从而致使它关联的组件得以更新。
变化检测问题
由于 Vue 会在初始化实例时对属性执行
getter/setter
转化过程,所以属性必须在data
对象上存在才能让 Vue 转换它,这样才能让它是响应的。-
Vue 不允许在已经创建的实例上动态添加新的根级响应式属性(root-level reactive property)。然而它可以使用
Vue.set(object, key, value)
方法将响应属性添加到嵌套的对象上,也可以使用vm.$set
实例方法,这也是全局Vue.set
方法的别名。Vue.set(vm.someObject, 'b', 2) this.$set(this.someObject,'b',2)
使用
Object.assign()
或_.extend()
等方法添加到对象上的新属性不会触发更新。-
可以创建一个新的对象,让它包含原对象的属性和新的属性:
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })` this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
声明响应式属性
- 必须在初始化实例前声明根级响应式属性,哪怕只是一个空值。
异步更新队列
Vue 异步执行 DOM 更新。
-
为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用
Vue.nextTick(callback)
。这样回调函数在 DOM 更新完成后就会调用。<div id="example">{{message}}</div>
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 })
-
在组件内使用
vm.$nextTick()
实例方法特别方便,因为它不需要全局 Vue ,并且回调函数中的this
将自动绑定到当前的 Vue 实例上。Vue.component('example', { template: '<span>{{ message }}</span>', data: function () { return { message: 'not updated' } }, methods: { updateMessage: function () { this.message = 'updated' console.log(this.$el.textContent) // => '没有更新' this.$nextTick(function () { console.log(this.$el.textContent) // => '更新完成' }) } } })
过渡效果
概述
- Vue 允许多种过渡动画:
- 在 CSS 过渡和动画中自动应用 class
- 配合使用第三方 CSS 动画库
- 在过渡钩子函数中使用 JavaScript 直接操作 DOM
- 可以配合使用第三方 JavaScript 动画库,如 Velocity.js
单元素 / 组件的过渡
- Vue 提供了
transition
的封装组件。在下列情形中,可以为元素或组件添加 entering / leaving 效果:- v-if
- v-show
- 动态组件
- 组件根节点
- 当插入或删除包含在
transition
组件中的元素时,Vue 会进行以下处理:- 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加 / 删除 CSS 类名。
- 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数会在恰当时机被调用。
- 如果没有找到钩子函数也没有检测到 CSS 过渡 / 动画,DOM 操作在下一帧中立即执行。
过渡的 CSS 类型
-
Vue 包含 4 个( CSS ) 类名在 enter / leave 的过渡中切换。
-
v-enter
:定义进入过渡的开始状态。在元素被插入时生效,在下一个帧移除。 -
v-enter-active
:定义进入过渡的结束状态。在元素被插入时生效,在transition/animation
完成之后移除。 -
v-leave
:定义离开过渡的开始状态。在离开过渡被触发时生效,在下一个帧移除。 -
v-leave-active
:定义离开过渡的结束状态。在离开过渡被触发时生效,在transition/animation
完成之后移除。
-
使用
<transition name="my-transition">
可以重置类名的v-
前缀。
CSS 动画 与 CSS 过渡
- 在 CSS animation 中,
v-enter
类名在节点插入 DOM 后不会立即删除。而是在animationend
事件触发时删除。
自定义过渡类名
-
<transition>
允许通过以下属性自定义过渡类名:enter-class
enter-active-class
leave-class
leave-active-class
同时使用 Transitions 和 Animations
Vue 为了知道过渡的完成,必须设置相应的事件监听器。它可以是 transitionend
或 animationend
,这取决于给元素应用的 CSS 规则。如果你使用其中任何一种,Vue 能自动识别类型并设置监听。
但是,在一些场景中,你需要给同一个元素同时设置两种过渡动效,比如 animation
很快的被触发并完成了,而 transition
效果还没结束。在这种情况中,你就需要使用 type
特性并设置 animation
或 transition
来明确声明你需要 Vue 监听的类型。
JavaScript 钩子
- 可以在
<transition>
的属性中声明 JavaScript 钩子:-
@before-enter
:function (el)
-
@enter
:function(el, done)
-
@after-enter
:function (el)
-
@enter-cancelled
:function (el)
-
@before-leave
:function (el)
-
@leave
:function(el, done)
-
@after-leave
:function (el)
-
@leave-cancelled
:function (el)
-
- 钩子函数可以结合 CSS 过渡动画一起使用,也可以单独使用,但是:
- 当只使用 JavaScript 过渡的时候,在
enter
和leave
中,回调函数done
是必须的。否则它们会被同步调用,过渡会立即完成。 - 推荐对于仅使用 JavaScript 过渡的
<transition>
添加:css="false"
,Vue 会跳过 CSS 检测。
- 当只使用 JavaScript 过渡的时候,在
初始渲染的过渡
可以通过 appear
特性设置节点在初始化渲染时的过渡。
<transition appear>
...
</transition>
-
这里的
<transition>
同样允许自定义 CSS 类名或 JavaScript 钩子:<transition appear appear-class="custom-appear-class" appear-active-class="custom-appear-active-class" > <!-- ... --> </transition> <transition appear v-on:before-appear="customBeforeAppearHook" v-on:appear="customAppearHook" v-on:after-appear="customAfterAppearHook" > <!-- ... --> </transition>
多个元素的过渡
对于原生标签,可以使用
v-if / v-else
在控制多个元素的过渡。当有相同标签名的元素切换时,要通过
key
特性设置唯一的值来标记以让 Vue 区分它们。即使在技术上没有必要,给在<transition>
组件中的多个元素设置 key 是一个更好的实践。-
使用多个
v-if
的多个元素的过渡可以重写为绑定了动态属性的单个过渡元素。<transition> <button v-if="docState === 'saved'" key="saved"> Edit </button> <button v-if="docState === 'edited'" key="edited"> Save </button> <button v-if="docState === 'editing'" key="editing"> Cancel </button> </transition>
重写为
<transition> <button v-bind:key="docState"> {{ buttonMessage }} </button> </transition>
// ... computed: { buttonMessage: function () { switch (docState) { case 'saved': return 'Edit' case 'edited': return 'Save' case 'editing': return 'Cancel' } } }
过渡模式
-
<transition>
在使用v-if
的多组件上作用过渡时,进入和离开会同时发生,这有可能导致布局错乱或样式重叠。可以在过渡的时候对元素使用绝对定位。(可以实现 button 滑动切换的效果) - 为了解决上述问题,Vue 提供了过渡模式:
-
in-out
新元素先进行过渡,完成之后当前元素过渡离开。 -
out-in
当前元素先进行过渡,完成之后新元素过渡离开。
-
多个组件的过渡
-
组件过渡不需要使用
key
特性,只需要使用<component>
构建的动态组件即可。<transition name="component-fade" mode="out-in"> <component v-bind:is="view"></component> </transition>
列表过渡
- 使用
<transition-group>
渲染列表。 - 不同于
<transition>
,<transition-group>
会以一个真实的元素呈现。默认为一个<span>
,也可以通过tag
属性更换。 - 内部元素总是需要提供一个唯一的
key
值。
列表的进入和离开过渡
-
<transition-group>
同样支持自定义前缀或自定义类名,与<transition>
完全相同。
列表的位移过渡
<transition-group>
组件可以改变定位。使用其v-move
特性,它会在元素改变定位的过程中应用。可以使用name
改变前缀,也可以使用move-class
手动指定类名。-
Vue 使用了一个叫 FLIP 的简单的动画队列,使用
transforms
将元素从之前的位置平滑过渡新的位置。.list-item { display: inline-block; margin-right: 10px; } .list-enter-active, .list-leave-active { transition: all 1s; } .list-enter, .list-leave-active { opacity: 0; transform: translateY(30px); }
FLIP 同样支持多维网格过渡。
列表的渐进过渡
- 通过 data 属性与 JavaScript 通信,可以实现列表的渐进过渡。技巧是设置 delay 。(可以参考官方文档的示例)
可复用的过渡
- 把
<transition>
封装成一个组件,即可进行复用。
动态过渡
- 动态过渡最基本的例子是通过
name
特性来绑定动态值。 - 利用过渡事件,根据组件的状态通过 JavaScript 过渡设置不同的过渡效果。
- 创建动态过渡的最终方案是组件通过接受
props
来动态修改之前的过渡。
过渡状态
- 对于数据元素本身的动态效果,Vue 允许结合第三方库实现切换动画。
状态动画与 watcher
- 通过 watcher 我们能监听到任何属性的数值更新,并可以获取到更新前和更新后的两组数值。这样,就可以利用第三方库,结合旧数据和新数据构建过渡动画了。
动态状态转换
通过组件组织过渡
- 将一些通用的动画效果提取到专用的子组件里。
Render 函数
-
一个例子
Vue.component('anchored-heading', { render: function (createElement) { return createElement( 'h' + this.level, // tag name 标签名称 this.$slots.default // 子组件中的阵列 ) }, props: { level: { type: Number, required: true } } })
需要知道当你不使用
slot
属性向组件中传递内容时, 这些子元素被存储在组件实例中的 $slots.default中。详见 instance properties API
createElement
参数
-
createElement
接受的参数:// @returns {VNode} createElement( // {String | Object | Function} // 一个 HTML 标签字符串,组件选项对象,或者一个返回值类型为String/Object的函数,必要参数 'div', // {Object} // 一个包含模板相关属性的数据对象 // 这样,您可以在 template 中使用这些属性.可选参数. { // (详情见下一节) }, // {String | Array} // 子节点(VNodes),可以是一个字符串或者一个数组. 可选参数. [ createElement('h1', 'hello world'), createElement(MyComponent, { props: { someProp: 'foo' } }), 'bar' ] )
createElement
的第二个参数是可选参数,且必须接收一个Object
。
深入 data object 对象
-
在 VNode 数据对象中,下列属性名是级别最高的字段。
{ // 和`v-bind:class`一样的 API 'class': { foo: true, bar: false }, // 和`v-bind:style`一样的 API style: { color: 'red', fontSize: '14px' }, // 正常的 HTML 特性 attrs: { id: 'foo' }, // 组件 props props: { myProp: 'bar' }, // DOM 属性 domProps: { innerHTML: 'baz' }, // 事件监听器基于 "on" // 所以不再支持如 v-on:keyup.enter 修饰器 // 需要手动匹配 keyCode。 on: { click: this.clickHandler }, // 仅对于组件,用于监听原生事件,而不是组件内部使用 vm.$emit 触发的事件。 nativeOn: { click: this.nativeClickHandler }, // 自定义指令. 注意事项:不能对绑定的旧值设值 // Vue 会为您持续追踪 directives: [ { name: 'my-custom-directive', value: '2' expression: '1 + 1', arg: 'foo', modifiers: { bar: true } } ], // Scoped slots in the form of // { name: props => VNode | Array<VNode> } scopedSlots: { default: props => h('span', props.text) }, // 如果组件是其他组件的子组件,需为slot指定名称 slot: 'name-of-slot' // 其他特殊顶层属性 key: 'myKey', ref: 'myRef' }
约束
-
VNode 必须唯一:组件树中的所有 VNodes 必须是唯一的。下面的 render function 无效:
render: function (createElement) { var myParagraphVNode = createElement('p', 'hi') return createElement('div', [ // 错误-重复的VNodes myParagraphVNode, myParagraphVNode ]) }
-
使用工厂函数来实现重复:
render: function (createElement) { return createElement('div', Array.apply(null, { length: 20 }).map(function () { return createElement('p', 'hi') }) ) }
使用 JavaScript 代替模板功能
v-if
and v-for
-
在 render 函数中使用 JavaScript 的 if/else 和 map 重写
v-if
和v-for
:render: function (createElement) { if (this.items.length) { return createElement('ul', this.items.map(function (item) { return createElement('li', item.name) })) } else { return createElement('p', 'No items found.') } }
v-model
-
render
函数中没有与v-model
相应的 API - 你必须自己来实现相应的逻辑render: function (createElement) { var self = this return createElement('input', { domProps: { value: self.value }, on: { input: function (event) { self.value = event.target.value } } }) }
事件 & 按键修饰符
-
一个例子:
on: { '!click': this.doThisInCapturingMode, '~keyup': this.doThisOnce, `~!mouseover`: this.doThisOnceInCapturingMode }
slots
-
可以从
this.$slots
获取 VNodes 列表中的静态内容:render: function (createElement) { // <div><slot></slot></div> return createElement('div', this.$slots.default) }
-
然后从
this.$scopedSlots
中以返回 VNodes 的函数返回值的形式访问局部 slots 。render: function (createElement) { // <div><slot :text="msg"></slot></div> return createElement('div', [ this.$scopedSlots.default({ text: this.msg }) ]) }
-
在 render function 中,使用 VNode data 中的
scopedSlots
域向子组件传递传递局部 slots 。render (createElement) { return createElement('div', [ createElement('child', { // pass scopedSlots in the data object // in the form of { name: props => VNode | Array<VNode> } scopedSlots: { default: function (props) { return createElement('span', props.text) } } }) ]) }
JSX
-
Babel plugin 插件,用于在 Vue 中使用 JSX 语法。
import AnchoredHeading from './AnchoredHeading.vue' new Vue({ el: '#demo', render (h) { return ( <AnchoredHeading level={1}> <span>Hello</span> world! </AnchoredHeading> ) } })
将
h
作为createElement
的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的,如果在作用域中h
失去作用, 在应用中会触发报错。
函数化组件
-
一个例子
Vue.component('my-component', { functional: true, // 为了弥补缺少的实例 // 提供第二个参数作为上下文 render: function (createElement, context) { // ... }, // Props 可选 props: { // ... } })
详见函数化组件
slots()
和 children
对比
-
对于下面这个组件:
<my-functional-component> <p slot="foo"> first </p> <p>second</p> </my-functional-component>
-
children
会给你两个段落标签,而slots().default
只会传递第二个匿名段落标签,slots().foo
会传递第一个具名段落标签。
-
可以选择让组件通过
slot()
系统分发或者简单的通过 children 接收,让其他组件去处理。
自定义指令
基础
-
Vue.js 允许用户通过
Vue.directive(id, definition)
方法注册一个全局自定义指令,接收两个参数:指令 ID 和 定义对象。也可以用组件的directive
选项注册一个局部自定义指令。// 注册一个全局自定义指令 v-focus Vue.directive('focus', { // 当绑定元素插入到 DOM 中。 inserted: function (el) { // 聚焦元素 el.focus() } })
directives: { focus: { // 指令的定义--- } }
使用:
<input v-focus>
钩子函数
- 指令定义函数提供了几个钩子函数(可选):
-
bind
: 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。 -
inserted
: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。 -
update
: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新(详细的钩子函数参数见下)。 -
componentUpdated
: 被绑定元素所在模板完成一次更新周期时调用。 -
unbind
: 只调用一次, 指令与元素解绑时调用。
-
钩子函数参数
- 钩子函数被赋予了以下参数:
el
: 指令所绑定的元素,可以用来直接操作 DOM 。-
binding
: 一个对象,包含以下属性:-
name
: 指令名,不包括v-
前缀。 -
value
: 指令的绑定值, 例如:v-my-directive="1 + 1"
,value
的值是2
。 -
oldValue
: 指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。 -
expression
: 绑定值的字符串形式。 例如v-my-directive="1 + 1"
,expression
的值是"1 + 1"
。 -
arg
: 传给指令的参数。例如v-my-directive:foo
,arg
的值是"foo"
。 -
modifiers
: 一个包含修饰符的对象。 例如:v-my-directive.foo.bar
,修饰符对象modifiers
的值是{ foo: true, bar: true }
。
-
vnode
: Vue 编译生成的虚拟节点,查阅 VNode API 了解更多详情。oldVnode
: 上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。除了
el
之外,其它参数都应该是只读的,尽量不要修改他们。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。-
一个例子:
<div id="hook-arguments-example" v-demo:hello.a.b="message"></div>
Vue.directive('demo', { bind: function (el, binding, vnode) { var s = JSON.stringify el.innerHTML = 'name: ' + s(binding.name) + '<br>' + 'value: ' + s(binding.value) + '<br>' + 'expression: ' + s(binding.expression) + '<br>' + 'argument: ' + s(binding.arg) + '<br>' + 'modifiers: ' + s(binding.modifiers) + '<br>' + 'vnode keys: ' + Object.keys(vnode).join(', ') } }) new Vue({ el: '#hook-arguments-example', data: { message: 'hello!' } })
函数简写
-
大多数情况下,我们可能想在
bind
和update
钩子上做重复动作,并且不想关心其它的钩子函数。可以这样写:Vue.directive('color-swatch', function (el, binding) { el.style.backgroundColor = binding.value })
对象字面量
-
如果指令需要多个值,可以传入一个 JavaScript 对象字面量。指令函数能够接受所有合法类型的 JavaScript 表达式。
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (el, binding) { console.log(binding.value.color) // => "white" console.log(binding.value.text) // => "hello!" })
混合
基础
-
混合以一种灵活的方式为组件提供分布复用功能。混合对象可以包含任意的组件选项。当组件使用了混合对象时,混合对象的所有选项将被“混入”组件自己的选项中。
// 定义一个混合对象 var myMixin = { created: function () { this.hello() }, methods: { hello: function () { console.log('hello from mixin!') } } } // 定义一个组件,使用这个混合对象 var Component = Vue.extend({ mixins: [myMixin] }) var component = new Component() // -> "hello from mixin!"
选项合并
- 当混合对象与组件包含同名选项时,这些选项将以适当的策略合并。
- 混合的钩子将在组件自己的钩子之前调用。
- 注意
Vue.extend()
使用同样的合并策略。 - 可以参考这里
全局混合
-
一旦使用全局混合对象,将会影响到所有之后创建的 Vue 实例。使用恰当时,可以为自定义对象注入处理逻辑。但是通常不建议使用。语法如下:
// 为自定义的选项 'myOption' 注入一个处理器。 Vue.mixin({ created: function () { var myOption = this.$options.myOption if (myOption) { console.log(myOption) } } }) new Vue({ myOption: 'hello!' }) // -> "hello!"
自定义选项混合策略
单文件组件
- 可以使用 vue-cli 或者参考我的另一篇文章:用 Vue + vue-router + Webpack 构建单页 Web APP (SPA)
- 官方文档:单文件组件
生产环境部署
- 详见生产环境部署
单元测试
[整理中][官方文档]
服务端渲染
[整理中][官方文档]