基础

官方教程:https://cn.vuejs.org/v2/guide/

示例

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

<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
1
2
3
4
5
6
7
8
9
10
var vue = new Vue({
el: '#app', // 选择器
data: { // 渲染的数据
str: ' ',
arr: [' ', ' ', ' '],
arr2: [{ }, { }, { }]
},
computed: {},
methods: {}
})
1
2
3
4
<div id="app">
{{str}} <br/>
<input type="text" v-model="str"/>
</div>

标签语法

数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值:

1
<span>Message: {{ msg }}</span>

v-if与v-else

1
2
<div v-if="boolVar"> true的内容 </div>
<div v-else="boolVar"> false的内容 </div>

可以把一个 <template> 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 <template> 元素(v-for也可):

1
<template v-if="ok">...</template>

v-else-if

1
2
3
4
<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>

用key管理可复用的元素

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:

1
2
3
4
5
6
7
8
<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 attribute 即可:

1
2
3
4
5
6
7
8
<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 attribute。

v-show

另一个用于根据条件展示元素的选项是 v-show 指令。用法大致一样:

1
<h1 v-show="ok">Hello!</h1>

不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS property display

注意,v-show 不支持 <template> 元素,也不支持 v-else

v-if vs v-show

v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。

v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

v-for

v-for 块中,可以访问所有父作用域的 property。

遍历数组

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
<div id="app">
<ul>
<!-- 未排序 -->
<li v-for="val in ss">
{{val}}
</li>
<!-- 使用 computed 排序 -->
<li v-for="val in newSs">
{{val}}
</li>
<!-- 带索引 -->
<li v-for="(val, index) in ss">
{{index}}->{{val}}
</li>
</ul>
</div>
<script>
var name = new Vue({
el: '#app', // 选择器
data: { // 渲染的数据
ss: ['s1', 's3', 'S2']
},
computed: { // 运算
// 可指定排序算法(其实是个新的数组了)
newSs: function() {
// 如果不指定排序方法,默认首字母排序
return this.ss.sort(sortNum);
}
}
})

// 值从小到大排序
function sortNum(a, b) {
return a - b;
}
</script>

其中in可以替换为of

1
<div v-for="item of items"></div>

遍历对象

在遍历对象时,会按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。

1
2
3
<li v-for="value in object">
{{ value }}
</li>

也可以提供第二个的参数为 property 名称 (也就是键名):

1
2
3
<div v-for="(value, name) in object">
{{ name }}: {{ value }}
</div>

还可以用第三个参数作为索引:

1
2
3
<div v-for="(value, name, index) in object">
{{ index }}. {{ name }}: {{ value }}
</div>

维护状态

它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute。

1
2
3
<div v-for="item in items" v-bind:key="item.id">
<!-- 内容 -->
</div>

不要使用对象或数组之类的非基本类型值作为 v-forkey。请用字符串或数值类型的值。

数组更新检测

注意:由于 JavaScript 的限制,Vue 不能检测数组和对象的变化,只是侦听一些方法的调用。

变更方法

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:

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

你可以打开控制台,然后对前面例子的 items 数组尝试调用变更方法。比如 vm.items.push({ message: 'Baz' })

替换数组

filter()concat()slice()等,不会变更原始数组,而总是返回一个新数组

1
2
3
vm.items = vm.items.filter(function (item) {
return item.message.match(/Foo/)
})

Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。

数值循环

1
2
3
<div>
<span v-for="n in 10">{{ n }} </span>
</div>

会重复 1 ~ 10。

v-forv-if 一同使用

推荐在同一元素上使用 v-ifv-for

当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你只想为部分项渲染节点时,这种优先级的机制会十分有用,如下:

1
2
3
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>

上面的代码将只渲染未完成的 todo。

而如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素 (或 template) 上。如:

1
2
3
4
5
6
<ul v-if="todos.length">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>

在组件上使用v-for

1
2
3
4
5
6
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>

组件中的 :key 是必需的。

简单的 todo 列表例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="todo-list-example">
<form v-on:submit.prevent="addNewTodo">
<label for="new-todo">Add a todo</label>
<input v-model="newTodoText"
id="new-todo"
placeholder="E.g. Feed the cat"
>
<button>Add</button>
</form>
<ul>
<li is="todo-item"
v-for="(todo, index) in todos"
v-bind:key="todo.id"
v-bind:title="todo.title"
v-on:remove="todos.splice(index, 1)"
></li>
</ul>
</div>

