组件 (Component) 是 Vue.js 最强大的功能之一。组件能够扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些状况下,组件也能够表现为用 is
特性进行了扩展的原生 HTML 元素。javascript
全部的
Vue 组件
同时也都是Vue 的实例
,因此可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。php
html代码:css
<div id="example">
<my-component></my-component> </div>
JS代码:html
// 注册 Vue.component('my-component', { template: '<div>A custom component!</div>' }) var vm = new Vue({ el: '#example', data: { } })
渲染结果为:vue
<div id="example"> <div>A custom component!</div> </div>
或者另一种注册方式,经过 全局API:Vue.extend()
代码以下:java
// 注册 var MyComponent = Vue.extend({ template: '<div>A custom component!</div>' }); // 注册 Vue.component('my-component', MyComponent); var vm = new Vue({ el: '#example', data: { } })
下面说明下Vue.extend( options )
的使用。
参数:{Object} options
用法:使用基础 Vue 构造器,建立一个“子类”。参数是一个包含组件选项的对象
。data
选项是特例,须要注意 - 在 Vue.extend()
中它必须是函数
。jquery
<div id="mount-point"></div>
// 建立构造器 var Profile = Vue.extend({ template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>', data: function () { return { firstName: 'Walter', lastName: 'White', alias: 'Heisenberg' } } }) // 建立 Profile 实例,并挂载到一个元素上。 new Profile().$mount('#mount-point')
结果以下:webpack
<p>Walter White aka Heisenberg</p>
上面又用到了实例方法vm.$mount()
,下面说明下它的使用方式。web
参数:vue-router
{Element | string} [elementOrSelector]
{boolean} [hydrating]
返回值:vm
- 实例自身
用法:
若是 Vue
实例在实例化时没有收到 el
选项,则它处于“未挂载
”状态,没有关联的 DOM
元素。可使用 vm.$mount()
手动地挂载一个未挂载的实例。
若是没有提供 elementOrSelector
参数,模板将被渲染为文档以外的的元素,而且你必须使用原生 DOM API
把它插入文档中。
这个方法返回实例自身,于是能够链式调用其它实例方法。
var MyComponent = Vue.extend({
template: '<div>Hello!</div>' }) // 建立并挂载到 #app (会替换 #app) new MyComponent().$mount('#app') // 同上 new MyComponent({ el: '#app' }) // 或者,在文档以外渲染而且随后挂载 var component = new MyComponent().$mount() document.getElementById('app').appendChild(component.$el)
你没必要把每一个组件都注册到全局。你能够经过某个 Vue 实例/组件的实例选项 components 注册仅在其做用域中可用的组件:
var Child = {
template: '<div>A custom component!</div>' } new Vue({ // ... components: { // <my-component> 将只在父组件模板中可用 'my-component': Child } })
这种封装也适用于其它可注册的 Vue 功能,好比指令
。
像 <ul>、<ol>、<table>、<select>
这样的元素里容许包含的元素有限制,而另外一些像 <option>
这样的元素只能出如今某些特定元素的内部。
例如:
<table>
<my-row>...</my-row> </table>
自定义组件 <my-row>
会被看成无效的内容,所以会致使错误的渲染结果。变通的方案是使用特殊的 is
特性:
<table> <tr is="my-row"></tr> </table>
应当注意,若是使用来自如下来源之一的字符串模板,则没有这些限制:
<script type="text/x-template">
JavaScript
内联模板字符串.vue
组件所以,请尽量使用字符串模板。
以下代码:
Vue.component('my-component', {
template: '<span>{{ message }}</span>', data: { message: 'hello' } })
那么 Vue 会中止运行,并在控制台发出警告,告诉你在组件实例中 data
必须是一个函数。
咱们来理解下,看下面代码:
<div id="example-2"> <simple-counter></simple-counter> <simple-counter></simple-counter> <simple-counter></simple-counter> </div>
var data = { counter: 0 } Vue.component('simple-counter', { template: '<button v-on:click="counter += 1">{{ counter }}</button>', // 技术上 data 的确是一个函数了,所以 Vue 不会警告, // 可是咱们却给每一个组件实例返回了同一个对象的引用 data: function () { return data } }) new Vue({ el: '#example-2' })
因为这三个组件实例共享
了同一个 data
对象,所以递增一个 counter
会影响全部组件!这就错了。咱们能够经过为每一个组件返回全新的数据对象来修复这个问题:
data: function () { return { counter: 0 } }
如今每一个 counter 都有它本身内部的状态了,不会相互影响。
最多见的应用就是:组件 A 在它的模板中使用了组件 B。它们之间必然须要相互通讯:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。
在 Vue 中,父子组件的关系能够总结为 prop
向下传递,事件
向上传递。父组件经过 prop
给子组件下发数据,子组件经过事件
给父组件发送消息。
实例1:
Vue.component('child', {
// 声明 props props: ['message'], // 就像 data 同样,prop 也能够在模板中使用 // 一样也能够在 vm 实例中经过 this.message 来使用 template: '<span>{{ message }}</span>' }) <child message="hello!"></child>
结果:hello!
实例2:
HTML 特性是不区分大小写的。因此,当使用的不是字符串模板时,camelCase (驼峰式命名) 的 prop 须要转换为相对应的 kebab-case (短横线分隔式命名):
Vue.component('child', {
// 在 JavaScript 中使用 camelCase
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>' }) <!-- 在 HTML 中使用 kebab-case --> <child my-message="hello!"></child>
若是你使用字符串模板,则没有这些限制。
<div> <input v-model="parentMsg"> <br> <child v-bind:my-message="parentMsg"></child> </div>
你也可使用 v-bind 的缩写语法:
<child :my-message="parentMsg"></child>
(重要)
若是你想把一个对象
的全部属性
做为 prop 进行传递,可使用不带任何参数的 v-bind
(即用 v-bind 而不是 v-bind:prop-name)。例如,已知一个 todo
对象:
todo: {
text: 'Learn Vue', isComplete: false }
而后:
<todo-item v-bind="todo"></todo-item>
将等价于:
<todo-item
v-bind:text="todo.text" v-bind:is-complete="todo.isComplete" ></todo-item>
初学者常犯的一个错误是使用字面量语法传递数值:
<!-- 传递了一个字符串 "1" --> <comp some-prop="1"></comp>
由于它是一个字面量 prop
,它的值是字符串 "1" 而不是一个数值。若是想传递一个真正的 JavaScript 数值,则须要使用 v-bind
,从而让它的值被看成 JavaScript 表达式
计算:
<!-- 传递真正的数值 --> <comp v-bind:some-prop="1"></comp>
Prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,可是反过来不会。这是为了防止子组件无心间修改了父组件的状态,来避免应用的数据流变得难以理解。
另外,每次父组件更新时,子组件的全部 prop 都会更新为最新值。这意味着你不该该
在子组件内部改变 prop
。若是你这么作了,Vue 会在控制台给出警告。
在两种状况下,咱们很容易忍不住想去修改 prop 中数据:
对这两种状况,正确的应对方式是:
1.定义一个局部变量,并用 prop 的值初始化它:
props: ['initialCounter'],
data: function () { return { counter: this.initialCounter } }
2.定义一个计算属性,处理 prop 的值并返回:
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
注意在 JavaScript 中
对象
和数组
是引用类型,指向同一个内存空间
,若是 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
等实例属性还没法使用。
其它实例:
Vue.component('modal', {
template: '#modal-template', props: { show: { type: Boolean, required: true, twoWay: true } } });
twoWay Prop
的参数 移除
。Props 如今只能单向传递。为了对父组件产生反向影响,子组件须要显式地传递一个事件而不是依赖于隐式地双向绑定。因此上面的的最后一个实例只是贴出来代码而已,最新版本已经移除了。
所谓非 prop 特性,就是指它能够直接传入组件,而不须要定义相应的 prop。
尽管为组件定义明确的 prop 是推荐的传参方式,组件的做者却并不总能预见到组件被使用的场景。因此,组件能够接收任意传入的特性
,这些特性都会被添加到组件的根元素上
。
例如,假设咱们使用了第三方组件 bs-date-input,它包含一个 Bootstrap 插件,该插件须要在 input 上添加 data-3d-date-picker 这个特性。这时能够把特性直接添加到组件上 (不须要事先定义 prop):
<bs-date-input data-3d-date-picker="true"></bs-date-input>
添加属性 data-3d-date-picker="true"
以后,它会被自动添加到 bs-date-input
的根元素上。
假设这是 bs-date-input 的模板:
<input type="date" class="form-control">
为了给该日期选择器插件增长一个特殊的主题,咱们可能须要增长一个特殊的 class,好比:
<bs-date-input
data-3d-date-picker="true" class="date-picker-theme-dark" ></bs-date-input>
最终在根元素上生成的class值为:form-control date-picker-theme-dark。
咱们知道,父组件使用 prop 传递数据给子组件。但子组件怎么跟父组件通讯呢?这个时候 Vue 的自定义事件系统就派得上用场了。
每一个 Vue 实例都实现了事件接口,即:
Vue 的事件系统与浏览器的
EventTarget API
有所不一样。尽管它们的运行起来相似,可是$on
和$emit
并非addEventListener
和dispatchEvent
的别名。
另外,父组件能够在使用子组件的地方直接用 v-on
来监听子组件触发的事件。
不能用 $on 侦听子组件释放的事件,而必须在模板里直接用 v-on 绑定,参见下面的例子。
<div id="counter-event-example"> <p>{{ total }}</p> <button-counter v-on:increment="incrementTotal"></button-counter> <button-counter v-on:increment="incrementTotal"></button-counter> </div>
Vue.component('button-counter', {
template: '<button v-on:click="incrementCounter">{{ counter }}</button>', data: function () { return { counter: 0 } }, methods: { incrementCounter: function () { this.counter += 1 this.$emit('increment') } }, }) new Vue({ el: '#counter-event-example', data: { total: 0 }, methods: { incrementTotal: function () { this.total += 1 } } })
有时候,你可能想在某个组件的根元素上监听一个原生事件。可使用 v-on 的修饰符 .native
。例如:
<my-component v-on:click.native="doTheThing"></my-component>
<comp :foo.sync="bar"></comp>
会被扩展为:
<comp :foo="bar" @update:foo="val => bar = val"></comp>
当子组件须要更新 foo 的值时,它须要显式地触发一个更新事件:
this.$emit('update:foo', newValue)
自定义事件能够用来建立自定义的表单输入组件,使用 v-model 来进行数据双向绑定。要牢记:
<input v-model="something">
这不过是如下示例的语法糖:
<input
v-bind:value="something" v-on:input="something = $event.target.value">
因此在组件中使用时,它至关于下面的简写:
<custom-input
v-bind:value="something" v-on:input="something = arguments[0]"> </custom-input>
因此要让组件的 v-model 生效,它应该 (从 2.2.0 起是可配置的):
value prop
input
事件并将新值做为参数例子1:
<div id="app">
<custom-input v-model="something"></custom-input> <br/> {{something}} </div>
// 注册 Vue.component('custom-input', { props:['something'], template: '<input type="text" v-bind:value="something" v-on:input="updateValue($event.target.value)"/>', methods:{ updateValue:function(value){ this.$emit('input', value) } } }) var vm = new Vue({ el: '#app', data: { something:'' } })
例子2:货币输入的自定义控件
<currency-input v-model="price"></currency-input>
Vue.component('currency-input', {
template: '\ <span>\ $\ <input\ ref="input"\ v-bind:value="value"\ v-on:input="updateValue($event.target.value)"\ >\ </span>\ ', props: ['value'], methods: { // 不是直接更新值,而是使用此方法来对输入值进行格式化和位数限制 updateValue: function (value) { var formattedValue = value // 删除两侧的空格符 .trim() // 保留 2 位小数 .slice( 0, value.indexOf('.') === -1 ? value.length : value.indexOf('.') + 3 ) // 若是值尚不合规,则手动覆盖为合规的值 if (formattedValue !== value) { this.$refs.input.value = formattedValue } // 经过 input 事件带出数值 this.$emit('input', Number(formattedValue)) } } })
实例3:更加完善的货币过滤器
<div id="app"> <currency-input label="Price" v-model="price" ></currency-input> <currency-input label="Shipping" v-model="shipping" ></currency-input> <currency-input label="Handling" v-model="handling" ></currency-input> <currency-input label="Discount" v-model="discount" ></currency-input> <p>Total: ${{ total }}</p> </div>
Vue.component('currency-input', {
template: '\ <div>\ <label v-if="label">{{ label }}</label>\ $\ <input\ ref="input"\ v-bind:value="value"\ v-on:input="updateValue($event.target.value)"\ v-on:focus="selectAll"\ v-on:blur="formatValue"\ >\ </div>\ ', props: { value: { type: Number, default: 0 }, label: { type: String, default: '' } }, mounted: function () { this.formatValue() }, methods: { updateValue: function (value) { var result = currencyValidator.parse(value, this.value) if (result.warning) { this.$refs.input.value = result.value } this.$emit('input', result.value) }, formatValue: function () { this.$refs.input.value = currencyValidator.format(this.value) }, selectAll: function (event) { // Workaround for Safari bug // http://stackoverflow.com/questions/1269722/selecting-text-on-focus-using-jquery-not-working-in-safari-and-chrome setTimeout(function () { event.target.select() }, 0) } } }) new Vue({ el: '#app', data: { price: 0, shipping: 0, handling: 0, discount: 0 }, computed: { total: function () { return (( this.price * 100 + this.shipping * 100 + this.handling * 100 - this.discount * 100 ) / 100).toFixed(2) } } })
默认状况下,一个组件的 v-model 会使用 value prop 和 input 事件。可是诸如单选框、复选框之类的输入类型可能把 value 用做了别的目的。model 选项能够避免这样的冲突:
Vue.component('my-checkbox', {
model: {
prop: 'checked', event: 'change' }, props: { checked: Boolean, // 这样就容许拿 `value` 这个 prop 作其它事了 value: String }, // ... })
<my-checkbox v-model="foo" value="some value"></my-checkbox>
上述代码等价于:
<my-checkbox
:checked="foo" @change="val => { foo = val }" value="some value"> </my-checkbox>
注意你仍然须要显式声明
checked
这个prop
。
完整的代码:
html:
<div id="app">
<my-checkbox v-model="foo" value="some value"></my-checkbox> {{foo}} </div>
JS代码:
Vue.component('my-checkbox', {
model: { prop: 'checked', event: 'change' }, props: { checked: Boolean, // 这样就容许拿 `value` 这个 prop 作其它事了 value: String }, template:'<input type="checkbox" @change="changefun(ischecked)"/>', data:function(){ return { ischecked:this.checked } }, methods:{ changefun(state){ this.ischecked = !state; this.$emit('change', this.ischecked); } } }) var vm = new Vue({ el: '#app', data: { foo:false } })
有时候,非父子关系的两个组件之间也须要通讯。在简单的场景下,可使用一个空的 Vue 实例做为事件总线:
var bus = new Vue()
// 触发组件 A 中的事件 bus.$emit('id-selected', 1)
// 在组件 B 建立的钩子中监听事件 bus.$on('id-selected', function (id) { // ... })
在复杂的状况下,咱们应该考虑使用专门的状态管理模式Vuex
。
来看一个完整的例子:
html代码:
<div id="app"> <comp-a v-on:id-selected="getdate"></comp-a> <comp-b></comp-b> </div>
JS代码:
var bus = new Vue(); Vue.component('comp-a', { template:'<button class="compa" @click="comfuna">组件A</button>', data:function(){ return { } }, methods:{ comfuna(){ bus.$emit('id-selected', 1); this.$emit('id-selected', 1); } } }) Vue.component('comp-b', { template:'<div class="compb">组件B</div>', data:function(){ return { } }, mounted(){ // 在组件 B 建立的钩子中监听事件 bus.$on('id-selected', function (id) { console.log('在B组件中获得的值:'+id); }) } }) var vm = new Vue({ el: '#app', data: {}, methods:{ getdate(value){ console.log('获得当前的值:'+value); } } })
在使用组件时,咱们经常要像这样组合它们:
<app> <app-header></app-header> <app-footer></app-footer> </app>
注意两点:
<app>
组件不知道它会收到什么内容。这是由使用 <app>
的父组件决定的。<app>
组件极可能有它本身的模板。为了让组件能够组合,咱们须要一种方式来混合父组件的内容与子组件本身的模板。使用特殊的 <slot>
元素做为原始内容的插槽。
一个常见错误是试图在父组件模板内将一个指令绑定到子组件的属性/方法:
<!-- 无效 --> <child-component v-show="someChildProperty"></child-component>
正确作法:
Vue.component('child-component', {
// 有效,由于是在正确的做用域内 template: '<div v-show="someChildProperty">Child</div>', data: function () { return { someChildProperty: true } } })
假定 my-component 组件有以下模板:
<div> <h2>我是子组件的标题</h2> <slot> 只有在没有要分发的内容时才会显示。 </slot> </div>
父组件模板:
<div> <h1>我是父组件的标题</h1> <my-component> <p>这是一些初始内容</p> <p>这是更多的初始内容</p> </my-component> </div>
渲染结果:
<div> <h1>我是父组件的标题</h1> <div> <h2>我是子组件的标题</h2> <p>这是一些初始内容</p> <p>这是更多的初始内容</p> </div> </div>
<slot>
元素能够用一个特殊的特性 name
来进一步配置如何分发内容。多个插槽能够有不一样的名字。具名插槽将匹配内容片断中有对应 slot
特性的元素。
仍然能够有一个匿名插槽,它是默认插槽,做为找不到匹配的内容片断的备用插槽。若是没有默认插槽,这些找不到匹配的内容片断将被抛弃。
例如,假定咱们有一个 app-layout 组件,它的模板为:
<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>
父组件模板:
<app-layout> <h1 slot="header">这里多是一个页面标题</h1> <p>主要内容的一个段落。</p> <p>另外一个主要段落。</p> <p slot="footer">这里有一些联系信息</p> </app-layout>
渲染结果为:
<div class="container"> <header> <h1>这里多是一个页面标题</h1> </header> <main> <p>主要内容的一个段落。</p> <p>另外一个主要段落。</p> </main> <footer> <p>这里有一些联系信息</p> </footer> </div>
做用域插槽是一种特殊类型的插槽,用做一个 (能被传递数据的) 可重用模板,来代替已经渲染好的元素。
在子组件中,只需将数据传递到插槽,就像你将 prop 传递给组件同样:
<div class="child"> <slot text="hello from child"></slot> </div>
在父级中,具备特殊特性 slot-scope
的 <template>
元素必须存在,表示它是做用域插槽的模板。slot-scope
的值将被用做一个临时变量名,此变量接收从子组件传递过来的 prop
对象:
<div class="parent"> <child> <template slot-scope="props"> <span>hello from parent</span> <span>{{ props.text }}</span> </template> </child> </div>
若是咱们渲染上述模板,获得的输出会是:
<div class="parent"> <div class="child"> <span>hello from parent</span> <span>hello from child</span> </div> </div>
在
2.5.0+
,slot-scope
能被用在任意元素或组件中而再也不局限于 <template>
做用域插槽更典型的用例是在列表组件中,容许使用者自定义如何渲染列表的每一项:
<my-awesome-list :items="items"> <!-- 做用域插槽也能够是具名的 --> <li slot="item" slot-scope="props" class="my-fancy-item"> {{ props.text }} </li> </my-awesome-list>
列表组件的模板:
<ul> <slot name="item" v-for="item in items" :text="item.text"> <!-- 这里写入备用内容 --> </slot> </ul>
解构
slot-scope
的值其实是一个能够出如今函数签名参数位置的合法的 JavaScript 表达式。这意味着在受支持的环境 (单文件组件或现代浏览器) 中,您还能够在表达式中使用 ES2015 解构:
<child> <span slot-scope="{ text }">{{ text }}</span> </child>
经过使用保留的 <component>
元素,并对其 is
特性进行动态绑定,你能够在同一个挂载点动态切换多个组件:
var vm = new Vue({ el: '#example', data: { currentView: 'home' }, components: { home: { /* ... */ }, posts: { /* ... */ }, archive: { /* ... */ } } })
<component v-bind:is="currentView">
<!-- 组件在 vm.currentview 变化时改变! --> </component>
也能够直接绑定到组件对象上:
var Home = {
template: '<p>Welcome home!</p>' } var vm = new Vue({ el: '#example', data: { currentView: Home } })
若是把切换出去的组件保留在内存中,能够保留它的状态或避免从新渲染。为此能够添加一个 keep-alive
指令参数:
<keep-alive> <component :is="currentView"> <!-- 非活动组件将被缓存! --> </component> </keep-alive>
Vue 组件的 API 来自三部分——prop、事件和插槽:
Prop
容许外部环境传递数据给组件;事件
容许从组件内触发外部环境的反作用;插槽
容许外部环境将额外的内容组合在组件中。使用 v-bind 和 v-on 的简写语法,模板的意图会更清楚且简洁:
<my-component
:foo="baz" :bar="qux" @event-a="doThis" @event-b="doThat" > <img slot="icon" src="..."> <p slot="main-text">Hello!</p> </my-component>
尽管有 prop 和事件,可是有时仍然须要在 JavaScript 中直接访问子组件。为此可使用 ref
为子组件指定一个引用 ID。例如:
<div id="parent">
<user-profile ref="profile"></user-profile> </div>
var parent = new Vue({ el: '#parent' }) // 访问子组件实例 var child = parent.$refs.profile
当 ref
和 v-for
一块儿使用时,获取到的引用会是一个数组,包含和循环数据源对应的子组件。
$refs
只在组件渲染完成后才填充,而且它是非响应式的。它仅仅是一个直接操做子组件的应急方案——应当避免在模板或计算属性中使用$refs
。
在大型应用中,咱们可能须要将应用拆分为多个小模块,按需从服务器下载。为了进一步简化,Vue.js 容许将组件定义为一个工厂函数,异步地解析组件的定义。Vue.js 只在组件须要渲染时触发工厂函数,而且把结果缓存起来,用于后面的再次渲染。例如:
Vue.component('async-example', function (resolve, reject) { setTimeout(function () { // 将组件定义传入 resolve 回调函数 resolve({ template: '<div>I am async!</div>' }) }, 1000) })
工厂函数接收一个 resolve
回调,在收到从服务器下载的组件定义时调用。也能够调用 reject(reason)
指示加载失败。这里使用 setTimeout 只是为了演示,实际上如何获取组件彻底由你决定。
推荐配合 webpack 的代码分割功能 来使用:
Vue.component('async-webpack-example', function (resolve) { // 这个特殊的 require 语法告诉 webpack // 自动将编译后的代码分割成不一样的块, // 这些块将经过 Ajax 请求自动下载。 require(['./my-async-component'], resolve) })
你能够在工厂函数中返回一个 Promise,因此当使用 webpack 2 + ES2015 的语法时能够这样:
Vue.component(
'async-webpack-example',
// 该 `import` 函数返回一个 `Promise` 对象。 () => import('./my-async-component') )
当使用局部注册时,也能够直接提供一个返回 Promise 的函数:
new Vue({
// ... components: { 'my-component': () => import('./my-async-component') } })
自 2.3.0 起,异步组件的工厂函数也能够返回一个以下的对象:
const AsyncComp = () => ({ // 须要加载的组件。应当是一个 Promise component: import('./MyComp.vue'), // 加载中应当渲染的组件 loading: LoadingComp, // 出错时渲染的组件 error: ErrorComp, // 渲染加载中组件前的等待时间。默认:200ms。 delay: 200, // 最长等待时间。超出此时间则渲染错误组件。默认:Infinity timeout: 3000 })
注意,当一个异步组件被做为
vue-router
的路由组件使用时,这些高级选项都是无效的,由于在路由切换前就会提早加载所须要的异步组件。另外,若是你要在路由组件中使用上述写法,须要使用vue-router 2.4.0 以上
的版本。
当注册组件 (或者 prop) 时,可使用 kebab-case (短横线分隔命名)、camelCase (驼峰式命名) 或 PascalCase (单词首字母大写命名)。
// 在组件定义中 components: { // 使用 kebab-case 注册 'kebab-cased-component': { /* ... */ }, // 使用 camelCase 注册 'camelCasedComponent': { /* ... */ }, // 使用 PascalCase 注册 'PascalCasedComponent': { /* ... */ } }
在 HTML 模板中,请使用 kebab-case:
<!-- 在 HTML 模板中始终使用 kebab-case --> <kebab-cased-component></kebab-cased-component> <camel-cased-component></camel-cased-component> <pascal-cased-component></pascal-cased-component>
当使用字符串模式时,能够不受 HTML 大小写不敏感的限制。这意味实际上在模板中,你可使用下面的方式来引用你的组件:
components: {
'kebab-cased-component': { /* ... */ }, camelCasedComponent: { /* ... */ }, PascalCasedComponent: { /* ... */ } }
<kebab-cased-component></kebab-cased-component> <camel-cased-component></camel-cased-component> <camelCasedComponent></camelCasedComponent> <pascal-cased-component></pascal-cased-component> <pascalCasedComponent></pascalCasedComponent> <PascalCasedComponent></PascalCasedComponent>
这意味着 PascalCase 是最通用的声明约定而 kebab-case 是最通用的使用约定。
组件在它的模板内能够递归地调用本身。不过,只有当它有 name
选项时才能够这么作:
name: 'unique-name-of-my-component'
当你利用 Vue.component
全局注册了一个组件,全局的 ID
会被自动设置为组件的 name
。
Vue.component('unique-name-of-my-component', {
// ... })
若是稍有不慎,递归组件可能致使死循环:
name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'
上面组件会致使一个“max stack size exceeded”错误,因此要确保递归调用有终止条件 (好比递归调用时使用 v-if
并最终解析为 false
)。
假设你正在构建一个文件目录树,像在 Finder 或资源管理器中。你可能有一个 tree-folder
组件:
<p>
<span>{{ folder.name }}</span> <tree-folder-contents :children="folder.children"/> </p>
以及一个 tree-folder-contents 组件:
<ul>
<li v-for="child in children"> <tree-folder v-if="child.children" :folder="child"/> <span v-else>{{ child.name }}</span> </li> </ul>
当你仔细看时,会发如今渲染树上这两个组件同时为对方的父节点和子节点——这是矛盾的!当使用 Vue.component 将这两个组件注册为全局组件的时候,框架会自动为你解决这个矛盾。
然而,若是你使用诸如 webpack 或者 Browserify 之类的模块化管理工具来 require/import 组件的话,就会报错了:
Failed to mount component: template or render function not defined.
在咱们的例子中,能够选择让 tree-folder 组件中来作这件事。咱们知道引发矛盾的子组件是 tree-folder-contents,因此咱们要等到 beforeCreate 生命周期钩子中才去注册它:
beforeCreate: function () { this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue') }
另外一种定义模板的方式是在 JavaScript 标签里使用 text/x-template
类型,而且指定一个 id。例如:
<script type="text/x-template" id="hello-world-template"> <p>Hello hello hello</p> </script> Vue.component('hello-world', { template: '#hello-world-template' })
这在有不少大模板的演示应用或者特别小的应用中可能有用,其它场合应该避免使用,由于这将模板和组件的其它定义分离了。
v-once
尽管在 Vue 中渲染 HTML 很快,不过当组件中包含大量静态内容时,能够考虑使用 v-once 将渲染结果缓存起来,就像这样:
Vue.component('terms-of-service', {
template: '\
<div v-once>\ <h1>Terms of Service</h1>\ ...不少静态内容...\ </div>\ ' })