vue 初级

  

模板语法

Vue.js 使用了基于 HTML 的模板语法,容许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。全部 Vue.js 的模板都是合法的 HTML ,因此能被遵循规范的浏览器和 HTML 解析器解析。javascript

在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 可以智能地计算出最少须要从新渲染多少组件,并把 DOM 操做次数减到最少。php

若是你熟悉虚拟 DOM 而且偏心 JavaScript 的原始力量,你也能够不用模板,直接写渲染 (render) 函数,使用可选的 JSX 语法。css

插值

文本

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

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

Mustache 标签将会被替代为对应数据对象上 msg 属性的值。不管什么时候,绑定的数据对象上 msg 属性发生了改变,插值处的内容都会更新。vue

经过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。但请留心这会影响到该节点上的其它数据绑定:java

<span v-once>这个将不会改变: {{ msg }}</span>

原始 HTML

双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你须要使用 v-html 指令:webpack

<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

Using mustaches: <span style="color: red">This should be red.</span>ios

Using v-html directive: This should be red.git

这个 span 的内容将会被替换成为属性值 rawHtml,直接做为 HTML——会忽略解析属性值中的数据绑定。注意,你不能使用 v-html 来复合局部模板,由于 Vue 不是基于字符串的模板引擎。反之,对于用户界面 (UI),组件更适合做为可重用和可组合的基本单位。github

你的站点上动态渲染的任意 HTML 可能会很是危险,由于它很容易致使 XSS 攻击。请只对可信内容使用 HTML 插值,毫不要对用户提供的内容使用插值。

特性

Mustache 语法不能做用在 HTML 特性上,遇到这种状况应该使用 v-bind 指令

<div v-bind:id="dynamicId"></div>

在布尔特性的状况下,它们的存在即暗示为 truev-bind 工做起来略有不一样,在这个例子中:

<button v-bind:disabled="isButtonDisabled">Button</button>

若是 isButtonDisabled 的值是 nullundefined 或 false,则 disabled 特性甚至不会被包含在渲染出来的 <button> 元素中。

使用 JavaScript 表达式

迄今为止,在咱们的模板中,咱们一直都只绑定简单的属性键值。但实际上,对于全部的数据绑定,Vue.js 都提供了彻底的 JavaScript 表达式支持。

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div v-bind:id="'list-' + id"></div>

这些表达式会在所属 Vue 实例的数据做用域下做为 JavaScript 被解析。有个限制就是,每一个绑定都只能包含单个表达式,因此下面的例子都不会生效。

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

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

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

指令

指令 (Directives) 是带有 v- 前缀的特殊特性。指令特性的值预期是单个 JavaScript 表达式 (v-for 是例外状况,稍后咱们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地做用于 DOM。回顾咱们在介绍中看到的例子:

<p v-if="seen">如今你看到我了</p>

这里,v-if 指令将根据表达式 seen 的值的真假来插入/移除 <p> 元素。

参数

一些指令可以接收一个“参数”,在指令名称以后以冒号表示。例如,v-bind 指令能够用于响应式地更新 HTML 特性:

<a v-bind:href="url">...</a>

在这里 href 是参数,告知 v-bind 指令将该元素的 href 特性与表达式 url 的值绑定。

另外一个例子是 v-on 指令,它用于监听 DOM 事件:

<a v-on:click="doSomething">...</a>

在这里参数是监听的事件名。咱们也会更详细地讨论事件处理。

修饰符

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

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

在接下来对 v-on 和 v-for 等功能的探索中,你会看到修饰符的其它例子。

缩写

v- 前缀做为一种视觉提示,用来识别模板中 Vue 特定的特性。当你在使用 Vue.js 为现有标签添加动态行为 (dynamic behavior) 时,v- 前缀颇有帮助,然而,对于一些频繁用到的指令来讲,就会感到使用繁琐。同时,在构建由 Vue.js 管理全部模板的单页面应用程序 (SPA - single page application) 时,v- 前缀也变得没那么重要了。所以,Vue.js 为 v-bind 和 v-on 这两个最经常使用的指令,提供了特定简写:

v-bind 缩写

<!-- 完整语法 -->
<a v-bind:href="url">...</a>

<!-- 缩写 -->
<a :href="url">...</a>

v-on 缩写

<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>

<!-- 缩写 -->
<a @click="doSomething">...</a>

它们看起来可能与普通的 HTML 略有不一样,但 : 与 @ 对于特性名来讲都是合法字符,在全部支持 Vue.js 的浏览器都能被正确地解析。并且,它们不会出如今最终渲染的标记中。缩写语法是彻底可选的,但随着你更深刻地了解它们的做用,你会庆幸拥有它们。

 

计算属性和侦听器

计算属性

模板内的表达式很是便利,可是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板太重且难以维护。例如:

<div id="example">
{{ message.split('').reverse().join('') }}
</div>

在这个地方,模板再也不是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中屡次引用此处的翻转字符串时,就会更加难以处理。

因此,对于任何复杂逻辑,你都应当使用计算属性。

基础例子

<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})

结果:

Original message: "Hello"

Computed reversed message: "olleH"

这里咱们声明了一个计算属性 reversedMessage。咱们提供的函数将用做属性 vm.reversedMessage 的 getter 函数:

console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'

你能够打开浏览器的控制台,自行修改例子中的 vm。vm.reversedMessage 的值始终取决于 vm.message 的值。

你能够像绑定普通属性同样在模板中绑定计算属性。Vue 知道 vm.reversedMessage依赖于 vm.message,所以当 vm.message 发生改变时,全部依赖 vm.reversedMessage 的绑定也会更新。并且最妙的是咱们已经以声明的方式建立了这种依赖关系:计算属性的 getter 函数是没有反作用 (side effect) 的,这使它更易于测试和理解。

计算属性缓存 vs 方法

你可能已经注意到咱们能够经过在表达式中调用方法来达到一样的效果:

<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在组件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}

咱们能够将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是彻底相同的。然而,不一样的是计算属性是基于它们的依赖进行缓存的。只在相关依赖发生改变时它们才会从新求值。这就意味着只要 message 尚未发生改变,屡次访问 reversedMessage 计算属性会当即返回以前的计算结果,而没必要再次执行函数。

这也一样意味着下面的计算属性将再也不更新,由于 Date.now() 不是响应式依赖:

computed: {
now: function () {
return Date.now()
}
}

相比之下,每当触发从新渲染时,调用方法将总会再次执行函数。

咱们为何须要缓存?假设咱们有一个性能开销比较大的计算属性 A,它须要遍历一个巨大的数组并作大量的计算。而后咱们可能有其余的计算属性依赖于 A 。若是没有缓存,咱们将不可避免的屡次执行 A 的 getter!若是你不但愿有缓存,请用方法来替代。

计算属性 vs 侦听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变更:侦听属性。当你有一些数据须要随着其它数据变更而变更时,你很容易滥用 watch——特别是若是你以前使用过 AngularJS。然而,一般更好的作法是使用计算属性而不是命令式的 watch 回调。细想一下这个例子:

<div id="demo">{{ fullName }}</div>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})

好得多了,不是吗?

计算属性的 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 也会相应地被更新。

侦听器

虽然计算属性在大多数状况下更合适,但有时也须要一个自定义的侦听器。这就是为何 Vue 经过 watch 选项提供了一个更通用的方法,来响应数据的变化。当须要在数据变化时执行异步或开销较大的操做时,这个方式是最有用的。

例如:

<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<!-- 由于 AJAX 库和通用工具的生态已经至关丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可让你自由选择本身更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/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, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},
created: function () {
  // `_.debounce` 是一个经过 Lodash 限制操做频率的函数。
  // 在这个例子中,咱们但愿限制访问 yesno.wtf/api 的频率
  // AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
// `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
// 请参考:https://lodash.com/docs#debounce
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer: function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
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
})
}
}
})
</script>

结果:

Ask a yes/no question: 

I cannot give you an answer until you ask a question!

在这个示例中,使用 watch 选项容许咱们执行异步操做 (访问一个 API),限制咱们执行该操做的频率,并在咱们获得最终结果前,设置中间状态。这些都是计算属性没法作到的。

除了 watch 选项以外,您还可使用命令式的 vm.$watch API

 

Class 与 Style 绑定

操做元素的 class 列表和内联样式是数据绑定的一个常见需求。由于它们都是属性,因此咱们能够用 v-bind 处理它们:只须要经过表达式计算出字符串结果便可。不过,字符串拼接麻烦且易错。所以,在将 v-bind 用于 class 和 style 时,Vue.js 作了专门的加强。表达式结果的类型除了字符串以外,还能够是对象或数组。

绑定 HTML Class

对象语法

咱们能够传给 v-bind:class 一个对象,以动态地切换 class:

<div v-bind:class="{ active: isActive }"></div>

上面的语法表示 active 这个 class 存在与否将取决于数据属性 isActive 的 truthiness

你能够在对象中传入更多属性来动态切换多个 class。此外,v-bind:class 指令也能够与普通的 class 属性共存。当有以下模板:

<div class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }">
</div>

和以下 data:

data: {
isActive: true,
hasError: false
}

结果渲染为:

<div class="static active"></div>

当 isActive 或者 hasError 变化时,class 列表将相应地更新。例如,若是 hasError 的值为 true,class 列表将变为 "static active text-danger"

绑定的数据对象没必要内联定义在模板里:

<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 列表:

<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}

渲染为:

<div class="active text-danger"></div>

若是你也想根据条件切换列表中的 class,能够用三元表达式:

<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>

这样写将始终添加 errorClass,可是只有在 isActive 是 truthy[1] 时才添加 activeClass

不过,当有多个条件 class 时这样写有些繁琐。因此在数组语法中也可使用对象语法:

<div v-bind:class="[{ active: isActive }, errorClass]"></div>

用在组件上

这个章节假设你已经对 Vue 组件有必定的了解。固然你也能够先跳过这里,稍后再回过头来看。

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

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

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

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

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

HTML 将被渲染为:

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

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

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

当 isActive 为 truthy[1] 时,HTML 将被渲染成为:

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

绑定内联样式

对象语法

v-bind:style 的对象语法十分直观——看着很是像 CSS,但实际上是一个 JavaScript 对象。CSS 属性名能够用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用单引号括起来) 来命名:

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

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

<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}

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

数组语法

v-bind:style 的数组语法能够将多个样式对象应用到同一个元素上:

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