注意这里的 is="todo-item" attribute。这种做法在使用 DOM 模板时是十分必要的,因为在 <ul> 元素内只有 <li> 元素会被看作有效内容。这样做实现的效果与 <todo-item> 相同,但是可以避开一些潜在的浏览器解析错误。

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
Vue.component('todo-item', {
template: '<li>\
{{ title }}\
<button v-on:click="$emit(\'remove\')">Remove</button>\
</li>',
props: ['title']
});

new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
{
id: 1,
title: 'Do the dishes',
},
{
id: 2,
title: 'Take out the trash',
}
],
nextTodoId: 3
},
methods: {
addNewTodo: function () {
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''
}
}
})

v-text / v-html

1
2
3
4
<!-- 会输出标签 -->
<span v-text="msg"></span>
<!-- 原模原样输出 -->
<span v-html="msg"></span>

v-on 事件绑定

简写示例:<a @click="doSomething">...</a><a @[event]="doSomething"> ... </a>

可以是直接运行JS语句、调用vue的method(无参、有参皆可)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var vm = new Vue({
el: '#app',
data: {
num: 10,
addNum: 0
},
methods: {
add: function(){ return this.num++; },
cut: function(){ return this.num--; },
onEnter: function(event){
this.num += parseInt(this.addNum);
// event 是原生 DOM 事件,可忽略
if (event) {
alert(event.target.tagName)
}
}
}
})
1
2
3
4
5
<button v-on:click="add">+1</button>
<button @click="cut">-1</button>

<!-- 相加;13是enter键 -->
<input type="text" v-model="addNum" @keyup.13="onEnter"/>

HTML的 onclick="func()" 带有括号,Vue的不带括号

或者带参数的方式:

1
2
<button v-on:click="modify(1)">+1</button>
<button @click="modify(-1)">-1</button>

原生DOM事件

有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法:

1
<button v-on:click="warn('Form cannot be submitted yet.', $event)"> Submit </button>
1
2
3
4
5
6
7
8
9
10
// ...
methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) {
event.preventDefault()
}
alert(message)
}
}

事件修饰符

修饰符是由点开头的指令后缀来表示的。

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive
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
<!-- 阻止单击事件继续传播 -->
<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>

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

<!-- 2.1.4新增:点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>

<!-- 2.3.0新增: 对应 addEventListener 中的 passive 选项-->
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<!-- 这个 .passive 修饰符尤其能够提升移动端的性能 -->
<div v-on:scroll.passive="onScroll">...</div>

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。

因此,用 v-on:click.prevent.self 会阻止所有的点击

v-on:click.self.prevent 只会阻止对元素自身的点击。

按键修饰符
1
2
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">

你可以直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符。

1
<input v-on:keyup.page-down="onPageDown">

在上述示例中,处理函数只会在 $event.key 等于 PageDown 时被调用。

已经废弃的使用 keyCode attribute 也是允许的,但可能不会被最新的浏览器支持:

1
<input v-on:keyup.13="submit">

有一些按键 (.esc 以及所有的方向键) 在 IE9 中有不同的 key 值, 如果你想支持 IE9,这些内置的别名应该是首选。

你还可以通过全局 config.keyCodes 对象自定义按键修饰符别名

1
2
// 可以使用 v-on:keyup.f1
Vue.config.keyCodes.f1 = 112
系统修饰键

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

  • .ctrl
  • .alt
  • .shift
  • .meta

注意:在 Mac 上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。其他系统类似。

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

<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>

只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。如果你想要这样的行为,请为 ctrl 换用 keyCodekeyup.17

.extra修饰符

.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。

1
2
3
4
5
6
7
8
<!-- 即使 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

这些修饰符会限制处理函数仅响应特定的鼠标按钮。

v-model

双向绑定,有以下的修饰符:

lazy延迟绑定

input框失去焦点之后才会引起改变

1
<input type="text" v-model.lazy="num" />

number数据转换

input框转换过来的数值自动变成number(默认是string)

