基础
官方教程:https://cn.vuejs.org/v2/guide/
示例
1 | <!-- 开发环境版本,包含了有帮助的命令行警告 --> |
1 | var vue = new Vue({ |
1 | <div id="app"> |
标签语法
数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值:
1 | <span>Message: {{ msg }}</span> |
v-if与v-else
1 | <div v-if="boolVar"> true的内容 </div> |
可以把一个 <template>
元素当做不可见的包裹元素,并在上面使用 v-if
。最终的渲染结果将不包含 <template>
元素(v-for
也可):
1 | <template v-if="ok">...</template> |
v-else-if
1 | <div v-if="type === 'A'"> A </div> |
用key管理可复用的元素
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:
1 | <template v-if="loginType === 'username'"> |
那么在上面的代码中切换 loginType
将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,<input>
不会被替换掉——仅仅是替换了它的 placeholder
。
这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key
attribute 即可:
1 | <template v-if="loginType === 'username'"> |
现在,每次切换时,输入框都将被重新渲染。
注意,<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
vsv-show
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。相比之下,
v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。一般来说,
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show
较好;如果在运行时条件很少改变,则使用v-if
较好。
v-for
在 v-for
块中,可以访问所有父作用域的 property。
遍历数组
1 | <div id="app"> |
其中in
可以替换为of
:
1 | <div v-for="item of items"></div> |
遍历对象
在遍历对象时,会按
Object.keys()
的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。
1 | <li v-for="value in object"> |
也可以提供第二个的参数为 property 名称 (也就是键名):
1 | <div v-for="(value, name) in object"> |
还可以用第三个参数作为索引:
1 | <div v-for="(value, name, index) in object"> |
维护状态
它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key
attribute。
1 | <div v-for="item in items" v-bind:key="item.id"> |
不要使用对象或数组之类的非基本类型值作为
v-for
的key
。请用字符串或数值类型的值。
数组更新检测
注意:由于 JavaScript 的限制,Vue 不能检测数组和对象的变化,只是侦听一些方法的调用。
变更方法
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
你可以打开控制台,然后对前面例子的 items
数组尝试调用变更方法。比如 vm.items.push({ message: 'Baz' })
。
替换数组
filter()
、concat()
和 slice()
等,不会变更原始数组,而总是返回一个新数组。
1 | vm.items = vm.items.filter(function (item) { |
Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
数值循环
1 | <div> |
会重复 1 ~ 10。
v-for
与 v-if
一同使用
不推荐在同一元素上使用 v-if
和 v-for
。
当它们处于同一节点,v-for
的优先级比 v-if
更高,这意味着 v-if
将分别重复运行于每个 v-for
循环中。当你只想为部分项渲染节点时,这种优先级的机制会十分有用,如下:
1 | <li v-for="todo in todos" v-if="!todo.isComplete"> |
上面的代码将只渲染未完成的 todo。
而如果你的目的是有条件地跳过循环的执行,那么可以将 v-if
置于外层元素 (或 template
) 上。如:
1 | <ul v-if="todos.length"> |
在组件上使用v-for
1 | <my-component |
组件中的 :key
是必需的。
简单的 todo 列表例子
1 | <div id="todo-list-example"> |
注意这里的
is="todo-item"
attribute。这种做法在使用 DOM 模板时是十分必要的,因为在<ul>
元素内只有<li>
元素会被看作有效内容。这样做实现的效果与<todo-item>
相同,但是可以避开一些潜在的浏览器解析错误。
1 | Vue.component('todo-item', { |
v-text / v-html
1 | <!-- 会输出标签 --> |
v-on 事件绑定
简写示例:<a @click="doSomething">...</a>
、<a @[event]="doSomething"> ... </a>
可以是直接运行JS语句、调用vue的method(无参、有参皆可)。
1 | var vm = new Vue({ |
1 | <button v-on:click="add">+1</button> |
HTML的
onclick="func()"
带有括号,Vue的不带括号
或者带参数的方式:
1 | <button v-on:click="modify(1)">+1</button> |
原生DOM事件
有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event
把它传入方法:
1 | <button v-on:click="warn('Form cannot be submitted yet.', $event)"> Submit </button> |
1 | // ... |
事件修饰符
修饰符是由点开头的指令后缀来表示的。
.stop
.prevent
.capture
.self
.once
.passive
1 | <!-- 阻止单击事件继续传播 --> |
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。
因此,用
v-on:click.prevent.self
会阻止所有的点击,而
v-on:click.self.prevent
只会阻止对元素自身的点击。
按键修饰符
1 | <!-- 只有在 `key` 是 `Enter` 时调用 `vm.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 | // 可以使用 v-on:keyup.f1 |
系统修饰键
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
.ctrl
.alt
.shift
.meta
注意:在 Mac 上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。其他系统类似。
1 | <!-- Alt + C --> |
只有在按住
ctrl
的情况下释放其它按键,才能触发keyup.ctrl
。而单单释放ctrl
也不会触发事件。如果你想要这样的行为,请为ctrl
换用keyCode
:keyup.17
。
.extra修饰符
.exact
修饰符允许你控制由精确的系统修饰符组合触发的事件。
1 | <!-- 即使 Alt 或 Shift 被一同按下时也会触发 --> |
鼠标按钮修饰符
.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 | <input type="checkbox" id="Check1" value="Val1" v-model="checked" /> |
多个复选框绑定一个数组
1 | <input type="checkbox" id="Check1" value="Val1" v-model="arr" /> |
多个单选框
1 | <input type="radio" id="one" value="One" v-model="picked" /> |
下拉列表
可以单选也可以多选。单选时是一个字符串,多选时是绑定到一个数组
1 | data:{ |
1 | <select v-model="array" multiple> |
如果
v-model
表达式的初始值未能匹配任何选项,<select>
元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。
值绑定
有时我们可能想把值绑定到 Vue 实例的一个动态 property 上,这时可以用 v-bind
实现,并且这个 property 的值可以不是字符串。
复选框
1 | <input |
1 | // 当选中时 |
这里的
true-value
和false-value
attribute 并不会影响输入控件的value
attribute,因为浏览器在提交表单时并不会包含未被选中的复选框。如果要确保表单中这两个值中的一个能够被提交,(即“yes”或“no”),请换用单选按钮。
单选按钮
1 | <input type="radio" v-model="pick" v-bind:value="a"> |
1 | // 当选中时 |
选择框
1 | <select v-model="selected"> |
1 | // 当选中时 |
修饰符
.lazy
在默认情况下,v-model
在每次 input
事件触发后将输入框的值与数据进行同步 (除了输入法组合文字时)。你可以添加 lazy
修饰符,从而转为在 change
事件_之后_进行同步:
1 | <!-- 在“change”时而非“input”时更新 --> |
.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:id
、v-bind:disabled
等。
简写示例:<a :href="url">...</a>
、<a :[key]="url"> ... </a>
img.src绑定
1 | <img v-bind:src="imageSrc" :width="width" /> |
1 | data:{ |
v-bind:class
1 | <style> |
1 | data: { |
style判断
1 | <!-- 单个style --> |
复杂style对象
1 | <div v-bind:class="classObject"></div> |
1 | data: { |
也可以是下面的:
1 | data: { |
用在组件上
当在一个自定义组件上使用 class
property 时,这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖。
例如,如果你声明了这个组件:
1 | Vue.component('my-component', { |
然后在使用它的时候添加一些 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 | <div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> |
直接绑定到一个样式对象通常更好,这会让模板更清晰:
1 | <div v-bind:style="styleObject"></div> |
同样的,对象语法常常结合返回对象的计算属性使用。
数组语法
将多个样式对象应用到同一个元素上:
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 | {{ number + 1 }} |
有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。
1 | <!-- 这是语句,不是表达式 --> |
模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如
Math
和Date
。你不应该在模板表达式中试图访问用户定义的全局变量。
绑定动态属性
从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:
1 | <!-- 注意,参数表达式的写法存在一些约束 --> |
这里的 attributeName
会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。
同样地,你可以使用动态参数为一个动态的事件名绑定处理函数:
1 | <a v-on:[eventName]="doSomething"> ... </a> |
在这个示例中,当 eventName
的值为 "focus"
时,v-on:[eventName]
将等价于 v-on:focus
。
对动态参数的值的约束
动态参数预期会求出一个字符串,异常情况下值为 null
。这个特殊的 null
值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。
对动态参数表达式的约束
动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。例如:
1 | <!-- 这会触发一个编译警告 --> |
变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式。
还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写。
修饰符
以半角句号 .
指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent
修饰符告诉 v-on
指令对于触发的事件调用 event.preventDefault()
:
1 | <form v-on:submit.prevent="onSubmit">...</form> |
杂项API
和其余部分有些重复,稍微看看就好
Vue实例
当一个 Vue 实例被创建时,它将 data
对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。只有当实例被创建时就已经存在于 data
中的 property 才是响应式的。
1 | // 我们的数据对象 |
这里唯一的例外是使用 Object.freeze()
,这会阻止修改现有的 property,也意味着响应系统无法再追踪变化。
一些有用的实例 property 与方法。它们都有前缀 $
,以便与用户定义的 property 区分开来:
1 | vm.$data === data // => true |
Vue.extend
构造一个组件的语法器
1 | <div id="header"></div> |
1 | var header = Vue.extend({ |
Vue.set
设置数据响应
1 | <ul> |
1 | var sql = { |
生命周期
beforeCreate
初始化之后created
创建完成beforeMount
挂载之前mounted
被创建- — 开始操作的位置 —
beforeUpdate
数据更新前updated
数据更新之后beforeDestroy
销毁之前(使用vm.$destroy()
)destroyed
销毁之后
组件 component
注册一个全局组件,使用:
1 | Vue.component(tagName, options); |
1 | <hello></hello> |
局部组件
1 | // 只能应用在对应 'app' 中的组件 |
带参数的component
1 | <div id="app-7"> |
1 | Vue.component('todo-item', { |
Vue选项
computed
可以在使用时进行函数计算而不是使用computed,两种结果方式完全相同;但计算属性是基于它们的响应式依赖进行缓存的,只要属性值没变,就不必再次执行函数。
示例:计算 fullName
1 | var vm = new Vue({ |
计算属性的setter
1 | computed: { |
运行 vm.fullName = 'John Doe'
时,setter 会被调用,vm.firstName
和 vm.lastName
也会相应地被更新。
method
watch
1 | <p>原价:¥{{prevPrice}]</p> |
props
props 可以是数组或对象,用于接收来自父组件的数据
1 | Vue.component('simple', { |
mixin 混入
接收一个混合对象的数组。这些混合实例对象可以像正常的示例对象一样包含选项,他们将在 Vue.extend()
里最终选择使用相同的选项合并逻辑合并。
1 | <script> |
实例
$mount()
挂载$destroy()
卸载$forceUpdate()
更新(迫使Vue实例重新渲染)$nextTick()
回调$on()
监听当前实例上的自定义事件$once
监听第一个自定义事件,但是只触发一次;之后移除监听器$emit()
触发当前实例上的事件$off()
移除事件监听器
1 | <div id='header'></div> |
事件
1 | // 绑定事件(添加监听器) |
slot 扩展
标签内容的扩展,常与组件component联合使用
1 | <slot name=''></slot> |
1 | <div id='app'> |
Ajax
1 | beforeMount: function(){ // 数据挂载之前 |
组件基础
基本示例
1 | new Vue({ el: '#components-demo' }); |
组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 <button-counter>
。我们可以在一个通过 new Vue
创建的 Vue 根实例中,把这个组件作为自定义元素来使用:
1 | <div id="components-demo"> |
注意当点击按钮时,每个组件都会各自独立维护它的 count
。
此处的示例是全局注册,可以用在其被注册之后的任何新创建的 Vue 根实例。
data 必须是一个函数
data
并不是直接提供一个对象,而必须是一个函数。因此每个实例可以维护一份被返回对象的独立的拷贝。
如果 Vue 没有这条规则,点击一个按钮就可能会影响到其它所有实例。
Prop 向子组件传递数据
Prop 在组件上注册的一些自定义 attribute,包含在该组件可接受的 prop 列表中:
1 | Vue.component('blog-post', { |
1 | <blog-post title="My journey with Vue"></blog-post> |
然而在一个典型的应用中,你可能在 data
里有一个博文的数组:
1 | new Vue({ |
并想要为每篇博文渲染一个组件:
1 | <blog-post |
如上所示,你会发现我们可以使用 v-bind
来动态传递 prop。
单个根元素
每个组件必须只有一个根元素,可以将模板的内容包裹在一个父元素内,来修复这个问题,例如:
1 | <div class="blog-post"> |
当组件变得越来越复杂的时候,尝试重构一下这个 <blog-post>
组件,让它变成接受一个单独的 post
prop:
1 | <blog-post |
1 | Vue.component('blog-post', { |
上述的这个和一些接下来的示例使用了 JavaScript 的模板字符串来让多行的模板更易读。它们在 IE 下并没有被支持,所以如果你需要在不 (经过 Babel 或 TypeScript 之类的工具) 编译的情况下支持 IE,请使用折行转义字符取而代之。
emit 监听子组件事件
子组件:
1 | <button v-on:click="$emit('enlarge-text')"> |
父组件:
1 | <blog-post |
带参数的子组件事件
例如我们可能想让 <blog-post>
组件决定它的文本要放大多少,这时可以使用 $emit
的第二个参数来提供这个值:
1 | <button v-on:click="$emit('enlarge-text', 0.1)"> |
然后当在父级组件监听这个事件的时候,我们可以通过 $event
访问到被抛出的这个值:
1 | <blog-post |
或者,如果这个事件处理函数是一个方法:
1 | <blog-post |
那么这个值将会作为第一个参数传入这个方法:
1 | methods: { |
在组件上使用 v-model
1 | <input v-model="searchText"> |
等价于:
1 | <input |
当用在组件上时,v-model
则会这样:
1 | <custom-input |
为了让它正常工作,这个组件内的 <input>
必须:
- 将其
value
attribute 绑定到一个名叫value
的 prop 上 - 在其
input
事件被触发时,将新的值通过自定义的input
事件抛出
写成代码之后是这样的:
1 | Vue.component('custom-input', { |
现在 v-model
就应该可以在这个组件上完美地工作起来了:
1 | <custom-input v-model="searchText"></custom-input> |
通过插槽分发内容
1 | Vue.component('alert-box', { |
只要在需要的地方加入插槽就行了。
动态组件
有的时候,在不同组件之间进行动态切换是非常有用的,比如多标签的界面。
通过 Vue 的 <component>
元素加一个特殊的 is
attribute 来实现:
1 | <!-- 组件会在 `currentTabComponent` 改变时改变 --> |
currentTabComponent
可以包括
- 已注册组件的名字,或
- 一个组件的选项对象
DOM注意事项
有些 HTML 元素,诸如 <ul>
、<ol>
、<table>
和 <select>
,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>
、<tr>
和 <option>
,只能出现在其它某些特定的元素内部。
这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:
1 | <table> |
这个自定义组件 <blog-post-row>
会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is
attribute 给了我们一个变通的办法:
1 | <table> |
需要注意的是如果我们从以下来源使用模板的话,这条限制是*不存在*的:
- 字符串 (例如:
template: '...'
) - 单文件组件 (
.vue
) <script type="text/x-template">
深入组件
组件注册
组件名
强烈推荐遵循 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 | Vue.component('my-component-name', { |
局部注册
通过一个普通的 JavaScript 对象来定义组件:
1 | var ComponentA = { /* ... */ } |
在 components
选项中定义你想要使用的组件:
1 | new Vue({ |
注意局部注册的组件在其子组件中*不可用*。例如,如果你希望 ComponentA
在 ComponentB
中可用,则你需要这样写:
1 | var ComponentA = { /* ... */ } |
或者如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:
1 | import ComponentA from './ComponentA.vue' |
1 | export default { |
模块系统
Prop
Prop的大小写
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
1 | Vue.component('blog-post', { |
1 | <!-- 在 HTML 中是 kebab-case 的 --> |
重申一次,如果使用字符串模板,那么这个限制就不存在了。
Prop类型
到这里,我们只看到了以字符串数组形式列出的 prop:
1 | props: ['title', 'likes', 'isPublished', 'commentIds', 'author'] |
通常你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:
1 | props: { |
传递动态Prop
数组用法:
1 | <blog-post |
静态传递:
1 | <blog-post title="My journey with Vue"></blog-post> |
通过 v-bind
动态赋值:
1 | <!-- 动态赋予一个变量的值 --> |
传入一个对象
1 | <!-- 这是一个 JavaScript 表达式而不是一个字符串。--> |
传入一个对象所有 property
如果你想要将一个对象的所有 property 都作为 prop 传入,你可以使用不带参数的 v-bind
(取代 v-bind:prop-name
)。例如,对于一个给定的对象 post
:
1 | post: { |
下面的模板:
1 | <blog-post v-bind="post"></blog-post> |
等价于:
1 | <blog-post |
单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
这里有两种常见的试图变更一个 prop 的情形:
这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:
1
2
3
4
5
6props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
1
2
3
4
5
6props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
Prop验证
如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。
为 props
中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:
1 | Vue.component('my-component', { |
当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如
data
、computed
等) 在default
或validator
函数中是不可用的。
类型检查
type
可以是下列原生构造函数中的一个:
String
Number
Boolean
Array
Object
Date
Function
Symbol
额外的,type
还可以是一个自定义的构造函数,并且通过 instanceof
来进行检查确认。例如,给定下列现成的构造函数:
1 | function Person (firstName, lastName) { |
你可以使用:
1 | Vue.component('blog-post', { |
来验证 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"
并把它破坏!庆幸的是,class
和 style
attribute 会稍微智能一些,即两边的值会被合并起来。
禁用Attribute继承
如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false
。例如:
1 | Vue.component('my-component', { |
这尤其适合配合实例的 $attrs
property 使用,该 property 包含了传递给一个组件的 attribute 名和 attribute 值,例如:
1 | { |
有了 inheritAttrs: false
和 $attrs
,你就可以手动决定这些 attribute 会被赋予哪个元素。在撰写基础组件的时候是常会用到的:
1 | Vue.component('base-input', { |
自定义事件
不同于组件和 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 | Vue.component('base-checkbox', { |
现在在这个组件上使用 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 | <label> |
这时,父级的 .native
监听器将静默失败。它不会产生任何报错,但是 onFocus
处理函数不会如你预期地被调用。
为了解决这个问题,Vue 提供了一个 $listeners
property,它是一个对象,里面包含了作用在这个组件上的所有监听器。例如:
1 | { |
有了这个 $listeners
property,你就可以配合 v-on="$listeners"
将所有的事件监听器指向这个组件的某个特定的子元素。对于类似 <input>
的你希望它也可以配合 v-model
工作的组件来说,为这些监听器创建一个类似下述 inputListeners
的计算属性通常是非常有用的:
1 | Vue.component('base-input', { |
现在 <base-input>
组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input>
元素一样使用了:所有跟它相同的 attribute 和监听器都可以工作,不必再使用 .native
监听器。
.sync修饰符
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件两侧都没有明显的变更来源。
这也是为什么我们推荐以 update:myPropName
的模式触发事件取而代之。举个例子,在一个包含 title
prop 的假设的组件中,我们可以用以下方法表达对其赋新值的意图:
1 | this.$emit('update:title', newTitle) |
然后父组件可以监听那个事件并根据需要更新一个本地的数据 property。例如:
1 | <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 }”
,是无法正常工作的,因为在解析一个像这样的复杂表达式的时候,有很多边缘情况需要考虑。