自动添加前缀

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

多重值

2.3.0+

从 2.3.0 起你能够为 style 绑定中的属性提供一个包含多个值的数组,经常使用于提供多个带前缀的值,例如:

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

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


译者注
[1] truthy 不是 true,详见 MDN 的解释。

 

v-if

在字符串模板中,好比 Handlebars,咱们得像这样写一个条件块:

<!-- Handlebars 模板 -->
{{#if ok}}
<h1>Yes</h1>
{{/if}}

在 Vue 中,咱们使用 v-if 指令实现一样的功能:

<h1 v-if="ok">Yes</h1>

也能够用 v-else 添加一个“else 块”:

<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>

在 <template> 元素上使用 v-if 条件渲染分组

由于 v-if 是一个指令,因此必须将它添加到一个元素上。可是若是想切换多个元素呢?此时能够把一个 <template> 元素当作不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 <template> 元素。

<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>

v-else

你可使用 v-else 指令来表示 v-if 的“else 块”:

<div v-if="Math.random() > 0.5">
Now you see me
</div>
<div v-else>
Now you don't
</div>

v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,不然它将不会被识别。

v-else-if

2.1.0 新增

v-else-if,顾名思义,充当 v-if 的“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-elsev-else-if 也必须紧跟在带 v-if 或者 v-else-if 的元素以后。

用 key 管理可复用的元素

Vue 会尽量高效地渲染元素,一般会复用已有元素而不是从头开始渲染。这么作除了使 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 指令。用法大体同样:

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

不一样的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 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-if 与 v-for 一块儿使用

不推荐同时使用 v-if 和 v-for。请查阅风格指南以获取更多信息。

当 v-if 与 v-for 一块儿使用时,v-for 具备比 v-if 更高的优先级。请查阅列表渲染指南 以获取详细信息。

 

列表渲染

用 v-for 把一个数组对应为一组元素

咱们用 v-for 指令根据一组数组的选项列表进行渲染。v-for 指令须要使用 item in items 形式的特殊语法,items 是源数据数组而且 item 是数组元素迭代的别名。

<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})

结果:

  • Foo
  • Bar

在 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' }
]
}
})

结果:

  • Parent - 0 - Foo
  • Parent - 1 - Bar

你也能够用 of 替代 in 做为分隔符,由于它是最接近 JavaScript 迭代器的语法:

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

一个对象的 v-for

你也能够用 v-for 经过一个对象的属性来迭代。

<ul id="v-for-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>
new Vue({
el: '#v-for-object',
data: {
object: {
firstName: 'John',
lastName: 'Doe',
age: 30
}
}
})

结果:

  • John
  • Doe
  • 30

你也能够提供第二个的参数为键名:

<div v-for="(value, key) in object">
{{ key }}: {{ value }}
</div>
firstName: John
lastName: Doe
age: 30

第三个参数为索引:

<div v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
</div>
0. firstName: John
1. lastName: Doe
2. age: 30

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

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 内容很是简单,或者是刻意依赖默认行为以获取性能上的提高。

由于它是 Vue 识别节点的一个通用机制,key 并不与 v-for 特别关联,key 还具备其余用途,咱们将在后面的指南中看到其余用途。

数组更新检测

变异方法

Vue 包含一组观察数组的变异方法,因此它们也将会触发视图更新。这些方法以下:

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

你打开控制台,而后用前面例子的 items 数组调用变异方法:example1.items.push({ message: 'Baz' }) 。

替换数组

变异方法 (mutation method),顾名思义,会改变被这些方法调用的原始数组。相比之下,也有非变异 (non-mutating method) 方法,例如:filter()concat() 和 slice() 。这些不会改变原始数组,但老是返回一个新数组。当使用非变异方法时,能够用新数组替换旧数组:

example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})

你可能认为这将致使 Vue 丢弃现有 DOM 并从新渲染整个列表。幸运的是,事实并不是如此。Vue 为了使得 DOM 元素获得最大范围的重用而实现了一些智能的、启发式的方法,因此用一个含有相同元素的数组去替换原来的数组是很是高效的操做。

注意事项

因为 JavaScript 的限制,Vue 不能检测如下变更的数组:

  1. 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

举个例子:

var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

为了解决第一类问题,如下两种方式均可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将触发状态更新:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

你也可使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名:

vm.$set(vm.items, indexOfItem, newValue)

为了解决第二类问题,你可使用 splice

vm.items.splice(newLength)

对象更改检测注意事项

仍是因为 JavaScript 的限制,Vue 不能检测对象属性的添加或删除:

var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` 如今是响应式的

vm.b = 2
// `vm.b` 不是响应式的

对于已经建立的实例,Vue 不能动态添加根级别的响应式属性。可是,可使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性。例如,对于:

var vm = new Vue({
data: {
userProfile: {
name: 'Anika'
}
}
})

你能够添加一个新的 age 属性到嵌套的 userProfile 对象:

Vue.set(vm.userProfile, 'age', 27)

你还可使用 vm.$set 实例方法,它只是全局 Vue.set 的别名:

vm.$set(vm.userProfile, 'age', 27)

有时你可能须要为已有对象赋予多个新属性,好比使用 Object.assign() 或 _.extend()。在这种状况下,你应该用两个对象的属性建立一个新的对象。因此,若是你想添加新的响应式属性,不要像这样:

Object.assign(vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})

你应该这样作:

vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})

显示过滤/排序结果

有时,咱们想要显示一个数组的过滤或排序副本,而不实际改变或重置原始数据。在这种状况下,能够建立返回过滤或排序数组的计算属性。

例如:

<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
})
}
}

一段取值范围的 v-for

v-for 也能够取整数。在这种状况下,它将重复屡次模板。

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

结果:

1 2 3 4 5 6 7 8 9 10

v-for on a <template>

相似于 v-if,你也能够利用带有 v-for 的 <template> 渲染多个元素。好比:

<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>

v-for with v-if

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

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

上面的代码只传递了未完成的 todos。

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

<ul v-if="todos.length">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>

一个组件的 v-for

了解组件相关知识,查看 组件。彻底能够先跳过它,之后再回来查看。

在自定义组件里,你能够像任何普通元素同样用 v-for 。

<my-component v-for="item in items" :key="item.id"></my-component>

2.2.0+ 的版本里,当在组件中使用 v-for 时,key 如今是必须的。

然而,任何数据都不会被自动传递到组件里,由于组件有本身独立的做用域。为了把迭代数据传递到组件里,咱们要用 props :

<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>

不自动将 item 注入到组件里的缘由是,这会使得组件与 v-for 的运做紧密耦合。明确组件数据的来源可以使组件在其余场合重复使用。

下面是一个简单的 todo list 的完整例子:

<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" 属性。这种作法在使用 DOM 模板时是十分必要的,由于在 <ul> 元素内只有 <li> 元素会被看做有效内容。这样作实现的效果与 <todo-item> 相同,可是能够避开一些潜在的浏览器解析错误。查看 DOM 模板解析说明 来了解更多信息。

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',
},
{
id: 3,
title: 'Mow the lawn'
}
],
nextTodoId: 4
},
methods: {
addNewTodo: function () {
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''
}
}
})
   
  • Do the dishes 
  • Take out the trash 
  • Mow the lawn 

 

事件处理

监听事件

能够用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。

示例:

<div id="example-1">
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})

结果:

The button above has been clicked 0 times.

事件处理方法

然而许多事件处理逻辑会更为复杂,因此直接把 JavaScript 代码写在 v-on 指令中是不可行的。所以 v-on 还能够接收一个须要调用的方法名称。

示例:

<div id="example-2">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// 在 `methods` 对象中定义方法
methods: {
greet: function (event) {
// `this` 在方法里指向当前 Vue 实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})

// 也能够用 JavaScript 直接调用方法
example2.greet() // => 'Hello Vue.js!'

结果:

内联处理器中的方法

除了直接绑定到一个方法,也能够在内联 JavaScript 语句中调用方法:

<div id="example-3">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div>
new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})

结果:

 

有时也须要在内联语句处理器中访问原始的 DOM 事件。能够用特殊变量 $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)
}
}

事件修饰符

在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是很是常见的需求。尽管咱们能够在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。以前提过,修饰符是由点开头的指令后缀来表示的。

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

使用修饰符时,顺序很重要;相应的代码会以一样的顺序产生。所以,用 v-on:click.prevent.self 会阻止全部的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

2.1.4 新增

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

不像其它只能对原生的 DOM 事件起做用的修饰符,.once 修饰符还能被用到自定义的组件事件上。若是你尚未阅读关于组件的文档,如今大可没必要担忧。

2.3.0 新增

Vue 还对应 addEventListener 中的 passive 选项提供了 .passive 修饰符。

<!-- 滚动事件的默认行为 (即滚动行为) 将会当即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的状况 -->
<div v-on:scroll.passive="onScroll">...</div>

这个 .passive 修饰符尤为可以提高移动端的性能。

不要把 .passive 和 .prevent 一块儿使用,由于 .prevent 将会被忽略,同时浏览器可能会向你展现一个警告。请记住,.passive 会告诉浏览器你想阻止事件的默认行为。

按键修饰符

在监听键盘事件时,咱们常常须要检查常见的键值。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">

所有的按键别名:

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

能够经过全局 config.keyCodes 对象自定义按键修饰符别名

// 可使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112

自动匹配按键修饰符

2.5.0 新增

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

<input @keyup.page-down="onPageDown">

在上面的例子中,处理函数仅在 $event.key === 'PageDown' 时被调用。

有一些按键 (.esc 以及全部的方向键) 在 IE9 中有不一样的 key 值, 若是你想支持 IE9,它们的内置别名应该是首选。

系统修饰键

2.1.0 新增

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

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

注意:在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操做系统键盘上,meta 对应实心宝石键 (◆)。在其余特定键盘上,尤为在 MIT 和 Lisp 机器的键盘、以及其后继产品,好比 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”。

例如:

<!-- Alt + C -->
<input @keyup.alt.67="clear">

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

请注意修饰键与常规按键不一样,在和 keyup 事件一块儿用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 的状况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。若是你想要这样的行为,请为 ctrl 换用 keyCodekeyup.17

.exact 修饰符

2.5.0 新增

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

<!-- 即便 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>

鼠标按钮修饰符

2.2.0 新增

  • .left
  • .right
  • .middle

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

为何在 HTML 中监听事件?

你可能注意到这种事件监听的方式违背了关注点分离 (separation of concern) 这个长期以来的优良传统。但没必要担忧,由于全部的 Vue.js 事件处理方法和表达式都严格绑定在当前视图的 ViewModel 上,它不会致使任何维护上的困难。实际上,使用 v-on 有几个好处:

  1. 扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。

  2. 由于你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码能够是很是纯粹的逻辑,和 DOM 彻底解耦,更易于测试。

  3. 当一个 ViewModel 被销毁时,全部的事件处理器都会自动被删除。你无须担忧如何清理它们。

 

表单输入绑定

基础用法

你能够用 v-model 指令在表单 <input><textarea> 及 <select> 元素上建立双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

v-model 会忽略全部表单元素的 valuecheckedselected 特性的初始值而老是将 Vue 实例的数据做为数据来源。你应该经过 JavaScript 在组件的 data 选项中声明初始值。

对于须要使用输入法 (如中文、日文、韩文等) 的语言,你会发现 v-model 不会在输入法组合文字过程当中获得更新。若是你也想处理这个过程,请使用 input 事件。

文本

<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>

Message is:

多行文本

<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
Multiline message is:

 


在文本区域插值 (<textarea></textarea>) 并不会生效,应用 v-model 来代替。

复选框

单个复选框,绑定到布尔值:

<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
 

多个复选框,绑定到同一个数组:

<div id='example-3'>
<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>
<br>
<span>Checked names: {{ checkedNames }}</span>
</div>
new Vue({
el: '#example-3',
data: {
checkedNames: []
}
})
           
Checked names: []

单选按钮

<div id="example-4">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>
</div>
new Vue({
el: '#example-4',
data: {
picked: ''
}
})
   
   
Picked:

选择框

单选时:

<div id="example-5">
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
new Vue({
el: '...',
data: {
selected: ''
}
})
 Selected:

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

多选时 (绑定到一个数组):

<div id="example-6">
<select v-model="selected" multiple style="width: 50px;">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{ selected }}</span>
</div>
new Vue({
el: '#example-6',
data: {
selected: []
}
})
 
Selected: []

用 v-for 渲染的动态选项:

<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>
new Vue({
el: '...',
data: {
selected: 'A',
options: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
]
}
})
 Selected: A

值绑定

对于单选按钮,复选框及选择框的选项,v-model 绑定的值一般是静态字符串 (对于复选框也能够是布尔值):

<!-- 当选中时,`picked` 为字符串 "a" -->
<input type="radio" v-model="picked" value="a">

<!-- `toggle` 为 true 或 false -->
<input type="checkbox" v-model="toggle">

<!-- 当选中第一个选项时,`selected` 为字符串 "abc" -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>

可是有时咱们可能想把值绑定到 Vue 实例的一个动态属性上,这时能够用 v-bind 实现,而且这个属性的值能够不是字符串。

复选框

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

这里的 true-value 和 false-value 特性并不会影响输入控件的 value 特性,由于浏览器在提交表单时并不会包含未被选中的复选框。若是要确保表单中这两个值中的一个可以被提交,(好比“yes”或“no”),请换用单选按钮。

单选按钮

<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 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你能够添加 lazy 修饰符,从而转变为使用 change事件进行同步:

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

.number

若是想自动将用户的输入值转为数值类型,能够给 v-model 添加 number 修饰符:

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

这一般颇有用,由于即便在 type="number" 时,HTML 输入元素的值也总会返回字符串。若是这个值没法被 parseFloat() 解析,则会返回原始的值。

.trim

若是要自动过滤用户输入的首尾空白字符,能够给 v-model 添加 trim 修饰符:

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

在组件上使用 v-model

若是你还不熟悉 Vue 的组件,能够暂且跳过这里。

HTML 原生的输入元素类型并不总能知足需求。幸亏,Vue 的组件系统容许你建立具备彻底自定义行为且可复用的输入组件。这些输入组件甚至能够和 v-model 一块儿使用!要了解更多,请参阅组件指南中的自定义输入组件

 

组件基础

基本示例

这里有一个 Vue 组件的示例:

// 定义一个名为 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 根实例中,把这个组件做为自定义元素来使用:

<div id="components-demo">
<button-counter></button-counter>
</div>
new Vue({ el: '#components-demo' })

由于组件是可复用的 Vue 实例,因此它们与 new Vue 接收相同的选项,例如 datacomputedwatchmethods 以及生命周期钩子等。仅有的例外是像 el这样根实例特有的选项。

组件的复用

你能够将组件进行任意次数的复用:

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

注意当点击按钮时,每一个组件都会各自独立维护它的 count。由于你每用一次组件,就会有一个它的新实例被建立。

data 必须是一个函数

当咱们定义这个 <button-counter> 组件时,你可能会发现它的 data 并非像这样直接提供一个对象:

data: {
count: 0
}

取而代之的是,一个组件的 data 选项必须是一个函数,所以每一个实例能够维护一份被返回对象的独立的拷贝:

data: function () {
return {
count: 0
}
}

若是 Vue 没有这条规则,点击一个按钮就可能会像以下代码同样影响到其它全部实例

   

组件的组织

一般一个应用会以一棵嵌套的组件树的形式来组织:

Component Tree

例如,你可能会有页头、侧边栏、内容区等组件,每一个组件又包含了其它的像导航连接、博文之类的组件。

为了能在模板中使用,这些组件必须先注册以便 Vue 可以识别。这里有两种组件的注册类型:全局注册和局部注册。至此,咱们的组件都只是经过 Vue.component 全局注册的:

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

全局注册的组件能够用在其被注册以后的任何 (经过 new Vue) 新建立的 Vue 根实例,也包括其组件树中的全部子组件的模板中。

到目前为止,关于组件注册你须要了解的就这些了,若是你阅读完本页内容并掌握了它的内容,咱们会推荐你再回来把组件注册读完。

经过 Prop 向子组件传递数据

早些时候,咱们提到了建立一个博文组件的事情。问题是若是你不能向这个组件传递某一篇博文的标题或内容之类的咱们想展现的数据的话,它是没有办法使用的。这也正是 prop 的由来。

Prop 是你能够在组件上注册的一些自定义特性。当一个值传递给一个 prop 特性的时候,它就变成了那个组件实例的一个属性。为了给博文组件传递一个标题,咱们能够用一个 props 选项将其包含在该组件可接受的 prop 列表中:

Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})

一个组件默承认以拥有任意数量的 prop,任何值均可以传递给任何 prop。在上述模板中,你会发现咱们可以在组件实例中访问这个值,就像访问 data 中的值同样。

一个 prop 被注册以后,你就能够像这样把数据做为一个自定义特性传递进来:

<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>

My journey with Vue

Blogging with Vue

Why Vue is so fun

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

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' }
]
}
})

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

<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>

如上所示,你会发现咱们可使用 v-bind 来动态传递 prop。这在你一开始不清楚要渲染的具体内容,好比从一个 API 获取博文列表的时候,是很是有用的。

到目前为止,关于 prop 你须要了解的大概就这些了,若是你阅读完本页内容并掌握了它的内容,咱们会推荐你再回来把 prop 读完。

单个根元素

当构建一个 <blog-post> 组件时,你的模板最终会包含的东西远不止一个标题:

<h3>{{ title }}</h3>

最最起码,你会包含这篇博文的正文:

<h3>{{ title }}</h3>
<div v-html="content"></div>

然而若是你在模板中尝试这样写,Vue 会显示一个错误,并解释道 every component must have a single root element (每一个组件必须只有一个根元素)。你能够将模板的内容包裹在一个父元素内,来修复这个问题,例如:

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

看起来当组件变得愈来愈复杂的时候,咱们的博文不仅须要标题和内容,还须要发布日期、评论等等。为每一个相关的信息定义一个 prop 会变得很麻烦:

<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.content"
v-bind:publishedAt="post.publishedAt"
v-bind:comments="post.comments"
></blog-post>

因此是时候重构一下这个 <blog-post> 组件了,让它变成接受一个单独的 postprop:

<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
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,请使用折行转义字符取而代之。

如今,不论什么时候为 post 对象添加一个新的属性,它都会自动地在 <blog-post> 内可用。

经过事件向父级组件发送消息

在咱们开发 <blog-post> 组件时,它的一些功能可能要求咱们和父级组件进行沟通。例如咱们可能会引入一个可访问性的功能来放大博文的字号,同时让页面的其它部分保持默认的字号。

在其父组件中,咱们能够经过添加一个 postFontSize 数据属性来支持这个功能:

new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [/* ... */],
postFontSize: 1
}
})

它能够在模板中用来控制全部博文的字号:

<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
</div>
</div>

如今咱们在每篇博文正文以前添加一个按钮来放大字号:

Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button>
Enlarge text
</button>
<div v-html="post.content"></div>
</div>
`
})