1
<input type="text" v-model.number="num" />

去除空格

1
<input type="text" v-model.trim="msg" />

表单中的v-model

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

单个复选框

checkbox的值会映射到bool中(默认值为bool的默认值)

1
2
3
<input type="checkbox" id="Check1" value="Val1" v-model="checked" />
<label for="Check1">{{checked}}</label>
<!-- true/false -->

多个复选框绑定一个数组

1
2
3
4
5
6
<input type="checkbox" id="Check1" value="Val1" v-model="arr" />
<input type="checkbox" id="Check2" value="Val2" v-model="arr" />
<input type="checkbox" id="Check3" value="Val3" v-model="arr" />

<!-- ["Val1", "Val3"] -->
{{arr}}

多个单选框

1
2
3
4
5
6
7
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">one</label>
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">two</label>

<!-- One/Two -->
{{picked}}

下拉列表

可以单选也可以多选。单选时是一个字符串,多选时是绑定到一个数组

1
2
3
4
data:{
selected: '',
array:[]
}
1
2
3
4
5
6
7
<select v-model="array" multiple>
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
{{array}}

如果 v-model 表达式的初始值未能匹配任何选项,<select> 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。

值绑定

有时我们可能想把值绑定到 Vue 实例的一个动态 property 上,这时可以用 v-bind 实现,并且这个 property 的值可以不是字符串。

复选框

1
2
3
4
5
6
<input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no"
>
1
2
3
4
// 当选中时
vm.toggle === 'yes'
// 当没有选中时
vm.toggle === 'no'

这里的 true-valuefalse-value attribute 并不会影响输入控件的 value attribute,因为浏览器在提交表单时并不会包含未被选中的复选框。如果要确保表单中这两个值中的一个能够被提交,(即“yes”或“no”),请换用单选按钮。

单选按钮

1
<input type="radio" v-model="pick" v-bind:value="a">
1
2
// 当选中时
vm.pick === vm.a

选择框

1
2
3
4
<select v-model="selected">
<!-- 内联对象字面量 -->
<option v-bind:value="{ number: 123 }">123</option>
</select>
1
2
3
// 当选中时
typeof vm.selected // => 'object'
vm.selected.number // => 123
修饰符

.lazy

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了输入法组合文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步:

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

.number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

1
<input v-model.number="age" type="number">

这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。

.trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

1
<input v-model.trim="msg">

v-bind 属性绑定

能绑定例如 v-bind:idv-bind:disabled 等。

简写示例:<a :href="url">...</a><a :[key]="url"> ... </a>

img.src绑定

1
<img v-bind:src="imageSrc" :width="width" />
1
2
3
4
data:{
imageSrc: 'http://xxx',
width: '200'
}

v-bind:class

1
2
3
4
5
6
7
8
9
<style>
.classA{}
.classB{}
</style>

<!-- 单个style;与v-bind:class与普通class可共存 -->
<div class="xxx" :class="style0"></div>
<!-- style数组 -->
<div :class="[style1, style2]"></div>
1
2
3
4
5
data: {
style0: 'classA',
style1: 'classA',
style2: 'classB'
}

style判断

1
2
3
4
5
6
7
8
<!-- 单个style -->
<div :class="{style0:false}"></div>

<div class="style0" v-bind:class=
"{ style1: isActive1, 'style-name': isActive2 }" ></div>

<!-- style数组形式 -->
<div :class="[boolVar ? style1 : style2, style3]"></div>

复杂style对象

1
<div v-bind:class="classObject"></div>
1
2
3
4
5
6
data: {
classObject: {
style1: true,
'styleb': false
}
}

也可以是下面的:

1
2
3
4
5
6
7
8
9
10
11
12
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}

用在组件上

当在一个自定义组件上使用 class property 时,这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖。

例如,如果你声明了这个组件:

1
2
3
Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})

然后在使用它的时候添加一些 class:

1
<my-component class="baz boo"></my-component>

HTML 将被渲染为:

1
<p class="foo bar baz boo">Hi</p>

对于带数据绑定 class 也同样适用:

1
<my-component v-bind:class="{ active: isActive }"></my-component>

isActive 为 true 时,HTML 将被渲染成为:

1
<p class="foo bar active">Hi</p>

