组件
# 创建一个 Vue 实例:
new Vue({
el: '#some-element',
// 选项
})
# 注册全局组件
Vue.component('my-component', {
// 选项 組件名小写,并且包含一个短杠
})
组件在注册之后,便可以作为自定义元素 ```<my-component></my-component>```
# 局部注册
var Child = {
template: '<div>A custom component!</div>'
}
new Vue({
// ...
components: {
// <my-component> 将只在父组件模板中可用
'my-component': Child
}
})
DOM 模板解析注意事项
<ul>、<ol>、<table>、<select>这样的元素里允许包含的元素有限制,而另一些像<option> 这样的元素
只能出现在某些特定元素的内部。
在自定义组件中使用这些受限制的元素时会导致一些问题,例如:
<table>
<my-row>...</my-row>
</table>
自定义组件 <my-row>
会被当作无效的内容,因此会导致错误的渲染结果。变通的方案是使用特殊的 is 特性:
<table>
<tr is="my-row"></tr>
</table>
data
必须是函数:但是在组件中,因为可能在多处调用同一组件,所以为了不让多处的组件共享同一data对象,只能
返回函数。
那么 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 都有它自己内部的状态了:
父子组件Prop
prop 向下传递,事件向上传递。父组件的数据需要通过 prop才能下发到子组件中。
子组件要显式地用 props 选项声明它预期的数据:
Vue.component('child', {
// 声明 props
props: ['message'],
// 就像 data 一样,prop 也可以在模板中使用
// 同样也可以在 vm 实例中通过 this.message 来使用
template: '<span>{{ message }}</span>'
})
然后我们可以这样向它传入一个普通字符串:
<child message="hello!"></child>
# 动态 Prop
与绑定到任何普通的 HTML 特性相类似,我们可以用 v-bind 来动态地将 prop 绑定到父组件的数据。每当
父组件的数据变化时,该变化也会传导给子组件:
<child v-bind:my-message="parentMsg"></child>
# Message from parent
如果你想把一个对象的所有属性作为 prop 进行传递,可以使用不带任何参数的 v-bind (即用 v-bind 而
不是 v-bind:prop-name)。例如,已知一个 todo 对象:
todo: {
text: 'Learn Vue',
isComplete: false
}
<todo-item
:text="todo.text"
:is-complete="todo.isComplete"
/>
# 字面量语法 vs 动态语法
初学者常犯的一个错误是使用字面量语法传递数值:
<!-- 传递了一个字符串 "1" -->
<comp some-prop="1"></comp>
因为它是一个字面量 prop,它的值是字符串 "1" 而不是一个数值。如果想传递一个真正的 JavaScript
数值,则需要使用 v-bind,从而让它的值被当作 JavaScript 表达式计算:
<!-- 传递真正的数值 -->
<comp v-bind:some-prop="1"></comp>
# 单向数据流:Prop 是单向绑定的
每次父组件更新时,子组件的所有 prop 都会更新为最新值。
在两种情况下,我们很容易忍不住想去修改 prop 中数据:
Prop 作为初始值传入后,子组件想把它当作局部数据来用;
Prop 作为原始数据传入,由子组件处理成其它数据输出。
对这两种情况,正确的应对方式是:
定义一个局部变量,并用 prop 的值初始化它:
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
定义一个计算属性,处理 prop 的值并返回:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子
组件内部改变它会影响父组件的状态。
# 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 等实例属性还无法使用。
# 非 Prop 特性:可以直接传入组件,而不需要定义相应的 prop。
组件可以接收任意传入的特性,这些特性都会被添加到组件的根元素上。
把特性直接添加到组件上 (不需要事先定义 prop):
<bs-date-input data-3d-date-picker="true"></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 值:
对于多数特性来说,传递给组件的值会覆盖组件本身设定的值。即例如传递 type="large" 将会覆盖
type="date" 且有可能破坏该组件!所幸我们对待 class 和 style 特性会更聪明一些,这两个特性的值都会做
合并 (merge) 操作,让最终生成的值为:form-control date-picker-theme-dark。
子-父通信:$emit&自定义事件系统
使用 $on(eventName) 监听事件
使用 $emit(eventName, optionalPayload) 触发事件
父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。
不能用 $on 监听子组件释放的事件,而必须在模板里直接用 v-on 绑定,参见下面的例子。
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter @increment="incrementTotal"></button-counter>
</div>
Vue.component('button-counter', {
template: '<button @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
}
}
})
在本例中,子组件已经和它外部完全解耦了。它所做的只是报告自己的内部事件,因为父组件可能会关心这些事件。
# 这里有一个如何使用载荷 (payload) 数据的示例:
<div id="message-event-example" class="demo">
<p v-for="msg in messages">{{ msg }}</p>
<button-message @message="handleMessage"></button-message>
</div>
Vue.component('button-message', {
template: `<div>
<input type="text" v-model="message" />
<button @click="handleSendMessage">Send</button>
</div>`,
data: function () {
return {
message: 'test message'
}
},
methods: {
handleSendMessage: function () {
this.$emit('message', { message: this.message })
}
}
})
new Vue({
el: '#message-event-example',
data: {
messages: []
},
methods: {
handleMessage: function (payload) {
this.messages.push(payload.message)
}
}
})
记录其自身的活动,活动记录是包括一份传入事件触发器的载荷数据在内的,只是为了展示父组件可以不关注的
一个场景。
# camelCase vs. kebab-case
HTML 特性是不区分大小写的。所以,当使用的不是字符串模板时,camelCase (驼峰式命名) 的 prop
需要转换为相对应的 kebab-case (短横线分隔式命名):
Vue.component('child', {
// 在 JavaScript 中使用 camelCase
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>'
})
<!-- 在 HTML 中使用 kebab-case -->
<child my-message="hello!"></child>
如果你使用字符串模板,则没有这些限制。
非父子组件的通信 vue实例
非父子关系的两个组件之间通信。
# 在简单的场景下,可以使用一个空的 Vue 实例作为事件总线:
var bus = new Vue()
// 触发组件 A 中的事件
bus.$emit('id-selected', 1)
// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function (id) {
// ...
})
# 在复杂的情况下,我们应该考虑使用专门的状态管理模式。
给组件绑定原生事件
在某个组件的根元素上监听一个原生事件。可以使用 v-on 的修饰符 .native。例如:
<my-component @click.native="doTheThing"></my-component>
# .sync 修饰符(2.3.0+)
对一个 prop 进行“双向绑定”.当一个子组件改变了一个带 .sync 的 prop 的值时,这个变化也会同步到
父组件中所绑定的值。这很方便,但也会导致问题,因为它破坏了单向数据流。在 2.0 发布之后的实际应用中,
.sync 还是有其适用之处,比如在开发可复用的组件库时。我们需要做的只是让子组件改变父组件状态的代码更
容易被区分。
但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器。
如下代码
<comp :foo.sync="bar"></comp>
会被扩展为:
<comp :foo="bar" @update:foo="val => bar = val"></comp>
当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件:
this.$emit('update:foo', newValue)
当使用一个对象一次性设置多个属性的时候,这个 .sync 修饰符也可以和 v-bind 一起使用:
<comp v-bind.sync="{ foo: 1, bar: 2 }"></comp>
这个例子会为 foo 和 bar 同时添加用于更新的 v-on 监听器。
# 使用自定义事件的表单输入组件
自定义事件可以用来创建自定义的表单输入组件,使用 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 事件并将新值作为参数
我们来看一个非常简单的货币输入的自定义控件:
<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))
}
}
})
当然,上面的例子还是比较初级的。比如,用户输入多个小数点或句号也是允许的,好恶心吧!因此我们需要
一个复杂一些的例子,下面是一个更加完善的货币过滤器:
# 自定义组件的 v-model(2.2.0 新增)
默认情况下,一个组件的 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。
内容分发slot
简单来说,假如父组件需要在子组件内放一些DOM,那么这些DOM是显示、不显示、在哪个地方显示、如何显示,
就是slot分发负责的活。这个过程被称为内容分发 (即 Angular 用户熟知的“transclusion”)。Vue.js
实现了一个内容分发 API,参照了当前 Web Components 规范草案,使用特殊的 <slot>元素作为原始内容
的插槽。
# 编译作用域
在深入内容分发 API 之前,我们先明确内容在哪个作用域里编译。假定模板为:
<child-component>
{{ message }}
</child-component>
message 应该绑定到父组件的数据,组件作用域简单地说是:
父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。
一个常见错误是试图在父组件模板内将一个指令绑定到子组件的属性/方法:
<!-- 无效 -->
<child-component v-show="someChildProperty"></child-component>
假定 someChildProperty 是子组件的属性,上例不会如预期那样工作。父组件模板并不感知子组件的状态。
如果要绑定子组件作用域内的指令到一个组件的根节点,你应当在子组件自己的模板里做:
Vue.component('child-component', {
// 有效,因为是在正确的作用域内
template: '<div v-show="someChildProperty">Child</div>',
data: function () {
return {
someChildProperty: true
}
}
})
类似地,被分发的内容会在父作用域内编译。
# 单个插槽
除非子组件模板包含至少一个 <slot> 插口,否则父组件的内容将会被丢弃。当子组件模板只有一个没有属性
的插槽时,父组件传入的整个内容片段将插入到插槽所在的 DOM 位置,并替换掉插槽标签本身。
最初在<slot>标签中的任何内容都被视为备用内容。备用内容在子组件的作用域内编译,并且只有在宿主元素为
空,且没有要插入的内容时才显示备用内容。
假定 my-component 组件有如下模板:
<div>
<h2>我是子组件的标题</h2>
<slot>
只有在没有要分发的内容时才会显示。
</slot>
</div>
父组件模板:
<div>
<h1>我是父组件的标题</h1>
<my-component>
<p>这是一些初始内容</p>
<p>这是更多的初始内容</p>
</my-component>
渲染结果:
<div>
<h1>我是父组件的标题</h1>
<div>
<h2>我是子组件的标题</h2>
<p>这是一些初始内容</p>
<p>这是更多的初始内容</p>
</div>
</div>
# 具名slot
将放在子组件里的不同html标签放在不同的位置
父组件在要分发的标签里添加 slot=”name名” 属性
子组件在对应分发的位置的slot标签里,添加name=”name名” 属性,
然后就会将对应的标签放在对应的位置了。
示例代码:
<div id="app">
<children>
<span slot="first">12345</span>
<span slot="second">56789</span>
<!--上面这行不会显示-->
</children>
</div>
<script>
var vm = new Vue({
el: '#app',
components: {
children: { //这个无返回值,不会继续派发
template: "<button>
<slot name='first'></slot>为了明确作用范围,
<slot name='second'></slot>所以使用button标签
</button>"
}
}
});
</script>
显示结果为:(为了方便查看,已手动调整换行)
<button>
<span slot="first">12345</span>
为了明确作用范围,
<span slot="second">56789</span>
所以使用button标签
</button>
# 作用域插槽(2.1.0 新增)
作用域插槽是一种特殊类型的插槽,用作一个 (能被传递数据的) 可重用模板,来代替已经渲染好的元素。
在子组件中,只需将数据传递到插槽,就像你将 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 表达式。这意味着在
受支持的环境 (单文件组件或现代浏览器)
<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
指令参数:
<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。例如:
// 访问子组件实例
当 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')
}
})
如果你是 Browserify 用户,可能就无法使用异步组件了,它的作者已经表明 Browserify 将“永远不会支
持异步加载”。Browserify 社区发现了一些解决方法,可能会有助于已存在的复杂应用。对于其他场景,我们
推荐使用 webpack,因为它对异步加载进行了内置、全面的支持。
高级异步组件(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 大小写不敏感的限制。这意味实际上在模板中,你可以使用下面的方式来
引用你的组件:
kebab-case
camelCase 或 kebab-case (如果组件已经被定义为 camelCase)
kebab-case、camelCase 或 PascalCase (如果组件已经被定义为 PascalCase)
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 是最通用的使用约定。
如果组件未经 slot 元素传入内容,你甚至可以在组件名后使用 / 使其自闭合:
<my-component/>
当然,这只在字符串模板中有效。因为自闭的自定义元素是无效的 HTML,浏览器原生的解析器也无法识别它。
递归组件 (待研究)
# 组件在它的模板内可以递归地调用自己。不过,只有当它有 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.
为了解释为什么会报错,简单的将上面两个组件称为 A 和 B。模块系统看到它需要 A,但是首先 A 需要 B,
但是 B 需要 A,而 A 需要 B,循环往复。因为不知道到底应该先解析哪个,所以将会陷入无限循环。要解决
这个问题,我们需要在其中一个组件中告诉模块化管理系统:“A 虽然最后会用到 B,但是不需要优先导入 B”。
在我们的例子中,可以选择让 tree-folder 组件中来做这件事。我们知道引起矛盾的子组件是
tree-folder-contents,所以我们要等到 beforeCreate 生命周期钩子中才去注册它:
beforeCreate: function () {
this.$options.components.TreeFolderContents =
require('./tree-folder-contents.vue').default
}
内联模板
# 如果子组件有 inline-template 特性,
组件将把它的内容当作它的模板,而不是把它当作分发内容。这让模板编写起来更灵活。
<my-component inline-template>
<div>
<p>这些将作为组件自身的模板。</p>
<p>而非父组件透传进来的内容。</p>
</div>
</my-component>
但是 inline-template 让模板的作用域难以理解。使用 template 选项在组件内定义模板或者在 .vue
文件中使用 template 元素才是最佳实践。
# X-Template
另一种定义模板的方式是在 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>\
'
})
$mount()
当Vue实例没有el属性时,则该实例尚没有挂载到某个dom中;
假如需要延迟挂载,可以在之后手动调用vm.$mount()方法来挂载。例如:
<div id="app">
{{a}}
</div>
<button onclick="test()">挂载</button>
<script>
var obj = {a: 1}
var vm = new Vue({
data: obj
})
function test() {
vm.$mount("#app");
}
初始,显示的是{{a}}
当点击按钮后,变成了1
原文发布时间为:2018年03月18日
原文作者:掘金
本文来源:掘金 如需转载请联系原作者