问题是这个按钮不会作任何事:

<button>
Enlarge text
</button>

当点击这个按钮时,咱们须要告诉父级组件放大全部博文的文本。幸亏 Vue 实例提供了一个自定义事件的系统来解决这个问题。咱们能够调用内建的 $emit 方法并传入事件的名字,来向父级组件触发一个事件:

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

而后咱们能够用 v-on 在博文组件上监听这个事件,就像监听一个原生 DOM 事件同样:

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

My journey with Vue

...content...

Blogging with Vue

...content...

Why Vue is so fun

...content...

使用事件抛出一个值

有的时候用一个事件来抛出一个特定的值是很是有用的。例如咱们可能想让 <blog-post> 组件决定它的文本要放大多少。这时可使用 $emit 的第二个参数来提供这个值:

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

而后当在父级组件监听这个事件的时候,咱们能够经过 $event 访问到被抛出的这个值:

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

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

<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>

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

methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}

在组件上使用 v-model

自定义事件也能够用于建立支持 v-model 的自定义输入组件。记住:

<input v-model="searchText">

等价于:

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

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

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

为了让它正常工做,这个组件内的 <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)"
   >
`
})

如今 v-model 就应该能够在这个组件上完美地工做起来了:

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

到目前为止,关于组件自定义事件你须要了解的大概就这些了,若是你阅读完本页内容并掌握了它的内容,咱们会推荐你再回来把自定义事件读完。

经过插槽分发内容

和 HTML 元素同样,咱们常常须要向一个组件传递内容,像这样:

<alert-box>
Something bad happened.
</alert-box>

可能会渲染出这样的东西:

Error! Something bad happened.

幸亏,Vue 自定义的 <slot> 元素让这变得很是简单:

Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})

如你所见,咱们只要在须要的地方加入插槽就好了——就这么简单!

到目前为止,关于插槽你须要了解的大概就这些了,若是你阅读完本页内容并掌握了它的内容,咱们会推荐你再回来把插槽读完。

动态组件

有的时候,在不一样组件之间进行动态切换是很是有用的,好比在一个多标签的界面里:

Home component

上述内容能够经过 Vue 的 <component> 元素加一个特殊的 is 特性来实现:

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

在上述示例中,currentTabComponent 能够包括

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

你能够在这里查阅并体验完整的代码,或在这个版本了解绑定组件选项对象,而不是已注册组件名的示例。

到目前为止,关于动态组件你须要了解的大概就这些了,若是你阅读完本页内容并掌握了它的内容,咱们会推荐你再回来把动态和异步组件读完。

解析 DOM 模板时的注意事项

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

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

<table>
<blog-post-row></blog-post-row>
</table>

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

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

须要注意的是若是咱们从如下来源使用模板的话,这条限制是不存在的:

到这里,你须要了解的解析 DOM 模板时的注意事项——实际上也是 Vue 的所有必要内容,大概就是这些了。恭喜你!接下来还有不少东西要去学习,不过首先,咱们推荐你先休息一下,试用一下 Vue,本身随意作些好玩的东西。

若是你感受已经掌握了这些知识,咱们推荐你再回来把完整的组件指南,包括侧边栏中组件深刻章节的全部页面读完。

 

组件注册

该页面假设你已经阅读过了组件基础。若是你还对组件不太了解,推荐你先阅读它。

组件名

在注册一个组件的时候,咱们始终须要给它一个名字。好比在全局注册的时候咱们已经看到了:

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

该组件名就是 Vue.component 的第一个参数。

你给予组件的名字可能依赖于你打算拿它来作什么。当直接在 DOM 中使用一个组件 (而不是在字符串模板或单文件组件) 的时候,咱们强烈推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及将来的 HTML 元素相冲突。

你能够在风格指南中查阅到关于组件名的其它建议。

组件名大小写

定义组件名的方式有两种:

使用 kebab-case

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

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

使用 PascalCase

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

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

全局注册

到目前为止,咱们只用过 Vue.component 来建立组件:

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

这些组件是全局注册的。也就是说它们在注册以后能够用在任何新建立的 Vue 根实例 (new Vue) 的模板中。好比:

Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

new Vue({ el: '#app' })
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>

在全部子组件中也是如此,也就是说这三个组件在各自内部也均可以相互使用。

局部注册

全局注册每每是不够理想的。好比,若是你使用一个像 webpack 这样的构建系统,全局注册全部的组件意味着即使你已经再也不使用一个组件了,它仍然会被包含在你最终的构建结果中。这形成了用户下载的 JavaScript 的无谓的增长。

在这些状况下,你能够经过一个普通的 JavaScript 对象来定义组件:

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }

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

new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})

对于 components 对象中的每一个属性来讲,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。

注意局部注册的组件在其子组件中不可用。例如,若是你但愿 ComponentA 在 ComponentB 中可用,则你须要这样写:

var ComponentA = { /* ... */ }

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

或者若是你经过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:

import ComponentA from './ComponentA.vue'

export default {
components: {
ComponentA
},
// ...
}

注意在 ES2015+ 中,在对象中放一个相似 ComponentA 的变量名实际上是 ComponentA: ComponentA 的缩写,即这个变量名同时是:

  • 用在模板中的自定义元素的名称
  • 包含了这个组件选项的变量名

模块系统

若是你没有经过 import/require 使用一个模块系统,也许能够暂且跳过这个章节。若是你使用了,那么咱们会为你提供一些特殊的使用说明和注意事项。

在模块系统中局部注册

若是你还在阅读,说明你使用了诸如 Babel 和 webpack 的模块系统。在这些状况下,咱们推荐建立一个 components 目录,并将每一个组件放置在其各自的文件中。

而后你须要在局部注册以前导入每一个你想使用的组件。例如,在一个假设的 ComponentB.js 或 ComponentB.vue 文件中:

import ComponentA from './ComponentA'
import ComponentC from './ComponentC'

export default {
components: {
ComponentA,
ComponentC
},
// ...
}

如今 ComponentA 和 ComponentC 均可以在 ComponentB 的模板中使用了。

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

可能你的许多组件只是包裹了一个输入框或按钮之类的元素,是相对通用的。咱们有时候会把它们称为基础组件,它们会在各个组件中被频繁的用到。

因此会致使不少组件里都会有一个包含基础组件的长列表:

import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'

export default {
components: {
BaseButton,
BaseIcon,
BaseInput
}
}

而只是用于模板中的一小部分:

<BaseInput
v-model="searchText"
@keydown.enter="search"
/>
<BaseButton @click="search">
<BaseIcon name="search"/>
</BaseButton>

幸亏若是你使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),那么就可使用 require.context 只全局注册这些很是通用的基础组件。这里有一份可让你在应用入口文件 (好比 src/main.js) 中全局导入基础组件的示例代码:

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.replace(/^\.\/(.*)\.\w+$/, '$1')
)
)

// 全局注册组件
Vue.component(
componentName,
// 若是这个组件选项是经过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 不然回退到使用模块的根。
componentConfig.default || componentConfig
)
})

记住全局注册的行为必须在根 Vue 实例 (经过 new Vue) 建立以前发生。这里有一个真实项目情景下的示例。

 

Prop

该页面假设你已经阅读过了组件基础。若是你还对组件不太了解,推荐你先阅读它。

Prop 的大小写 (camelCase vs kebab-case)

HTML 中的特性名是大小写不敏感的,因此浏览器会把全部大写字符解释为小写字符。这意味着当你使用 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>

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

Prop 类型

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

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

可是,一般你但愿每一个 prop 都有指定的值类型。这时,你能够以对象形式列出 prop,这些属性的名称和值分别是 prop 各自的名称和类型:

props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object
}

这不只为你的组件提供了文档,还会在它们遇到错误的类型时从浏览器的 JavaScript 控制台提示用户。你会在这个页面接下来的部分看到类型检查和其它 prop 验证

传递静态或动态 Prop

像这样,你已经知道了能够像这样给 prop 传入一个静态的值:

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

你也知道 prop 能够经过 v-bind 动态赋值,例如:

<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>

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

在上述两个示例中,咱们传入的值都是字符串类型的,但实际上任何类型的值均可以传给一个 prop。

传入一个数字

<!-- 即使 `42` 是静态的,咱们仍然须要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:likes="42"></blog-post>

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

传入一个布尔值

<!-- 包含该 prop 没有值的状况在内,都意味着 `true`。-->
<blog-post is-published></blog-post>

<!-- 即使 `false` 是静态的,咱们仍然须要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:is-published="false"></blog-post>

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

传入一个数组

<!-- 即使数组是静态的,咱们仍然须要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>

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

传入一个对象

<!-- 即使对象是静态的,咱们仍然须要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:author="{ name: 'Veronica', company: 'Veridian Dynamics' }"></blog-post>

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

传入一个对象的全部属性

若是你想要将一个对象的全部属性都做为 prop 传入,你可使用不带参数的 v-bind(取代 v-bind:prop-name)。例如,对于一个给定的对象 post

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>

单向数据流

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

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

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

  1. 这个 prop 用来传递一个初始值;这个子组件接下来但愿将其做为一个本地的 prop 数据来使用。在这种状况下,最好定义一个本地的 data 属性并将这个 prop 用做其初始值:

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

    props: ['size'],
    computed: {
    normalizedSize: function () {
    return this.size.trim().toLowerCase()
    }
    }

注意在 JavaScript 中对象和数组是经过引用传入的,因此对于一个数组或对象类型的 prop 来讲,在子组件中改变这个对象或数组自己将会影响到父组件的状态。

Prop 验证

咱们能够为组件的 prop 指定验证要求,例如你知道的这些类型。若是有一个需求没有被知足,则 Vue 会在浏览器控制台中警告你。这在开发一个会被别人用到的组件时尤为有帮助。

为了定制 prop 的验证方式,你能够为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:

Vue.component('my-component', {
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 ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})

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

注意那些 prop 会在一个组件实例建立以前进行验证,因此实例的属性 (如 datacomputed 等) 在 default 或 validator 函数中是不可用的。

类型检查

type 能够是下列原生构造函数中的一个:

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

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

function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}

你可使用:

Vue.component('blog-post', {
props: {
author: Person
}
})

来验证 author prop 的值是不是经过 new Person 建立的。

非 Prop 的特性

一个非 prop 特性是指传向一个组件,可是该组件并无相应 prop 定义的特性。

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

例如,想象一下你经过一个 Bootstrap 插件使用了一个第三方的 <bootstrap-date-input> 组件,这个插件须要在其 <input> 上用到一个 data-date-picker 特性。咱们能够将这个特性添加到你的组件实例上:

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

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

替换/合并已有的特性

想象一下 <bootstrap-date-input> 的模板是这样的:

<input type="date" class="form-control">

为了给咱们的日期选择器插件定制一个主题,咱们可能须要像这样添加一个特别的类名:

<bootstrap-date-input
data-date-picker="activated"
class="date-picker-theme-dark"
></bootstrap-date-input>

在这种状况下,咱们定义了两个不一样的 class 的值:

  • form-control,这是在组件的模板内设置好的
  • date-picker-theme-dark,这是从组件的父级传入的

对于绝大多数特性来讲,从外部提供给组件的值会替换掉组件内部设置好的值。因此若是传入 type="text" 就会替换掉 type="date" 并把它破坏!庆幸的是,class和 style 特性会稍微智能一些,即两边的值会被合并起来,从而获得最终的值:form-control date-picker-theme-dark

禁用特性继承

若是你不但愿组件的根元素继承特性,你能够在组件的选项中设置 inheritAttrs: false。例如:

Vue.component('my-component', {
inheritAttrs: false,
// ...
})

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

{
class: 'username-input',
placeholder: 'Enter your username'
}

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

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>
`
})

这个模式容许你在使用基础组件的时候更像是使用原始的 HTML 元素,而不会担忧哪一个元素是真正的根元素:

<base-input
v-model="username"
class="username-input"
placeholder="Enter your username"
></base-input>
相关文章
相关标签/搜索