1. 扩展Vue构造器*
可以扩展 Vue
构造器,从而用预定义选项创建可复用的组件构造器:
尽管可以命令式地创建扩展实例,不过在多数情况下建议将组件构造器注册为一个自定义元素,然后声明式地用在模板中。 所有的 Vue.js 组件其实都是被扩展的 Vue 实例。var MyComponent = Vue.extend({// 扩展选项})// 所有的 `MyComponent` 实例都将以预定义的扩展选项被创建var myComponentInstance = new MyComponent()
按照我自己的理解就是, 可以通过Vue.extend获取Vue构造器, 然后通过传递一个对象并且在对象里面添加一些属性、方法等从而创建出一个新的构造器, 而且这个构造器具备Vue构造器的所有特性, 后面就可以通过这个新建的构造器去创建对象.
2.属性与方法*
每个 Vue 实例都会代理其 data
对象里所有的属性:
var data = { a: 1 }
var vm = new Vue({
data: data
})
vm.a === data.a // -> true
// 设置属性也会影响到原始数据
vm.a = 2
data.a // -> 2
// ... 反之亦然
data.a = 3
vm.a // -> 3
注意只有这些被代理的属性是
响应的,也就是说值的任何改变都是触发视图的重新渲染。如果在实例创建之后添加新的属性到实例上,它不会触发视图更新。
除了 data 属性, Vue 实例暴露了一些有用的实例属性与方法。这些属性与方法都有前缀 $
,以便与代理的 data 属性区分。例如:
var data = { a: 1 }var vm = new Vue({el: '#example',data: data})vm.$data === data // -> truevm.$el === document.getElementById( 'example') // -> true
3.实例生命周期
每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如,实例需要配置数据观测(data observer)、编译模版、挂载实例到 DOM ,然后在数据变化时更新 DOM 。在这个过程中,实例也会调用一些生命周期钩子 ,这就给我们提供了执行自定义逻辑的机会。例如,created
这个钩子在实例被创建之后被调用:
|
也有一些其它的钩子,在实例生命周期的不同阶段调用,如 mounted
、updated
、destroyed
。钩子的this
指向调用它的 Vue 实例。
4.生命周期图示*
https://cn.vuejs.org/v2/guide/instance.html#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%9B%BE%E7%A4%BA查看这个链接就能找到图
5.插值
1).文本
数据绑定最常见的形式就是使用 “Mustache” 语法(双大括号)的文本插值:
|
Mustache 标签将会被替代为对应数据对象上 msg
属性的值。无论何时,绑定的数据对象上msg
属性发生了改变,插值处的内容都会更新。
通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。但请留心这会影响到该节点上所有的数据绑定:
|
2).纯 HTML
双大括号会将数据解释为纯文本,而非 HTML 。为了输出真正的 HTML ,你需要使用 v-html
指令:
|
这个 div
的内容将会被替换成为属性值 rawHtml
,直接作为 HTML —— 数据绑定会被忽略。
3).属性
Mustache 不能在 HTML 属性中使用,应使用 v-bind 指令:
|
这对布尔值的属性也有效 —— 如果条件被求值为 false 的话该属性会被移除:
|
4).使用 JavaScript 表达式
迄今为止,在我们的模板中,我们一直都只绑定简单的属性键值。但实际上,对于所有的数据绑定, Vue.js 都提供了完全的 JavaScript 表达式支持。
|
这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。
|
6.指令
指令(Directives)是带有 v-
前缀的特殊属性。指令属性的值预期是单一 JavaScript 表达式(除了v-for
,之后再讨论)。指令的职责就是当其表达式的值改变时相应地将某些行为应用到 DOM 上。让我们回顾一下在介绍里的例子:
|
这里, v-if
指令将根据表达式 seen
的值的真假来移除/插入 <p>
元素。
1).参数
一些指令能接受一个“参数”,在指令后以冒号指明。例如, v-bind
指令被用来响应地更新 HTML 属性:
|
在这里 href
是参数,告知 v-bind
指令将该元素的 href
属性与表达式url
的值绑定。
另一个例子是 v-on
指令,它用于监听 DOM 事件:
|
在这里参数是监听的事件名。我们也会更详细地讨论事件处理。
2).修饰符
修饰符(Modifiers)是以半角句号 .
指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent
修饰符告诉v-on
指令对于触发的事件调用event.preventDefault()
:
|
7.过滤器
Vue.js 允许你自定义过滤器,可被用作一些常见的文本格式化。过滤器可以用在两个地方:mustache 插值和 v-bind
表达式。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符指示:
|
Vue 2.x 中,过滤器只能在 mustache 绑定和 v-bind
表达式 (后者从 2.1.0 起支持) 中使用,因为过滤器设计目的就是用于文本转换。为了在其他指令中实现更复杂的数据变换,你应该使用计算属性。
过滤器函数总接受表达式的值作为第一个参数。
|
过滤器可以串联:
|
过滤器是 JavaScript 函数,因此可以接受参数:
|
这里,字符串 'arg1'
将传给过滤器作为第二个参数, arg2
表达式的值将被求值然后传给过滤器作为第三个参数。
8.缩写
v-bind
缩写
|
v-on
缩写
|
9.计算属性
模板内的表达式是非常便利的,但是它们实际上只用于简单的运算。在模板中放入太多的逻辑会让模板过重且难以维护。例如:
|
在这种情况下,模板不再简单和清晰。在意识到这是反向显示 message
之前,你不得不再次确认第二遍。当你想要在模板中多次反向显示 message 的时候,问题会变得更糟糕。
这就是对于任何复杂逻辑,你都应当使用计算属性的原因。
1).基础例子
|
|
结果:
Original message: "Hello"
Computed reversed message: "olleH"
这里我们声明了一个计算属性 reversedMessage
。我们提供的函数将用作属性 vm.reversedMessage
的 getter 。
|
2).计算属性 vs Methods
你可能已经注意到我们可以通过调用表达式中的 method 来达到同样的效果:
|
|
我们可以将同一函数定义为一个 method 而不是一个计算属性。对于最终的结果,两种方式确实是相同的。然而,不同的是计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。这就意味着只要message
还没有发生改变,多次访问reversedMessage
计算属性会立即返回之前的计算结果,而不必再次执行函数。
这也同样意味着下面的计算属性将不再更新,因为 Date.now()
不是响应式依赖:
|
相比而言,只要发生重新渲染,method 调用总会执行该函数。
3).计算 setter
计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter :
|
现在再运行 vm.fullName = 'John Doe'
时, setter 会被调用, vm.firstName
和vm.lastName
也相应地会被更新。
10.绑定 HTML Class
1).对象语法
我们可以传给 v-bind:class
一个对象,以动态地切换 class 。
|
上面的语法表示 classactive
的更新将取决于数据属性 isActive
是否为真值 。
我们也可以在对象中传入更多属性用来动态切换多个 class 。此外, v-bind:class
指令可以与普通的 class 属性共存。如下模板:
|
如下 data:
|
渲染为:
|
当 isActive
或者 hasError
变化时,class 列表将相应地更新。例如,如果 hasError
的值为 true
, class列表将变为 "static active text-danger"
。
你也可以直接绑定数据里的一个对象:
|
|
渲染的结果和上面一样。我们也可以在这里绑定返回对象的计算属性。这是一个常用且强大的模式:
|
data: {isActive: true,error: null},computed: {classObject: function () {return {active: this.isActive && ! this.error,'text-danger': this.error && this.error.type === 'fatal',}}}
2).数组语法
我们可以把一个数组传给 v-bind:class
,以应用一个 class 列表:
|
|
渲染为:
|
如果你也想根据条件切换列表中的 class ,可以用三元表达式:
|
此例始终添加 errorClass
,但是只有在 isActive
是 true 时添加 activeClass
。
不过,当有多个条件 class 时这样写有些繁琐。可以在数组语法中使用对象语法:
|
3).用在组件上
当你在一个定制的组件上用到 class
属性的时候,这些类将被添加到根元素上面,这个元素上已经存在的类不会被覆盖。
例如,如果你声明了这个组件:
|
然后在使用它的时候添加一些 class:
|
HTML 最终将被渲染成为:
|
同样的适用于绑定 HTML class :
|
当 isActive
为 true 的时候,HTML 将被渲染成为:
|
11.绑定内联样式
1).对象语法
v-bind:style
的对象语法十分直观——看着非常像 CSS ,其实它是一个 JavaScript 对象。 CSS 属性名可以用驼峰式 (camelCase) 或 (配合引号的) 短横分隔命名 (kebab-case):
|
|
直接绑定到一个样式对象通常更好,让模板更清晰:
|
|
同样的,对象语法常常结合返回对象的计算属性使用。
2).数组语法
v-bind:style
的数组语法可以将多个样式对象应用到一个元素上:
|
3).自动添加前缀
当 v-bind:style
使用需要特定前缀的 CSS 属性时,如transform
,Vue.js 会自动侦测并添加相应的前缀。
4).多重值
2.3.0+
从 2.3.0 起你可以为 style
绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值:
|
这会渲染数组中最后一个被浏览器支持的值。在这个例子中,如果浏览器支持不带浏览器前缀的 flexbox,那么渲染结果会是 display: flex
。
12.v-if
1).在 <template>
中配合 v-if
条件渲染一整组
因为 v-if
是一个指令,需要将它添加到一个元素上。但是如果我们想切换多个元素呢?此时我们可以把一个 <template>
元素当做包装元素,并在上面使用v-if
。最终的渲染结果不会包含<template>
元素。
|
2).v-else
你可以使用 v-else
指令来表示 v-if
的“else 块”:
|
v-else
元素必须紧跟在 v-if
或者 v-else-if
元素的后面——否则它将不会被识别。
3).v-else-if
2.1.0 新增
v-else-if
,顾名思义,充当 v-if
的“else-if 块”。可以链式地使用多次:
|
类似于 v-else
,v-else-if
必须紧跟在 v-if
或者 v-else-if
元素之后。
4).用 key
管理可复用的元素
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做,除了使 Vue 变得非常快之外,还有一些有用的好处。例如,如果你允许用户在不同的登录方式之间切换:
|
那么在上面的代码中切换 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>
13.v-show
另一个用于根据条件展示元素的选项是 v-show
指令。用法大致一样:
|
不同的是带有 v-show
的元素始终会被渲染并保留在 DOM 中。v-show
是简单地切换元素的 CSS 属性display
。
注意, v-show
不支持 <template>
语法,也不支持 v-else
。
14.v-if
vs v-show
v-if
是“真正的”条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下, v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说, v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show
较好;如果在运行时条件不太可能改变,则使用v-if
较好。
15.v-for
我们用 v-for
指令根据一组数组的选项列表进行渲染。 v-for
指令需要以 item in items
形式的特殊语法,items
是源数据数组并且item
是数组元素迭代的别名。
基本用法
|
|
在 v-for
块中,我们拥有对父作用域属性的完全访问权限。 v-for
还支持一个可选的第二个参数为当前项的索引。
|
var example2 = new Vue({el: '#example-2',data: {parentMessage: 'Parent',items: [{ message: 'Foo' },{ message: 'Bar' }]}})
你也可以用 of
替代 in
作为分隔符,因为它是最接近 JavaScript 迭代器的语法:
<div v-for="item of items"> </div>
1).Template v-for
如同 v-if
模板,你也可以用带有 v-for
的 <template>
标签来渲染多个元素块。例如:
|
2).对象迭代 v-for
你也可以用 v-for
通过一个对象的属性来迭代。
|
|
你也可以提供第二个的参数为键名:
|
第三个参数为索引:
<div v-for="(value, key, index) in object">{{ index }}. {{ key }} : {{ value }}</div>
3).整数迭代 v-for
v-for
也可以取整数。在这种情况下,它将重复多次模板。
|
4).组件 和 v-for
在自定义组件里,你可以像任何普通元素一样用 v-for
。
|
2.2.0+ 的版本里,当在组件中使用
v-for
时,key
现在是必须的。
然而他不能自动传递数据到组件里,因为组件有自己独立的作用域。为了传递迭代数据到组件里,我们要用 props
:
|
item
到组件里的原因是,因为这使得组件会紧密耦合到
v-for
如何运作。在一些情况下,明确数据的来源可以使组件可重用。
5).v-for
with v-if
当它们处于同一节点, v-for
的优先级比 v-if
更高,这意味着 v-if
将分别重复运行于每个v-for
循环中。当你想为仅有的一些 项渲染节点时,这种优先级的机制会十分有用,如下:
|
上面的代码只传递了未complete的todos。
而如果你的目的是有条件地跳过循环的执行,那么将 v-if
置于包装元素 (或 <template>
)上。如:
|
16.key
当 Vue.js 用 v-for
正在更新已渲染过的元素列表时,它默认用 “就地复用” 策略。如果数据项的顺序被改变,Vue将不是移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
这个默认的模式是有效的,但是只适用于不依赖子组件状态或临时 DOM 状态(例如:表单输入值)的列表渲染输出。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key
属性。理想的 key
值是每项都有唯一 id。它的工作方式类似于一个属性,所以你需要用 v-bind
来绑定动态值(在这里使用简写):
|
建议尽可能使用 v-for
来提供 key
,除非迭代 DOM 内容足够简单,或者你是故意要依赖于默认行为来获得性能提升。
因为它是 Vue 识别节点的一个通用机制, key
并不特别与 v-for
关联,key 还具有其他用途,我们将在后面的指南中看到其他用途。
17.数组更新检测
1).变异方法
Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新。这些方法如下:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
2).重塑数组
变异方法(mutation method),顾名思义,会改变被这些方法调用的原始数组。相比之下,也有非变异(non-mutating method)方法,例如:filter()
,concat()
,slice()
。这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组:
|
你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。 Vue 实现了一些智能启发式方法来最大化 DOM 元素重用,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
3).注意事项
由于 JavaScript 的限制, Vue 不能检测以下变动的数组:
- 当你利用索引直接设置一个项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue
相同的效果, 同时也将触发状态更新:
|
|
为了解决第二类问题,你可以使用 splice
:
|
4).显示过滤/排序结果*
有时,我们想要显示一个数组的过滤或排序副本,而不实际改变或重置原始数据。在这种情况下,可以创建返回过滤或排序数组的计算属性。
例如:
|
|
在计算属性不适用的情况下 (例如,在嵌套 v-for
循环中) 你可以使用一个 method 方法:
|
|
18.监听事件
可以用 v-on
指令监听 DOM 事件来触发一些 JavaScript 代码。
示例:
|
|
19.方法事件处理器
许多事件处理的逻辑都很复杂,所以直接把 JavaScript 代码写在 v-on
指令中是不可行的。因此 v-on
可以接收一个定义的方法来调用。
示例:
|
|
20.内联处理器方法
除了直接绑定到一个方法,也可以用内联 JavaScript 语句:
|
|
有时也需要在内联语句处理器中访问原生 DOM 事件。可以用特殊变量 $event
把它传入方法:
|
// ...methods: {warn: function (message, event) {// 现在我们可以访问原生事件对象if (event) event.preventDefault()alert(message)}}
21.事件修饰符
在事件处理程序中调用 event.preventDefault()
或 event.stopPropagation()
是非常常见的需求。尽管我们可以在 methods 中轻松实现这点,但更好的方式是:methods 只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题, Vue.js 为 v-on
提供了 事件修饰符。通过由点(.)表示的指令后缀来调用修饰符。
.stop
.prevent
.capture
.self
.once
|
@click.prevent.self
会阻止
所有的点击,而
@click.self.prevent
只会阻止元素上的点击。
2.1.4 新增
|
不像其它只能对原生的 DOM 事件起作用的修饰符,.once
修饰符还能被用到自定义的组件事件上. 如果你还没有阅读关于组件的文档,现在大可不必担心。
22.键值修饰符
在监听键盘事件时,我们经常需要监测常见的键值。 Vue 允许为 v-on
在监听键盘事件时添加关键修饰符:
|
记住所有的 keyCode 比较困难,所以 Vue 为最常用的按键提供了别名:
|
全部的按键别名:
.enter
.tab
.delete
(捕获 “删除” 和 “退格” 键).esc
.space
.up
.down
.left
.right
可以通过全局 config.keyCodes
对象自定义键值修饰符别名:
// 可以使用 v-on:keyup.f1Vue.config.keyCodes.f1 = 112
23.修饰键
2.1.0 新增
可以用如下修饰符开启鼠标或键盘事件监听,使在按键按下时发生响应。
.ctrl
.alt
.shift
.meta
注意:在Mac系统键盘上,meta对应命令键 (⌘)。在Windows系统键盘meta对应windows徽标键(⊞)。在Sun操作系统键盘上,meta对应实心宝石键 (◆)。在其他特定键盘上,尤其在MIT和Lisp键盘及其后续,比如Knight键盘,space-cadet键盘,meta被标记为“META”。在Symbolics键盘上,meta被标记为“META” 或者 “Meta”。
例如:
|
修饰键比正常的按键不同;修饰键和 keyup
事件一起用时,事件引发时必须按下正常的按键。换一种说法:如果要引发keyup.ctrl
,必须按下ctrl
时释放其他的按键;单单释放ctrl
不会引发事件
1).滑鼠按键修饰符
2.1.0 新增
.left
.right
.middle
这些修饰符会限制处理程序监听特定的滑鼠按键。
23.表单控件绑定
基础用法
你可以用 v-model
指令在表单控件元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model
本质上不过是语法糖,它负责监听用户的输入事件以更新数据,并特别处理一些极端的例子。
v-model
会忽略所有表单元素的 value
、checked
、selected
特性的初始值。因为它会选择 Vue 实例数据来作为具体的值。你应该通过 JavaScript 在组件的data
选项中声明初始值。
1).文本
|
2).多行文本
|
在文本区域插值( <textarea></textarea>
) 并不会生效,应用 v-model
来代替
3).复选框
单个勾选框,逻辑值:
|
多个勾选框,绑定到同一个数组:
|
new Vue({el: '...',data: {checkedNames: []}})
4).单选按钮
|
|
5).选择列表
单选列表:
|
|
v-model
表达初始的值不匹配任何的选项,
<select>
元素就会以”未选中”的状态渲染。
多选列表(绑定到一个数组):
|
new Vue({el: '#example-6',data: {selected: []}})
动态选项,用 v-for
渲染:
|
new Vue({el: '...',data: {selected: 'A',options: [{ text: 'One', value: 'A' },{ text: 'Two', value: 'B' },{ text: 'Three', value: 'C' }]}})
24.绑定 value
对于单选按钮,勾选框及选择列表选项, v-model
绑定的 value 通常是静态字符串(对于勾选框是逻辑值):
|
但是有时我们想绑定 value 到 Vue 实例的一个动态属性上,这时可以用 v-bind
实现,并且这个属性的值可以不是字符串。
1).复选框
|
|
2).单选按钮
|
|
3).选择列表设置
|
|
.lazy
在默认情况下, v-model
在 input
事件中同步输入框的值与数据 (除了上述 IME 部分),但你可以添加一个修饰符lazy
,从而转变为在change
事件中同步:
|
.number
如果想自动将用户的输入值转为 Number 类型(如果原值的转换结果为 NaN 则返回原值),可以添加一个修饰符number
给v-model
来处理输入值:
|
这通常很有用,因为在 type="number"
时 HTML 中输入的值也总是会返回字符串类型。
.trim
如果要自动过滤用户输入的首尾空格,可以添加 trim
修饰符到 v-model
上过滤输入:
|
25.什么是组件?
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以is
特性扩展。
26.使用组件
1).注册
之前说过,我们可以通过以下方式创建一个 Vue 实例:
|
要注册一个全局组件,你可以使用 Vue.component(tagName, options)
。例如:
|
组件在注册之后,便可以在父实例的模块中以自定义元素 <my-component></my-component>
的形式使用。要确保在初始化根实例之前注册了组件:
|
|
渲染为:
<div id="example"><div>A custom component! </div></div>
2).局部注册
不必在全局注册每个组件。通过使用组件实例选项注册,可以使组件仅在另一个实例/组件的作用域中可用:
|
这种封装也适用于其它可注册的 Vue 功能,如指令。
3).DOM 模版解析说明*
当使用 DOM 作为模版时 (例如,将 el
选项挂载到一个已存在的元素上), 你会受到 HTML 的一些限制,因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模版内容。尤其像这些元素<ul>
,<ol>
,<table>
,<select>
限制了能被它包裹的元素,而一些像<option>
这样的元素只能出现在某些其它元素内部。
在自定义组件中使用这些受限制的元素时会导致一些问题,例如:
|
自定义组件 <my-row>
被认为是无效的内容,因此在渲染的时候会导致错误。变通的方案是使用特殊的 is
属性:
|
应当注意,如果您使用来自以下来源之一的字符串模板,这些限制将不适用:
<script type="text/x-template">
- JavaScript 内联模版字符串
.vue
组件
因此,有必要的话请使用字符串模版。
4).data
必须是函数
通过 Vue 构造器传入的各种选项大多数都可以在组件里用。data
是一个例外,它必须是函数。实际上,如果你这么做:
|
那么 Vue 会停止,并在控制台发出警告,告诉你在组件中 data
必须是一个函数。理解这种规则的存在意义很有帮助,让我们假设用如下方式来绕开 Vue 的警告:
|
|
由于这三个组件共享了同一个 data
,因此增加一个 counter 会影响所有组件!这不对。我们可以通过为每个组件返回全新的 data 对象来解决这个问题:
|
现在每个 counter 都有它自己内部的状态了:
5).构成组件
组件意味着协同工作,通常父子组件会是这样的关系:组件 A 在它的模版中使用了组件 B。它们之间必然需要相互通信:父组件要给子组件传递数据,子组件需要将它内部发生的事情告知给父组件。然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性。
在 Vue 中,父子组件的关系可以总结为 props down, events up。父组件通过 props 向下传递数据给子组件,子组件通过events 给父组件发送消息。
27.Prop
1).使用 Prop 传递数据
组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。要让子组件使用父组件的数据,我们需要通过子组件的props 选项。
子组件要显式地用 props
选项声明它期待获得的数据:
|
然后我们可以这样向它传入一个普通字符串:
|
2).camelCase vs. kebab-case
HTML 特性是不区分大小写的。所以,当使用的不是字符串模版,camelCased (驼峰式) 命名的 prop 需要转换为相对应的 kebab-case (短横线隔开式) 命名:
|
|
如果你使用字符串模版,则没有这些限制。
3).动态 Prop
在模板中,要动态地绑定父组件的数据到子模板的 props,与绑定到任何普通的HTML特性相类似,就是用 v-bind
。每当父组件的数据变化时,该变化也会传导给子组件:
|
使用 v-bind
的缩写语法通常更简单:
|
4).字面量语法 vs 动态语法
初学者常犯的一个错误是使用字面量语法传递数值:
|
因为它是一个字面 prop,它的值是字符串 "1"
而不是 number。如果想传递一个实际的 number,需要使用 v-bind
,从而让它的值被当作 JavaScript 表达式计算:
|
4).单向数据流
prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。
另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。
5).Prop 验证
我们可以为组件的 props 指定验证规格。如果传入的数据不符合规格,Vue 会发出警告。当组件给其他人使用时,这很有用。
要指定验证规格,需要用对象的形式,而不能用字符串数组:
|
type
可以是下面原生构造器:
- String
- Number
- Boolean
- Function
- Object
- Array
- Symbol
type
也可以是一个自定义构造器函数,使用 instanceof
检测。
当 prop 验证失败,Vue 会在抛出警告 (如果使用的是开发版本)。注意 props 会在组件实例创建之前进行校验,所以在default
或 validator
函数里,诸如 data
、computed
或methods
等实例属性还无法使用。
28.非 Prop 属性
所谓非 prop
属性,就是它可以直接传入组件,而不需要定义相应的 prop
。
明确给组件定义 prop
是传参的推荐方式,但组件的作者并不总能预见到组件被使用的场景。所以,组件可以接收任意传入的属性,这些属性都会被添加到组件的根元素上。
例如,第三方组件 bs-date-input
,当它要和一个 Bootstrap 插件互操作时,需要在这个第三方组件的 input 上添加data-3d-date-picker
属性,这时可以把属性直接添加到组件上 (不需要事先定义 prop
):
|
添加属性 data-3d-date-picker="true"
之后,它会被自动添加到 bs-date-input
的根元素上
1).替换/覆盖现有的特性
想象一下这是 bs-date-input
的模板:
|
为了给该日期选择器插件增加一个特殊的主题,我们可能需要增加一个特殊的 class,比如:
|
在这个 case 当中,我们定义了两个不一样的 class
的值:
form-control
,来自组件的模板date-picker-theme-dark
,从父组件传进来的
对于多数特性来说,传递给组件的值会覆盖组件本身设定的值。即例如传递 type="large"
将会覆盖 type="date"
且有可能破坏该组件!索性我们对待class
和 style
特性会更聪明一些,这两个特性的值都会做合并 (merge) 操作,让最终生成的值为:form-control date-picker-theme-dark
。
29.自定义事件
1).使用 v-on
绑定自定义事件
每个 Vue 实例都实现了事件接口 (Events interface),即:
- 使用
$on(eventName)
监听事件 - 使用
$emit(eventName)
触发事件
Vue 的事件系统分离自浏览器的 EventTarget API。尽管它们的运行类似,但是 $on
和 $emit
不是addEventListener
和 dispatchEvent
的别名。
另外,父组件可以在使用子组件的地方直接用 v-on
来监听子组件触发的事件。
不能用 $on
侦听子组件抛出的事件,而必须在模板里直接用 v-on
绑定,就像以下的例子:
下面是一个例子:
|
|
this.$emit('increment')
在这里是什么意思呢?按我自己的大白话就是这样说的:
通过这句函数可以让父组件知道子组件调用了什么函数,this.$emit(‘increment’) 即类似于子组件跟父组件说了一声“hi,爸爸 我调用了我自己的increment函数”,通知父组件
v-on:increment="incrementTotal"
什么意思呢?我们还是用大白话来解释一下
就是说“孩子,当你调用了increment函数的时候,我将调用incrementTotal函数来回应你”
2).给组件绑定原生事件
有时候,你可能想在某个组件的根元素上监听一个原生事件。可以使用 .native
修饰 v-on
。例如:
|
.sync
修饰符
2.3.0+
在一些情况下,我们可能会需要对一个 prop 进行『双向绑定』。事实上,这正是 Vue 1.x 中的 .sync
修饰符所提供的功能。当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定的值。这很方便,但也会导致问题,因为它破坏了『单向数据流』的假设。由于子组件改变 prop 的代码和普通的状态改动代码毫无区别,当光看子组件的代码时,你完全不知道它何时悄悄地改变了父组件的状态。这在 debug 复杂结构的应用时会带来很高的维护成本。
上面所说的正是我们在 2.0 中移除 .sync
的理由。但是在 2.0 发布之后的实际应用中,我们发现 .sync
还是有其适用之处,比如在开发可复用的组件库时。我们需要做的只是让子组件改变父组件状态的代码更容易被区分。
从 2.3.0 起我们重新引入了 .sync
修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on
侦听器。
如下代码
|
会被扩展为:
|
当子组件需要更新 foo
的值时,它需要显式地触发一个更新事件:
|