v-bind:style

与class的区别是,这是内联样式。

看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式或短横线分隔 (kebab-case,记得用引号括起来) 来命名:

1
2
3
4
5
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}

直接绑定到一个样式对象通常更好,这会让模板更清晰:

1
2
3
4
5
6
7
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}

同样的,对象语法常常结合返回对象的计算属性使用。

数组语法

将多个样式对象应用到同一个元素上:

1
<div v-bind:style="[baseStyles, overridingStyles]"></div>

自动添加前缀

v-bind:style 使用需要添加浏览器引擎前缀的 CSS property 时,如 transform,Vue.js 会自动侦测并添加相应的前缀。

多重值

从 2.3.0 起你可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:

1
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex

使用JS表达式

1
2
3
4
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>

有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。

1
2
3
4
5
<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}

<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}

模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 MathDate 。你不应该在模板表达式中试图访问用户定义的全局变量。

绑定动态属性

从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:

1
2
<!-- 注意,参数表达式的写法存在一些约束 -->
<a v-bind:[attributeName]="url"> ... </a>

这里的 attributeName 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。

同样地,你可以使用动态参数为一个动态的事件名绑定处理函数:

1
<a v-on:[eventName]="doSomething"> ... </a>

在这个示例中,当 eventName 的值为 "focus" 时,v-on:[eventName] 将等价于 v-on:focus

对动态参数的值的约束

动态参数预期会求出一个字符串,异常情况下值为 null。这个特殊的 null 值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。

对动态参数表达式的约束

动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。例如:

1
2
<!-- 这会触发一个编译警告 -->
<a v-bind:['foo' + bar]="value"> ... </a>

变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式。

还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写

修饰符

以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()

1
<form v-on:submit.prevent="onSubmit">...</form>

杂项API

和其余部分有些重复,稍微看看就好

Vue实例

当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。只有当实例被创建时就已经存在data 中的 property 才是响应式的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 我们的数据对象
var data = { a: 1 }

// 该对象被加入到一个 Vue 实例中
var vm = new Vue({
data: data
})

// 获得这个实例上的 property返回源数据中对应的字段
// 但是使用a的话视图不会自动响应(若要使用,可以创建时设置为空或null)
vm.a == data.a // => true

// 设置 property 也会影响到原始数据
vm.a = 2
data.a // => 2

// ……反之亦然
data.a = 3
vm.a // => 3

这里唯一的例外是使用 Object.freeze(),这会阻止修改现有的 property,也意味着响应系统无法再追踪变化。

一些有用的实例 property 与方法。它们都有前缀 $,以便与用户定义的 property 区分开来:

1
2
3
4
5
6
vm.$data === data // => true
vm.$el === document.getElementById('example') // => true

vm.$watch('a', function (newValue, oldValue) {
// 这个回调将在 `vm.a` 改变后调用
})

Vue.extend

构造一个组件的语法器

1
<div id="header"></div>
1
2
3
4
5
6
7
var header = Vue.extend({
template: '<p>{{message}}</p>',
data: function() {
return { message: 'This is header'; }
}
})
new header().$mount('#header'); // 挂载

Vue.set

设置数据响应

1
2
3
4
5
6
7
<ul>
<li v-for="val in goods">
{{val.name}} - {{val.num}}</li>
</ul>
总计{{count}}件产品
<button onclick="add()">添加</button>
<button @click="clear">清空</button>
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
var sql = {
count: 3,
goods: [{name: '物品0', num: '2'}, {xxx}, {xxx}]
}
var vm = new Vue({
el: '#app',
data: sql,
method: {
clear: function(){
this.count = 0;
this.goods = [];
// 将两个都设置为null,可禁用添加
}
}
})

// 直接使用set
function testSet() {
Vue.set(sql, 'count', '4');
}

// 挨个添加
function add() {
sql.goods.push({name:'物品3', num: '1'});
sql.count++;
}

