Vue.js(读音/vjuː/, 类似于 view)是一个构建数据驱动的web 界面的渐进式框架。Vue.js的目标是通过尽可能简单的API 实现响应的数据绑定和组合的视图组件。只聚焦于视图层。
一、Vue实例
1、创建一个vue实例
每个 Vue 应用都是通过用Vue 函数创建一个新的Vue 实例开始的:
var vm = new Vue({ //选项 })
2、数据与方法
当一个Vue 实例被创建时,它向Vue 的响应式系统中加入了其data 对象中能找到的所有的属性。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
只有当实例被创建时data 中存在的属性才是响应式的。也就是说如果你添加一个新的属性,那么对它的改动将不会触发任何视图的更新
使用 Object.freeze(),这会阻止修改现有的属性,也意味着响应系统无法再追踪变化。
除了数据属性,Vue 实例还暴露了一些有用的实例属性与方法。它们都有前缀$,以便与用户定义的属性区分开来。
二、模板语法
1、插值
数据绑定最常见的形式就是使用“Mustache”语法(双大括号)的文本插值:Mustache标签将会被替代为对应数据对象上msg 属性的值。无论何时,绑定的数据对象上msg 属性发生了改变,插值处的内容都会更新。
<span>Message:{{ msg }}</span>
通过使用v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。
<span v-once>Message:{{msg}}</span>
原始html:双大括号会将数据解释为普通文本,而非HTML 代码。为了输出真正的HTML,你需要使用v-html 指令:
<p>Using v-html directive:<span v-html="rowhtml"></span></p>
特性:Mustache语法不能作用在HTML 特性上,遇到这种情况应该使用v-bind 指令:
<p v-bind:id="textid"></p>
使用JavaScript表达式:对于所有的数据绑定,Vue.js 都提供了完全的JavaScript 表达式支持。这些表达式会在所属 Vue 实例的数据作用域下作为JavaScript 被解析。有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。
<!-- 这是语句,不是表达式-->
{{ var a = 1 }}
流控制也不会生效,请使用三元表达式-->
{{ if (ok) { return message } }}
2、指令
指令 (Directives) 是带有v- 前缀的特殊属性。指令属性的值预期是单个JavaScript 表达式 (v-for 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM。
<p v-if="seen">现在你看到我了</p>
v-if 指令将根据表达式seen 的值的真假来插入/移除<p> 元素。
参数:一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind指令可以用于响应式地更新HTML 属性:
<a v-bind:href="href"></a>
<a :href="href"></a>
在这里 href 是参数,告知v-bind 指令将该元素的 href 属性与表达式url 的值绑定。
另一个例子是v-on指令,它用于监听DOM事件,在这里参数是监听的事件名。
<a v-on:click="dosomething">...</a>
<a @click="dosomething"></a>
可以用方括号括起来的 JavaScript 表达式作为一个指令的参数
<a v-bind:[name]="textid"></a>
name其值为id,则上式可以转换成<a v-bind:id="textid"></a>
修饰符:修饰符(Modifiers) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定,例如:
.prevent 修饰符告诉v-on 指令对于触发的事件调event.preventDefault():
<form v-on:submit.prevent="onSubmit"></form>
三、计算属性和侦听器
1、计算属性
<p>Computed reversed message: "{{ reversedMessage }}"</p>
computed: {
//计算属性的getter
reversedMessage: function () {
// `this`指向vm 实例
return this.message.split('').reverse().join('')
}
}
2、在表达式中调用方法
<p>Reversedmessage: "{{ reversedMessage() }}"</p>
// 在组件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
3、计算属性vs方法
两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。计算属性只有在它的相关响应式依赖发生改变时才会重新求值。这就意味着只要message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
4、侦听属性
当你有一些数据需要随着其它数据变动而变动时,你很容易滥用watch
watch: {
firstName: function (val) {
this.fullName = val + ' ' +this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + '' + val
}
}
5、计算属性vs侦听属性
computed: {
fullName: function () {
return this.firstName + ' ' +this.lastName
}
}
侦听属性代码命令式且重复
6、计算属性的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]
}
}
}
7、侦听器
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。Vue通过watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
使用 watch 选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
四、class与style绑定
1、绑定HTMLClass
对象语法:
v-bind:class 指令也可以与普通的class 属性共存
绑定的数据对象不必内联定义在模板里:
data: {
classObject: {
active: true,
'text-danger': false
}
}
数组语法:
<div v-bind:class="[activeClass,errorClass]"></div>
把一个数组传给v-bind:class,以应用一个 class 列表:
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
渲染为:<div class="active text-danger"></div>
用在组件上:当在一个自定义组件上使用class 属性时,这些类将被添加到该组件的根元素上面。这个元素上已经存在的类不会被覆盖。
2、绑定内联样式
对象语法:
<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 :style="[baseStyles, overridingStyles]"></div>
自动添加前缀:
当v-bind:style 使用需要添加浏览器引擎前缀的 CSS 属性时,如transform,Vue.js 会自动侦测并添加相应的前缀。
多重值
可以为style 绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:
<div :style="{display:['-webkit-box', '-ms-flexbox', 'flex']}"></div>
这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的flexbox,那么就只会渲染 display: flex。
五、条件渲染
1.v-if
在<template> 元素上使用 v-if 条件渲染分组:
因为v-if 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个<template> 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含<template> 元素。
可以使用v-else 指令来表示 v-if 的“else块”,v-else 元素必须紧跟在带 v-if 或者v-else-if 的元素的后面,否则它将不会被识别,v-else-if 也必须紧跟在带v-if 或者v-else-if 的元素之后。
用key管理可复用元素
Vue为你提供了一种方式来表达“两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的key 属性即可:
2、v-show
另一个用于根据条件展示元素的选项是v-show 指令:
不同的是带有v-show 的元素始终会被渲染并保留在 DOM 中。v-show只是简单地切换元素的CSS 属性display。v-show 不支持<template> 元素,也不支持 v-else。
3、v-if vs v-show
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。v-if也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于CSS 进行切换。
一般来说,v-if 有更高的切换开销,而v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
4、v-if 与v-for
当v-if 与v-for 一起使用时,v-for 具有比v-if 更高的优先级。
六、列表渲染
1、用v-for把一个数组对应为一组元素
用 v-for 指令根据一组数组的选项列表进行渲染。v-for指令需要使用item in items 形式的特殊语法,items 是源数据数组并且item 是数组元素迭代的别名。
在v-for 块中,我们拥有对父作用域属性的完全访问权限。v-for还支持一个可选的第二个参数为当前项的索引。
<ul id="example-2">
<li v-for="(item,index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
也可以用 of 替代 in 作为分隔符
2、一个对象的v-for
可以用v-for 通过一个对象的属性来迭代。
<ul id="v-for-object">
<li v-for="value in object">
{{value}}
</li>
</ul>
new Vue({
el: '#v-for-object',
data: {
object: {
firstName: 'John',
lastName: 'Doe',
age: 30
}
}
})
也可以提供第二个的参数为键名:
{{ key }}: {{ value }}
第三个参数为索引:
{{ index }}. {{ key }}: {{ value }}
3、key
为了给Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一key 属性。理想的key 值是每项都有的且唯一的id。这个特殊的属性相当于Vue 1.x 的 track-by ,但它的工作方式类似于一个属性,所以你需要用v-bind 来绑定动态值。建议尽可能在使用 v-for 时提供key,因为它是Vue 识别节点的一个通用机制,key并不与v-for 特别关联。
4、数组更新检测
变异方法:
Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新。
push() pop() shift() unshift() splice() sort() reverse()
变异方法(mutation method),顾名思义,会改变被这些方法调用的原始数组。
替换数组:非变异(non-mutating method) 方法,例如:filter(),
concat() 和 slice() 。这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组:
注意事项:
由于JavaScript 的限制,Vue 不能检测以下变动的数组:
当你利用索引直接设置一个项时:
Vue.set(vm.items, indexOfItem, newValue)
也可以使用vm.$set实例方法,该方法是全局方法 Vue.set 的一个别名:
vm.$set(vm.items, indexOfItem, newValue)
当你修改数组的长度时
使用vm.items.splice(newLength)
5、对象更改检测注意事项
由于JavaScript 的限制,Vue 不能检测对象属性的添加或删除:可以使用
Vue.set(object,key, value) 方法向嵌套对象添加响应式属性,也可以使用vm.$set 实例方法,它只是全局 Vue.set 的别名:Vue.$set(object,key, value)
有时你可能需要为已有对象赋予多个新属性,比如使用Object.assign() 或 _.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象
vm.userProfile =Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
6、显示过滤/排序结果
有时,我们想要显示一个数组的过滤或排序副本,而不实际改变或重置原始数据。在这种情况下,可以创建返回过滤或排序数组的计算属性。
<li v-for="value 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="value 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
})
}
}
7、一段取值范围的v-for
<span v-for="n in 10"> {{ n }}</span>
8、在template上使用v-for
类似于v-if,你也可以利用带有v-for 的 <template> 渲染多个元素。
9、v-for with v-if
当它们处于同一节点,v-for的优先级比v-if 更高,这意味着v-if 将分别重复运行于每个v-for 循环中:
10、一个组件的v-for
在自定义组件里,你可以像任何普通元素一样用 v-for 。
当在组件中使用 v-for 时,key 现在是必须的。
然而,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要用 props :
七、事件处理
1、监听事件
可以用v-on 指令监听DOM 事件,并在触发时运行一些JavaScript 代码。
<button v-on:click="count+=1">Add1</button>
<p>{{count}}</p>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})
2、事件处理方法
直接把JavaScript 代码写在 v-on 指令中是不可行的。因此v-on 还可以接收一个需要调用的方法名称。
3、内联处理器中的方法
除了直接绑定到一个方法,也可以在内联JavaScript 语句中调用方法,有时也需要在内联语句处理器中访问原始的DOM 事件。可以用特殊变量$event 把它传入方法。
4、事件修饰符
方法只有纯粹的数据逻辑,而不是去处理DOM 事件细节。解决了这个问题,Vue.js为v-on 提供了事件修饰符,修饰符是由点开头的指令后缀来表示的。
<!-- 阻止单击事件继续传播-->
<a v-on:click.stop="dothis"></a>
提交事件不再重载页面-->
<form v-on:submit.prevent="onsubmit"></form>
修饰符可以串联-->
<a v-on:click.stop.prevent="dothat"></a>
添加事件监听器时使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理-->
<div v-on:click.capture="dothis"></div>
只当在 event.target 是当前元素自身时触发处理函数,即事件不是从内部元素触发的-->
<div v-on:click.self="dothis"></div>
使用修饰符时,顺序很重要。
<!-- 点击事件将只会触发一次-->
<a v-on:click.once="dothis"></a>
<!-- 滚动事件的默认行为(即滚动行为)将会立即触发,而不会等待 `onScroll` 完成,这其中包含 `event.preventDefault()`的情况-->
<div v-on:scroll.passive="onScroll"></div>
不要把.passive 和 .prevent 一起使用,因为.prevent 将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive会告诉浏览器你不想阻止事件的默认行为。
5、按键修饰符
<!-- 只有在`keyCode` 是 13 时调用`vm.submit()` -->
缩写语法-->
全部的按键别名:
.enter .tab .delete (捕获“删除”和“退格”键) .esc .space .up .down .left .right
可以通过全局vue.config.keyCodes 对象自定义按键修饰符别名
自动匹配按键修饰符
你也可直接将KeyboardEvent.key暴露的任意有效按键名转换为kebab-case 来作为修饰符:
在上面的例子中,处理函数仅在$event.key === 'PageDown' 时被调用。
6、系统修饰符
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
.ctrl .alt .shift .meta
.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。
鼠标按钮修饰符.left .right .middle这些修饰符会限制处理函数仅响应特定的鼠标按钮。
7、为什么在HTML中监听事件
所有的Vue.js 事件处理方法和表达式都严格绑定在当前视图的ViewModel 上,它不会导致任何维护上的困难。实际上,使用v-on 有几个好处:
1、扫一眼HTML 模板便能轻松定位在JavaScript 代码里对应的方法。
2、因为你无须在JavaScript 里手动绑定事件,你的 ViewModel 代码可以是非常纯粹的逻辑,和DOM 完全解耦,更易于测试。
3、当一个ViewModel 被销毁时,所有的事件处理器都会自动被删除。你无须担心如何自己清理它们。
八、表单输入绑定
1、基础用法
你可以用v-model 指令在表单 <input> 及<textarea> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。
<input v-model="data1" placeholder="hahaha">
<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>
<br>
<span>Checked names: {{ checkedNames }}</span>
2、值绑定
有时我们可能想把值绑定到Vue 实例的一个动态属性上,这时可以用v-bind 实现,并且这个属性的值可以不是字符串。
3、修饰符
.lazy:在默认情况下,v-model在每次input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加lazy 修饰符,从而转变为使用change 事件进行同步:
.number:如果想自动将用户的输入值转为数值类型,可以给v-model 添加 number 修饰符:
.trim:如果要自动过滤用户输入的首尾空白字符,可以给v-model 添加 trim 修饰符:
4、在组件上使用v-model(后面返回来学习)
九、组件
1、什么是组件
组件(Component) 是 Vue.js 最强大的功能之一。组件可以扩展HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用is 特性进行了扩展的原生HTML 元素。
所有的Vue 组件同时也都是Vue 的实例,所以可接受相同的选项对象(除了一些根级特有的选项)并提供相同的生命周期钩子。
2、使用组件
要注册一个全局组件,可以使用Vue.component(tagName, options)。例如:
Vue.component('my-component', {data:function(){
return {count:0}
},template:'<button v-on:click="count++">You clicked me {{ count }} times.</button>'})
组件在注册之后,便可以作为自定义元素 <my-component></my-component> 在一个实例的模板中使用。注意确保在初始化根实例之前注册组件:
***一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
创建根实例
new Vue({
el: '#example'
})
局部注册:
你不必把每个组件都注册到全局。你可以通过某个 Vue 实例/组件的实例选项 components 注册仅在其作用域中可用的组件:
var Child = {
template: 'A custom component!'
}
new Vue({
// ...
components: {
//
将只在父组件模板中可用
'my-component': Child
}
})
Dom模板解析注意事项:
当使用 DOM 作为模板时 (例如,使用 el 选项来把 Vue 实例挂载到一个已有内容的元素上),你会受到 HTML 本身的一些限制,因为 Vue 只有在浏览器解析、规范化模板之后才能获取其内容,在自定义组件中使用这些受限制的元素时会导致一些问题,自定义组件 <my-row> 会被当作无效的内容,因此会导致错误的渲染结果。变通的方案是使用特殊的 is 特性:应当注意,如果使用来自以下来源之一的字符串模板,则没有这些限制:
JavaScript
内联模板字符串
.vue
组件
因此,请尽可能使用字符串模板。
Data必须是函数:构造 Vue 实例时传入的各种选项大多数都可以在组件里使用。只有一个例外:data 必须是函数。
组件组合:组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。
3、Prop
使用prop传递数据,组件实例的作用域是孤立的。这意味着不能 (也不应该)
在子组件的模板内直接引用父组件的数据。父组件的数据需要通过 prop 才能下发到子组件中。
Vue.component('child', {
//
声明props
props: ['message'],
//
就像 data 一样,prop 也可以在模板中使用
//
同样也可以在 vm 实例中通过 this.message 来使用
template: '{{ message }}'
})
camelCase vs. kebab-case
HTML
特性是不区分大小写的。所以,当使用的不是字符串模板时,camelCase (驼峰式命名)
的 prop 需要转换为相对应的 kebab-case (短横线分隔式命名):
Vue.component('child', {
//
在 JavaScript 中使用camelCase
props: ['myMessage'],
template: '{{ myMessage }}'
})
<!--
在 HTML 中使用kebab-case -->
动态Prop
与绑定到任何普通的 HTML 特性相类似,我们可以用 v-bind 来动态地将 prop 绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件:
如果你想把一个对象的所有属性作为 prop 进行传递,可以使用不带任何参数的 v-bind (即用 v-bind 而不是v-bind:prop-name)
将等价于:
v-bind:text="todo.text"
v-bind:is-complete="todo.isComplete"
>
字面量语法vs动态语法
初学者常犯的一个错误是使用字面量语法传递数值:
<!-- 传递了一个字符串"1" -->
因为它是一个字面量 prop,它的值是字符串 "1" 而不是一个数值。如果想传递一个真正的 JavaScript 数值,则需要使用 v-bind,从而让它的值被当作 JavaScript 表达式计算:
<!--传递真正的数值-->
单向数据流
Prop
是单向绑定的:当父组件的属性变化时,将传导给子组件,但是反过来不会。在两种情况下,我们很容易忍不住想去修改 prop 中数据:
Prop
作为初始值传入后,子组件想把它当作局部数据来用;
Prop
作为原始数据传入,由子组件处理成其它数据输出。
定义一个局部变量,并用 prop 的值初始化它:
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
定义一个计算属性,处理 prop 的值并返回:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
Prop验证
我们可以为组件的 prop 指定验证规则。如果传入的数据不符合要求,Vue
会发出警告,要指定验证规则,需要用对象的形式来定义 prop,而不能用字符串数组:
Vue.component('example', {
props: {
//
基础类型检测 (`null` 指允许任何类型)
propA: Number,
//
可能是多种类型
propB: [String, Number],
//
必传且是字符串
propC: {
type: String,
required: true
},
//
数值且有默认值
propD: {
type: Number,
default: 100
},
//
数组/对象的默认值应当由一个工厂函数返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
//
自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
}
}
})
type
可以是下面原生构造器:
String Number
Boolean Function Object
Array Symbol
type
也可以是一个自定义构造器函数,使用 instanceof 检测。
当 prop 验证失败,Vue 会抛出警告 (如果使用的是开发版本)。注意 prop 会在组件实例创建之前进行校验,所以在 default 或 validator 函数里,诸如 data、computed 或 methods 等实例属性还无法使用。
4、非prop特性
所谓非 prop 特性,就是指它可以直接传入组件,而不需要定义相应的 prop。尽管为组件定义明确的 prop 是推荐的传参方式,组件的作者却并不总能预见到组件被使用的场景。所以,组件可以接收任意传入的特性,这些特性都会被添加到组件的根元素上。
替换/合并现有的特性
对于多数特性来说,传递给组件的值会覆盖组件本身设定的值。即例如传递 type="large" 将会覆盖 type="date" 且有可能破坏该组件!所幸我们对待 class 和 style 特性会更聪明一些,这两个特性的值都会做合并 (merge) 操作,让最终生成的值为:form-control
date-picker-theme-dark。
5、自定义事件
使用v-on绑定自定义事件:每个 Vue 实例都实现了事件接口,即:
使用 $on(eventName) 监听事件
使用 $emit(eventName, optionalPayload) 触发事件
父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。
给组件绑定原生事件
有时候,你可能想在某个组件的根元素上监听一个原生事件。可以使用 v-on 的修饰符.native
.sync修饰符
.sync
还是有其适用之处,比如在开发可复用的组件库时。我们需要做的只是让子组件改变父组件状态的代码更容易被区分。
使用自定义事件的表单输入组件:
自定义事件可以用来创建自定义的表单输入组件,使用 v-model 来进行数据双向绑定。
自定义组件的v-model
默认情况下,一个组件的 v-model 会使用 value prop 和 input 事件。但是诸如单选框、复选框之类的输入类型可能把 value 用作了别的目的。model
选项可以避免这样的冲突。
非父子组件的通信
有时候,非父子关系的两个组件之间也需要通信。在简单的场景下,可以使用一个空的 Vue 实例作为事件总线:
6、使用插槽分发内容
为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为内容分发 (即 Angular 用户熟知的“transclusion”)。Vue.js 实现了一个内容分发 API,参照了当前 Web Components 规范草案,使用特殊的 <slot> 元素作为原始内容的插槽。
编译作用域:父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。
单个插槽:除非子组件模板包含至少一个 <slot> 插口,否则父组件的内容将会被丢弃。当子组件模板只有一个没有属性的插槽时,父组件传入的整个内容片段将插入到插槽所在的 DOM 位置,并替换掉插槽标签本身。
具名插槽:<slot>
元素可以用一个特殊的特性 name 来进一步配置如何分发内容。多个插槽可以有不同的名字。具名插槽将匹配内容片段中有对应 slot 特性的元素。仍然可以有一个匿名插槽,它是默认插槽,作为找不到匹配的内容片段的备用插槽。如果没有默认插槽,这些找不到匹配的内容片段将被抛弃。
作用域插槽:作用域插槽是一种特殊类型的插槽,用作一个 (能被传递数据的)
可重用模板,来代替已经渲染好的元素。
7、动态组件
通过使用保留的 <component> 元素,并对其 is 特性进行动态绑定,你可以在同一个挂载点动态切换多个组件:
var vm = new Vue({
el: '#example',
data: {
currentView: 'home'
},
components: {
home: { /* ... */ },
posts: { /* ... */ },
archive: { /* ... */ }
}
})
组件在 vm.currentview 变化时改变!-->
也可以直接绑定到组件对象上:
var Home = {
template: '
Welcome home!
'
}
var vm = new Vue({
el: '#example',
data: {
currentView: Home
}
})
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:
非活动组件将被缓存!-->
8、杂项
编写可复用组件:在编写组件时,最好考虑好以后是否要进行复用。一次性组件间有紧密的耦合没关系,但是可复用组件应当定义一个清晰的公开接口,同时也不要对其使用的外层数据作出任何假设。
Vue
组件的 API 来自三部分——prop、事件和插槽:
(1)Prop 允许外部环境传递数据给组件;
(2)事件允许从组件内触发外部环境的副作用;
(3)插槽允许外部环境将额外的内容组合在组件中。
子组件引用
尽管有 prop 和事件,但是有时仍然需要在 JavaScript 中直接访问子组件。为此可以使用 ref 为子组件指定一个引用 ID。
var parent = new Vue({ el: '#parent' })
//
访问子组件实例
var child = parent.$refs.profile
当 ref 和 v-for 一起使用时,获取到的引用会是一个数组,包含和循环数据源对应的子组件。
异步组件:在大型应用中,我们可能需要将应用拆分为多个小模块,按需从服务器下载。为了进一步简化,Vue.js 允许将组件定义为一个工厂函数,异步地解析组件的定义。Vue.js 只在组件需要渲染时触发工厂函数,并且把结果缓存起来,用于后面的再次渲染。
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
//
将组件定义传入 resolve 回调函数
resolve({
template: '
I am async!
'
})
}, 1000)
})
组件命名约定:当注册组件 (或者 prop) 时,可以使用 kebab-case (短横线分隔命名)、camelCase (驼峰式命名)
或 PascalCase (单词首字母大写命名)。
递归组件:组件在它的模板内可以递归地调用自己。不过,只有当它有 name 选项时才可以这么做,当你利用 Vue.component 全局注册了一个组件,全局的 ID 会被自动设置为组件的 name。要确保递归调用有终止条件 (比如递归调用时使用 v-if 并最终解析为 false)。
组件间的循环引用:假设你正在构建一个文件目录树,像在 Finder 或资源管理器中。你可能有一个 tree-folder 组件以及一个 tree-folder-contents 组件,在我们的例子中,可以选择让 tree-folder 组件中来做这件事。我们知道引起矛盾的子组件是 tree-folder-contents,所以我们要等到 beforeCreate 生命周期钩子中才去注册它。
内联模板:如果子组件有 inline-template 特性,组件将把它的内容当作它的模板,而不是把它当作分发内容。这让模板编写起来更灵活。
这些将作为组件自身的模板。
而非父组件透传进来的内容。
inline-template
让模板的作用域难以理解。使用 template 选项在组件内定义模板或者在 .vue 文件中使用 template 元素才是最佳实践。
X-template:另一种定义模板的方式是在 JavaScript 标签里使用 text/x-template 类型,并且指定一个 id。
Hello hello hello
Vue.component('hello-world', {
template: '#hello-world-template'
})
这在有很多大模板的演示应用或者特别小的应用中可能有用,其它场合应该避免使用,因为这将模板和组件的其它定义分离了。
对低开销的静态组件使用 v-once:尽管在 Vue 中渲染 HTML 很快,不过当组件中包含大量静态内容时,可以考虑使用 v-once 将渲染结果缓存起来,就像这样:
Vue.component('terms-of-service', {
template: '\
\
Terms of Service
\
...
很多静态内容...\
\
'
})
十、组件注册
1、组件名
给予组件的名字可能依赖于你打算拿它来做什么。当直接在 DOM 中使用一个组件 (而不是在字符串模板或单文件组件)的时候,我们强烈推荐遵循W3C规范中的自定义组件名 (字母全小写且必须包含一个连字符)。
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>。
当使用 PascalCase (驼峰式命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name> 和 <MyComponentName> 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板)
中使用时只有 kebab-case 是有效的。
2、全局注册
到目前为止,我们只用过 Vue.component 来创建组件,这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。
3、局部注册
4、模块系统
在模块系统中局部注册:
需要在局部注册之前导入每个你想使用的组件。例如,在一个假设的 ComponentB.js 或 ComponentB.vue 文件中:
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
ComponentA,
ComponentC
},
// ...
}
现在 ComponentA 和 ComponentC 都可以在 ComponentB 的模板中使用了。
基础组件的自动化全局注册
可能你的许多组件只是包裹了一个输入框或按钮之类的元素,是相对通用的。我们有时候会把它们称为基础组件,它们会在各个组件中被频繁的用到。
所以会导致很多组件里都会有一个包含基础组件的长列表: