title: VUE官方文档学习—深入了解组件
date: 2021-6-4 22:59:27
author: Xilong88
tags: Vue
组件注册
全局注册:
Vue.component('my-component-name', { /* ... */ })
组件名称建议用“-”链接起来的,而且命名要和功能挂钩。
组件名大小写
可以使用“-”或者大驼峰模式命名,但是在html中只能用连线。
全局注册
通过Vue.component创建组件就是全局创建,全局创建的组件可以在所有子组件中使用,可以在组件内部使用。
局部注册
可以通过实例的components选项定义你要使用的组件:
先用js对象定义组件:
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
property的属性名就是组件的名称。
局部注册的组件在其子组件中不可用
假如想子组件可以用某个子组件就要在该子组件中使用:
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
这个js对象可以通过ES2015模块那样引入:
import ComponentA from './ComponentA.vue'
export default {
components: {
ComponentA //这里是同名简写
},
// ...
}
模块系统
在模块系统中局部注册
组件可以放在一个components目录,所有的组件可以引入到一个文件中一起导出,可以单独导出:
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
ComponentA,
ComponentC
},
// ...
}
基础组件的自动化全局注册
有些组件很多页面会用,就是基础组件,那么频繁地手动引用会导致代码冗余
这时可以使用webpack或者Vue CLI 3+ 中使用require.context
只全局注册这些非常通用的基础组件。
这样可以在入口文件里面全局导入基础组件:
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
.split('/')
.pop()
.replace(/\.\w+$/, '')
)
)
// 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})
看不懂没关系,学webpack的时候应该还会再看。
Prop
Prop 的大小写 (camelCase vs kebab-case)
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。
使用 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>
因为在template里面出现“-”会被当成减号。
Prop 类型
可以通过字符串数组形式列出prop:
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
也可以规定属性类型,在后面还会讲一些高级的用法,也就是验证prop:
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
传递静态或动态 Prop
静态:
<blog-post title="My journey with Vue"></blog-post>
动态:
<!-- 动态赋予一个变量的值 -->
<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>
这里传数字要用v-bind,不用就会传入字符串形式的数字
传入一个布尔值
<!-- 包含该 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>
这里传false和上面传数字同理,假如属性没有值,那么就是true
传入一个数组
<!-- 即便数组是静态的,我们仍然需要 `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>
传对象也一样,因为要解析,而不是简单的字符串。
传入一个对象的所有 property
假如想把一个对象的所有属性传入到模板,就可以直接不接对象某一属性,而是直接v-bind:对象名称:
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就会警告
两种常见的试图变更一个 prop 的情形:
1.这样访问了prop,但是不要操作prop
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
组件上传入这个prop后,组件中的data把它当做初始值保存为一个内部的数据。
2.prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
这种情况是需要对prop进行一些计算的情况,最好用计算属性,但是不要改变原数据。
假如传的是引用类型,就要更加谨慎了,因为一不小心就会改变引用的属性。
Prop 验证
我们可以通过一些方法来验证prop:
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
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
}
}
}
})
在开发环境中,验证失败就会在控制台打印错误。
这里要在创建实例之前验证,也就是实例创建后才有的东西是不能用的。
类型检查
type 可以是下列原生构造函数中的一个:
String
Number
Boolean
Array
Object
Date
Function
Symbol
也可以是自定义的构造函数,内部会通过instanceof来检查。
比如:
function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
可以这样:
Vue.component('blog-post', {
props: {
author: Person
}
})
这样可以验证prop author是不是由new Person创建的。
非 Prop 的 Attribute
假如一个Attribute在prop里面没有,就会自动添加到组件的根元素上去,变成根元素的属性。
替换/合并已有的 Attribute
一般情况下,假如有重复的attribute会被父组件的attribute替换掉,但是class和style是比较智能的,可以自动合并attribute值。
禁用 Attribute 继承
如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false
Vue.component('my-component', {
inheritAttrs: false,
// ...
})
这样根元素就不会继承父组件的attribute了:
这里根元素是label,上面是设置inheritAttrs: true的情况
下面是false
这时根元素没有继承attributes
$attrs是一个对象,里面是父组件里面的attribute而不存在于根元素里面的内容:
{
required: true,
placeholder: 'Enter your username'
}
和inheritAttrs: false一起用,手动来绑定哪些这些属性:
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元素,因为不用担心谁是根元素,属性是我们手动绑定的,可以不用管谁才是真正的根元素。
自定义事件
事件名
前面提到过,事件名称会自动转化为小写,所以用驼峰不行,我们应该全部用短横来命名。
<!-- 没有效果 -->
this.$emit('myEvent')
<!-- 有效果 -->
this.$emit('my-event')
<my-component v-on:my-event="doSomething"></my-component>
自定义组件的 v-model
前面提到过,v-model实际上就是语法糖,这里假如我们不想让v-model默认利用value和input事件,我们可以改选项model:
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
现在在这个组件上使用 v-model 的时候:
<base-checkbox v-model="lovingVue"></base-checkbox>
现在就会把lovingVue的值传给checked,并且调用change事件了,这是双向绑定的。
但是我们还是得在props中声明checked这个选项,不然不能正常访问。
将原生事件绑定到组件
假如我们要在组件上绑定一个原生DOM事件,那么我们需要用 .native 修饰符
<base-input v-on:focus.native="onFocus"></base-input>
这在大多数情况有用,但是对于一些特殊事件,比如input标签的事件,在组件内重构了:
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
那么实际上根元素是一个label标签,那么就不存在focus事件,那么.native会静默失败,onFocus不会被调用,这里就要用:$listeners属性,这是一个对象,包含了组件上所有的监听器。
{
focus: function (event) { /* ... */ }
input: function (value) { /* ... */ },
}
有了这个属性,我们就可以把组件上的事件,绑定到我们想绑定的元素上:
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,
// 或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})
这样所有组件的事件都绑定在了模板里面的input上,事件触发后就会在input标签上处理,并且在计算属性内我们还可以重写触发后的回调函数。这时就不需要 .native 了
.sync 修饰符
有时候需要对prop进行双向绑定,假如不明确属性在哪改变的,那么维护就会很困难,所以可以用以下方式:
this.$emit('update:title', newTitle)
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
这样明确更新title这个事件,就可以知道哪里变更的这个属性了,然后这种模式的简写:
<text-document v-bind:title.sync="doc.title"></text-document>
假如多个prop都想这么做,还可以简化:
<text-document v-bind.sync="doc"></text-document>
这样相当于对doc的每个属性进行了以上操作。
也不能传入对象的字面量,因为要去解析对象,这里不支持。
插槽
v-slot 取代了slot和slot-scope,但是在Vue2+里面后两者还是能用,Vue3就不支持了
插槽内容
<slot>
就是用于把组件内部的一些别的节点包含进去:
<navigation-link url="/profile">
Your Profile
</navigation-link>
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
这里有slot所以Your Profile可以替换slot从而渲染出来,假如没有slot,Your Profile就不会渲染。
同样可以传入任何模板代码包括HTML:
<navigation-link url="/profile">
<!-- 添加一个 Font Awesome 图标 -->
<span class="fa fa-user"></span>
Your Profile
</navigation-link>
包括其他组件:
<navigation-link url="/profile">
<!-- 添加一个图标的组件 -->
<font-awesome-icon name="user"></font-awesome-icon>
Your Profile
</navigation-link>
假如没有slot,标签包含的内容都会被抛弃。
编译作用域
插槽内部的组件或节点是不能访问父级模板的属性的,比如:
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
<!--
这里的 `url` 会是 undefined,因为其 (指该插槽的) 内容是
_传递给_ <navigation-link> 的而不是
在 <navigation-link> 组件*内部*定义的。
-->
</navigation-link>
这里url就是访问不到的。
后备内容
插槽内部可以定义默认内容,假如没有内容传进来就显示默认内容:
<button type="submit">
<slot>Submit</slot>
</button>
<submit-button></submit-button>
渲染为:
<button type="submit">
Submit
</button>
假如提供内容:
<submit-button>
Save
</submit-button>
渲染为:
<button type="submit">
Save
</button>
具名插槽
假如我们需要把特定内容放入特定插槽,就需要具名插槽:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
假如插槽没有name,那就是一个"default"为name的插槽
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
假如想更明确,可以写default:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
渲染结果:
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
v-slot只能在template标签上使用,除了独占默认插槽的情况。
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
作用域插槽
<current-user>
组件:
<span>
<slot>{{ user.lastName }}</slot>
</span>
使用时:
<current-user>
{{ user.firstName }}
</current-user>
这里不会成功,因为父级才能访问到user,而slot访问不到。
为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 元素的一个 attribute 绑定上去:
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
绑定在 <slot>
元素上的 attribute 被称为插槽 prop。
现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
slotProps是起的别名,随便什么都可以,我们通过别名来访问slot定义时绑定的props,这个别名是所有prop的集合。
独占默认插槽的缩写语法
假如提供的内容只有默认插槽,那么组件标签可以当成template使用:
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
这种写法还可以更简单。就像假定未指明的内容对应默认插槽一样,不带参数的 v-slot 被假定对应默认插槽:
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
</current-user>
这种模式不能和具名插槽混用:
<!-- 无效,会导致警告 -->
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
<template v-slot:other="otherSlotProps">
slotProps is NOT available here
</template>
</current-user>
只要出现多个插槽,请始终为所有的插槽使用完整的基于 的语法:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
解构插槽 Prop
作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里:
function (slotProps) {
// 插槽内容
}
所以可以传入任何能作为 函数中定义的参数 的js表达式,在环境支持的情况下,可以用解构来简化:
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
这里传入一个对象,被解构为含user的对象,这样直接用user去访问属性。
这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候。
它同样开启了 prop 重命名等其它可能,例如将 user 重命名为 person:
<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>
下面官方文档好像写错了,总之用不了:
动态插槽名
2.6.0 新增:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
dynamicSlotName计算出一个名字来作为插槽的name
具名插槽的缩写
"v-slot:"缩写为:#,如v-slot:header 可以被重写为 #header
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
有参数才能这样用:
<!-- 这样会触发一个警告 -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>
如果希望使用缩写的话,你必须始终以明确插槽名取而代之:
<current-user #default="{ user }">
{{ user.firstName }}
</current-user>
其它示例
插槽 prop 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。
<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"
>
<!--
我们为每个 todo 准备了一个插槽,
将 `todo` 对象作为一个插槽的 prop 传入。
-->
<slot name="todo" v-bind:todo="todo">
<!-- 后备内容 -->
{{ todo.text }}
</slot>
</li>
</ul>
<todo-list v-bind:todos="todos">
<template v-slot:todo="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>
也就是基于插槽,我们可以在具体使用模板时去改变一些东西,这样就能更灵活。
这只是作用域插槽用武之地的冰山一角。想了解更多现实生活中的作用域插槽的用法,我们推荐浏览诸如
Vue VirtualScroller、 Vue Promised 、 Portal Vue 等库。
废弃了的语法
这里废弃了slot和slot-scope 可以了解一下
动态组件 & 异步组件
在动态组件上使用 keep-alive
通过is来切换不同组件时:
<component v-bind:is="currentTabComponent"></component>
组件会重复渲染,这样会浪费资源,这时可以用到keep-alive
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
这样每次创建的组件实例会被缓存起来,然后下次切回来就不会重新创建了。
可以参考文档查看更多详情
异步组件
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
这样会异步注册组件,工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用 reject(reason) 来表示加载失败。这里的 setTimeout 是为了演示用的,如何获取组件取决于你自己。
配合webpack的code-splitting功能一起:
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
你也可以在工厂函数中返回一个 Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以这样使用动态导入:
Vue.component(
'async-webpack-example',
// 这个动态导入会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
当使用局部注册的时候,你也可以直接提供一个返回 Promise 的函数:
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
这里不怎么看得懂,后面学webpack应该会慢慢看得懂一点。
处理加载状态
2.3.0+ 新增
这里就是说异步工厂函数返回如下的对象,来处理异步组件的一些相当于生命周期
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
处理边界情况
访问元素 & 组件
$root可以访问到根实例
// Vue 根实例
new Vue({
data: {
foo: 1
},
computed: {
bar: function () { /* ... */ }
},
methods: {
baz: function () { /* ... */ }
}
})
所有的子组件都可以将这个实例作为一个全局 store 来访问或使用。
// 获取根组件的数据
this.$root.foo
// 写入根组件的数据
this.$root.foo = 2
// 访问根组件的计算属性
this.$root.bar
// 调用根组件的方法
this.$root.baz()
但是不推荐这样做
访问父级组件实例
$parent可以访问父组件实例
但是不推荐这样做
访问子组件实例或子元素
ref可以作为子元素的ID引用,被父元素访问:
<base-input ref="usernameInput"></base-input>
下面这样就可以访问了:
this.$refs.usernameInput
当 ref 和 v-for 一起使用的时候,你得到的 ref 将会是一个包含了对应数据源的这些子组件的数组。
同样不推荐用$refs
依赖注入
provide 和 inject
provide: function () {
return {
getMap: this.getMap
}
}
inject: ['getMap']
通过这样的方式来让子组件访问父组件的内容。
这样会增加耦合度,官方推荐用Vuex做状态管理,后面会学。
程序化的事件侦听器
通过 $on(eventName, eventHandler) 侦听一个事件
通过 $once(eventName, eventHandler) 一次性侦听一个事件
通过 $off(eventName, eventHandler) 停止侦听一个事件
注意 Vue 的事件系统不同于浏览器的 EventTarget API。尽管它们工作起来是相似的,但是 $emit、$on, 和 $off 并不是 dispatchEvent、addEventListener 和 removeEventListener 的别名。
循环引用
递归组件
name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'
类似上述的组件将会导致“max stack size exceeded”错误,所以请确保递归调用是条件性的 (例如使用一个最终会得到 false 的 v-if)。
组件之间的循环引用
局部注册的组件相互引用会报错,必须
beforeCreate: function () {
this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
}
或者,在本地注册组件的时候,你可以使用 webpack 的异步 import:
components: {
TreeFolderContents: () => import('./tree-folder-contents.vue')
}
总之其中某一个要后加载,这样就能让另一个创建一个"入口"实例
模板定义的替代品
内联模板
inline-template可以使子组件中的内容直接使用,不需要slot
<my-component inline-template>
<div>
<p>These are compiled as the component's own template.</p>
<p>Not parent's transclusion content.</p>
</div>
</my-component>
不推荐使用inline-template
X-Template
<script type="text/x-template" id="hello-world-template">
<p>Hello hello hello</p>
</script>
Vue.component('hello-world', {
template: '#hello-world-template'
})
script标签可以作为模板,要改type属性
x-template 需要定义在 Vue 所属的 DOM 元素外。
控制更新
假如发现要手动刷新实例,那么肯定是出错了,但是vue提供了刷新的方法$forceUpdate
通过 v-once 创建低开销的静态组件
根元素上添加 v-once attribute 以确保这些内容只计算一次然后缓存起来,就像这样:
Vue.component('terms-of-service', {
template: `
<div v-once>
<h1>Terms of Service</h1>
... a lot of static content ...
</div>
`
})
但是这样可能会让后面的开发者疑惑,不知道为什么数据不正常更新,所以还是慎用。