生命周期

  • beforeCreate 初始化之后
  • created 创建完成
  • beforeMount 挂载之前
  • mounted 被创建
  • — 开始操作的位置 —
  • beforeUpdate 数据更新前
  • updated 数据更新之后
  • beforeDestroy 销毁之前(使用 vm.$destroy()
  • destroyed 销毁之后

组件 component

注册一个全局组件,使用:

1
Vue.component(tagName, options);
1
2
3
4
5
6
7
8
9
<hello></hello>

<script>
// 可以在不是'#app'的作用域外中使用(但也必须是其他的el,不能是没el的)
Vue.component('hello', {
template: '<div>全局化注册的组件</div>'
});
var vm = new Vue({ /*...*/ })
</script>

局部组件

1
2
3
4
5
6
7
8
9
10
11
12
13
// 只能应用在对应 'app' 中的组件
word = {
template: '<div>局部化注册的组件</div>'
}

var vm = new Vue({
el: '#app',
data: { msg: '' },
// 这里的语法的双引号和全局组件正好相反
components: { // 注意这里多了个s,因为是可以多个的
"world": world;
}
})

带参数的component

1
2
3
4
5
6
7
8
9
<div id="app-7">
<ol>
<!-- 为每个 todo-item 提供 todo 对象,其内容可以是动态的 -->
<todo-item v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id"
></todo-item>
</ol>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Vue.component('todo-item', {
// todo-item 组件现在接受一个 "prop",类似一个自定义 attribute。
// 这个 prop 名为 todo
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})

var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '随便其它什么人吃的东西' }
]
}
})

Vue选项

computed

可以在使用时进行函数计算而不是使用computed,两种结果方式完全相同;但计算属性是基于它们的响应式依赖进行缓存的,只要属性值没变,就不必再次执行函数。

示例:计算 fullName

1
2
3
4
5
6
7
8
9
10
11
12
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})

计算属性的setter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.firstNamevm.lastName 也会相应地被更新。

method

watch

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
43
<p>原价:¥{{prevPrice}]</p>
<p>折扣:{{sale}]</p>
<p>现价:¥{{newPrice}]</p>
<button @click="add">+</button>
<button @click="cut">+</button>

<script>
var vm = new Vue({
el: '#app',
data {
prevPrice: 10,
sale: 0.8
},
computed: {
newPrice: function(){
return '¥' + this.prevPrice * this.sale;
}
},
methods: {
add: function() {
this.prevPrice++;
},
cut: function() {
this.prevPrice--;
}
},
// 写法一:局部
watch: {
prevPrice: function(newVal, oldVal) {
if (newVal < 1) { // 不允许减到 1 以下
this.prevPrice = 1;
}
}
}
});

// 写法二:全局(两种方法写一个就够了)
vm.$watch('prevPrice', function(newVal, oldVal) {
if (newVal < 1) { // 不允许减到 1 以下
this.prevPrice = 1;
}
});
</script>

props

props 可以是数组或对象,用于接收来自父组件的数据

1
2
3
Vue.component('simple', {
props: ['size', 'myMessage']
})

mixin 混入

接收一个混合对象的数组。这些混合实例对象可以像正常的示例对象一样包含选项,他们将在 Vue.extend() 里最终选择使用相同的选项合并逻辑合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
var mix = {
created: function() {
// 先执行被混入的生命周期,再执行构造器原装的
this.msg = '被混入的'; // 修改data中的值
}
};
var vm = new Vue({
el: '#app',
data {
msg: 'hello world'
},
created: function() {
// 构造器原装的生命周期
},
mixins: [mix]
});

</script>

实例

  • $mount() 挂载

  • $destroy() 卸载

  • $forceUpdate() 更新(迫使Vue实例重新渲染)

  • $nextTick() 回调

  • $on() 监听当前实例上的自定义事件

  • $once 监听第一个自定义事件,但是只触发一次;之后移除监听器

  • $emit() 触发当前实例上的事件

  • $off() 移除事件监听器

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
<div id='header'></div>
<button onclick="destroy()">卸载</button>

<script>
var header = Vue.extend({
template: '<p>{{message}}</p>',
data: function() {
return {
message: 'This is header';
}
},
destroyed: function() {
console.log("控制器和视图脱离关系了");
}
})

// 绑定 id='header' 的组件
var vm = new header().$mount('#header');

// 卸载
function destroy() {
vm.$destroy();
// 卸载之后其实是看不到区别的,因为控制器和视图脱离关系了
}

// 回调
function testTick() {
vm.$nextTick(function() {
alert('我是回调函数');
})
}
</script>

事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 绑定事件(添加监听器)
vm.$on('added', function(num){
this.count += num;
});

// 和上面的区别就是只触发一次
vm.$once('cutted', function(num){
this.count -= num;
});

function add() {
vm.$emit('added', '2');
}

// 移除监听器
function remove() {
vm.$off('added');
}

slot 扩展

标签内容的扩展,常与组件component联合使用

1
<slot name=''></slot>
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
<div id='app'>
<goods v-for="val in good">
<!-- 注意slot是要双标签,单标签没有任何效果的 -->
<span slot="name">{{val.name}}</span>
<span slot="num">{{val.num}}</span>
</goods>
</div>

<template id="demo">
<p>
名称:<slot name="name"></slot>
数量:<slot name="num"></slot>
</p>
</template>

<script>
goods = {
template: '#demo'
}
var vm= new Vue({
el: '#app',
data: {
good: [
{name: 'car', num: '1'},
{name: 'house', num: '2'}
]
},
components: {
"goods": goods // 其实就是id=demo
}
})
</script>

Ajax

1
2
3
4
5
6
beforeMount: function(){ // 数据挂载之前
// 这是 jquery 的
$.get('1.txt', function(result){
$('#app').html(result);
})
}

组件基础

基本示例

1
2
3
4
5
6
7
8
new Vue({ el: '#components-demo' });
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return { count: 0 }
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 <button-counter>。我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用:

1
2
3
<div id="components-demo">
<button-counter></button-counter>
</div>

注意当点击按钮时,每个组件都会各自独立维护它的 count

此处的示例是全局注册,可以用在其被注册之后的任何新创建的 Vue 根实例。

data 必须是一个函数

data 并不是直接提供一个对象,而必须是一个函数。因此每个实例可以维护一份被返回对象的独立的拷贝。

如果 Vue 没有这条规则,点击一个按钮就可能会影响到其它所有实例

Prop 向子组件传递数据

Prop 在组件上注册的一些自定义 attribute,包含在该组件可接受的 prop 列表中:

1
2
3
4
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
1
<blog-post title="My journey with Vue"></blog-post>

然而在一个典型的应用中,你可能在 data 里有一个博文的数组:

1
2
3
4
5
6
7
8
9
10
new Vue({
el: '#blog-post-demo',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
})

并想要为每篇博文渲染一个组件:

1
2
3
4
5
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>

如上所示,你会发现我们可以使用 v-bind 来动态传递 prop。

单个根元素

每个组件必须只有一个根元素,可以将模板的内容包裹在一个父元素内,来修复这个问题,例如:

1
2
3
4
<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>

当组件变得越来越复杂的时候,尝试重构一下这个 <blog-post> 组件,让它变成接受一个单独的 post prop:

1
2
3
4
5
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
1
2
3
4
5
6
7
8
9
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
`
})

上述的这个和一些接下来的示例使用了 JavaScript 的模板字符串来让多行的模板更易读。它们在 IE 下并没有被支持,所以如果你需要在不 (经过 Babel 或 TypeScript 之类的工具) 编译的情况下支持 IE,请使用折行转义字符取而代之。

emit 监听子组件事件

子组件:

1
2
3
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>

父组件:

1
2
3
4
<blog-post
...
v-on:enlarge-text="postFontSize += 0.1"
></blog-post></blog-post>

带参数的子组件事件

例如我们可能想让 <blog-post> 组件决定它的文本要放大多少,这时可以使用 $emit 的第二个参数来提供这个值:

1
2
3
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>

然后当在父级组件监听这个事件的时候,我们可以通过 $event 访问到被抛出的这个值:

1
2
3
4
<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>

或者,如果这个事件处理函数是一个方法:

1
2
3
4
<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>

那么这个值将会作为第一个参数传入这个方法:

1
2
3
4
5
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}

在组件上使用 v-model

1
<input v-model="searchText">

等价于:

1
2
3
4
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>

当用在组件上时,v-model 则会这样:

1
2
3
4
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>

为了让它正常工作,这个组件内的 <input> 必须:

  • 将其 value attribute 绑定到一个名叫 value 的 prop 上
  • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出

写成代码之后是这样的:

1
2
3
4
5
6
7
8
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>`
})

现在 v-model 就应该可以在这个组件上完美地工作起来了:

1
<custom-input v-model="searchText"></custom-input>

通过插槽分发内容

1
2
3
4
5
6
7
8
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})

只要在需要的地方加入插槽就行了。

动态组件

有的时候,在不同组件之间进行动态切换是非常有用的,比如多标签的界面。

通过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现:

1
2
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>

currentTabComponent 可以包括

  • 已注册组件的名字,或
  • 一个组件的选项对象

DOM注意事项

有些 HTML 元素,诸如 <ul><ol><table><select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li><tr><option>,只能出现在其它某些特定的元素内部。

这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:

1
2
3
<table>
<blog-post-row></blog-post-row>
</table>

这个自定义组件 <blog-post-row> 会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is attribute 给了我们一个变通的办法:

1
2
3
<table>
<tr is="blog-post-row"></tr>
</table>

需要注意的是如果我们从以下来源使用模板的话,这条限制是*不存在*的

深入组件

组件注册

组件名

强烈推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及未来的 HTML 元素相冲突。

组件名大小写

使用 kebab-case

1
Vue.component('my-component-name', { /* ... */ })

当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>

使用 PascalCase

1
Vue.component('MyComponentName', { /* ... */ })

当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name><MyComponentName> 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。

全局注册

Vue.component 来创建组件:

1
2
3
Vue.component('my-component-name', {
// ... 选项 ...
})

局部注册

通过一个普通的 JavaScript 对象来定义组件:

1
2
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }

components 选项中定义你想要使用的组件:

1
2
3
4
5
6
7
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})

注意局部注册的组件在其子组件中*不可用*。例如,如果你希望 ComponentAComponentB 中可用,则你需要这样写:

1
2
3
4
5
6
7
8
var ComponentA = { /* ... */ }

var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}

或者如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:

1
import ComponentA from './ComponentA.vue'
1
2
3
4
5
6
export default {
components: {
ComponentA
},
// ...
}

模块系统

基础组件的自动化全局注册

Prop

Prop的大小写

HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

1
2
3
4
5
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
1
2
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>

重申一次,如果使用字符串模板,那么这个限制就不存在了。

Prop类型

到这里,我们只看到了以字符串数组形式列出的 prop:

1
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

通常你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:

1
2
3
4
5
6
7
8
9
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}

传递动态Prop

数组用法:

1
2
3
4
5
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>

静态传递:

1
<blog-post title="My journey with Vue"></blog-post>

通过 v-bind 动态赋值:

1
2
3
4
5
6
7
8
9
10
11
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
<blog-post v-bind:likes="42"></blog-post>
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
<!-- 布尔值:包含该 prop 没有值的情况在内,都意味着 `true`。-->
<blog-post is-published></blog-post>

<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>

传入一个对象

1
2
3
4
5
6
7
8
9
10
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post
v-bind:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
></blog-post>

<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:author="post.author"></blog-post>

传入一个对象所有 property

如果你想要将一个对象的所有 property 都作为 prop 传入,你可以使用不带参数的 v-bind (取代 v-bind:prop-name)。例如,对于一个给定的对象 post

1
2
3
4
post: {
id: 1,
title: 'My Journey with Vue'
}

下面的模板:

1
<blog-post v-bind="post"></blog-post>

等价于:

1
2
3
4
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>

单向数据流

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

这里有两种常见的试图变更一个 prop 的情形:

  1. 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:

    1
    2
    3
    4
    5
    6
    props: ['initialCounter'],
    data: function () {
    return {
    counter: this.initialCounter
    }
    }
  2. 这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:

    1
    2
    3
    4
    5
    6
    props: ['size'],
    computed: {
    normalizedSize: function () {
    return this.size.trim().toLowerCase()
    }
    }

注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。

Prop验证

如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。

props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:

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
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'].indexOf(value) !== -1
}
}
}
})

当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。

注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 datacomputed 等) 在 defaultvalidator 函数中是不可用的。

类型检查

type 可以是下列原生构造函数中的一个:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。例如,给定下列现成的构造函数:

1
2
3
4
function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}

你可以使用:

1
2
3
4
5
Vue.component('blog-post', {
props: {
author: Person
}
})

来验证 author prop 的值是否是通过 new Person 创建的。

非Prop的Attribute

一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 prop 定义的 attribute。

因为显式定义的 prop 适用于向一个子组件传入信息,然而组件库的作者并不总能预见组件会被用于怎样的场景。这也是为什么组件可以接受任意的 attribute,而这些 attribute 会被添加到这个组件的根元素上。

例如,想象一下你通过一个 Bootstrap 插件使用了一个第三方的 <bootstrap-date-input> 组件,这个插件需要在其 <input> 上用到一个 data-date-picker attribute。我们可以将这个 attribute 添加到你的组件实例上:

1
<bootstrap-date-input data-date-picker="activated"></bootstrap-date-input>

然后这个 data-date-picker="activated" attribute 就会自动添加到 <bootstrap-date-input> 的根元素上。

替换/合并已有的Attribute

对于绝大多数 attribute 来说,从外部提供给组件的值会替换掉组件内部设置好的值。如果传入 type="text" 就会替换掉内部的 type="date" 并把它破坏!庆幸的是,classstyle attribute 会稍微智能一些,即两边的值会被合并起来。

禁用Attribute继承

如果你希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false。例如:

1
2
3
4
Vue.component('my-component', {
inheritAttrs: false,
// ...
})

这尤其适合配合实例的 $attrs property 使用,该 property 包含了传递给一个组件的 attribute 名和 attribute 值,例如:

1
2
3
4
{
required: true,
placeholder: 'Enter your username'
}

有了 inheritAttrs: false$attrs,你就可以手动决定这些 attribute 会被赋予哪个元素。在撰写基础组件的时候是常会用到的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
`
})

自定义事件

不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。

因此,推荐始终使用 kebab-case 的事件名

自定义组件的v-model

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的model 选项可以用来避免这样的冲突:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})

现在在这个组件上使用 v-model 的时候:

1
<base-checkbox v-model="lovingVue"></base-checkbox>

这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。

注意你仍然需要在组件的 props 选项里声明 checked 这个 prop。

将原生事件绑定到组件

你可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on.native 修饰符:

1
<base-input v-on:focus.native="onFocus"></base-input>

在有的时候这是很有用的,不过在你尝试监听一个类似 <input> 的非常特定的元素时,这并不是个好主意。比如上述 <base-input> 组件可能做了如下重构,所以根元素实际上是一个 <label> 元素:

1
2
3
4
5
6
7
8
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>

这时,父级的 .native 监听器将静默失败。它不会产生任何报错,但是 onFocus 处理函数不会如你预期地被调用。

为了解决这个问题,Vue 提供了一个 $listeners property,它是一个对象,里面包含了作用在这个组件上的所有监听器。例如:

1
2
3
4
{
focus: function (event) { /* ... */ }
input: function (value) { /* ... */ },
}

有了这个 $listeners property,你就可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。对于类似 <input> 的你希望它也可以配合 v-model 工作的组件来说,为这些监听器创建一个类似下述 inputListeners 的计算属性通常是非常有用的:

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
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,
// 或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})

现在 <base-input> 组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input> 元素一样使用了:所有跟它相同的 attribute 和监听器都可以工作,不必再使用 .native 监听器。

.sync修饰符

在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件两侧都没有明显的变更来源。

这也是为什么我们推荐以 update:myPropName 的模式触发事件取而代之。举个例子,在一个包含 title prop 的假设的组件中,我们可以用以下方法表达对其赋新值的意图:

1
this.$emit('update:title', newTitle)

然后父组件可以监听那个事件并根据需要更新一个本地的数据 property。例如:

1
2
3
4
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>

为了方便起见,我们为这种模式提供一个缩写,即 .sync 修饰符:

1
<text-document v-bind:title.sync="doc.title"></text-document>

注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的 property 名,类似 v-model

当我们用一个对象同时设置多个 prop 的时候,也可以将这个 .sync 修饰符和 v-bind 配合使用:

1
<text-document v-bind.sync="doc"></text-document>

这样会把 doc 对象中的每一个 property (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。

v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是无法正常工作的,因为在解析一个像这样的复杂表达式的时候,有很多边缘情况需要考虑。

插槽