组件是可复用的 Vue 实例,且带有一个名字。因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
与自定义元素的关系
你可能已经注意到 Vue 组件非常类似于自定义元素——它是Web 组件规范的一部分,这是因为 Vue 的组件语法部分参考了该规范。例如 Vue 组件实现了Slot API与isattribute。但是,还是有几个关键差别:
- Web Components 规范已经完成并通过,但未被所有浏览器原生实现。目前 Safari 10.1+、Chrome 54+ 和 Firefox 63+ 原生支持 Web Components。相比之下,Vue 组件不需要任何 polyfill,并且在所有支持的浏览器 (IE9 及更高版本) 之下表现一致。必要时,Vue 组件也可以包装于原生自定义元素之内。
- Vue 组件提供了纯自定义元素所不具备的一些重要功能,最突出的是跨组件数据流、自定义事件通信以及构建工具集成。
虽然 Vue 内部没有使用自定义元素,不过在应用使用自定义元素、或以自定义元素形式发布时,依然有很好的互操作性。Vue CLI 也支持将 Vue 组件构建成为原生的自定义元素。
组件的复用
你可以将组件进行任意次数的复用。你每用一次组件,就会有一个它的 新实例 被创建,每个组件都会各自独立维护它的 data。
data 必须是一个函数
当我们定义组件时,你可能会发现它的 data 并不是像这样直接提供一个对象:
data: {
count: 0
}
取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function () {
return {
count: 0
}
}
组件的组织
组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。通常一个应用会以一棵嵌套的组件树的形式来组织:
在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。
组件注册
To use these components in templates, they must be registered so that Vue knows about them. There are two types of component registration:globalandlocal. So far, we’ve only registered components globally, usingVue.component.
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。至此,我们的组件都只是通过 Vue.component 全局注册的。
组件名
在注册一个组件的时候,我们始终需要给它一个名字。比如在全局注册的时候我们已经看到了:
Vue.component('my-component-name', { /* ... */ })
该组件名就是 Vue.component 的第一个参数。
当直接在 DOM 中使用一个组件 (而不是在字符串模板或单文件组件) 的时候,我们强烈推荐遵循W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及未来的 HTML 元素相冲突。
组件名大小写
使用 kebab-case
Vue.component('my-component-name', { /* ... */ })
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如
。 使用 PascalCase
Vue.component('MyComponentName', { /* ... */ })
当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说
和 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。 全局注册
到目前为止,我们只用过 Vue.component 来创建组件:
Vue.component('my-component-name', {
// ... 选项 ...
})
全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。 ```jsx Vue.component(‘component-a’, { / … / }) Vue.component(‘component-b’, { / … / }) Vue.component(‘component-c’, { / … / })
new Vue({ el: ‘#app’ })
在所有子组件中也是如此,也就是说这三个组件 _在各自内部 _也都可以相互使用。
<a name="TlA4M"></a>
## 局部注册
全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
<br />
<br />在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:
```javascript
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
然后在components选项中定义你想要使用的组件:
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
对于components对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象。
注意局部注册的组件在其子组件中不可用。例如,如果你希望ComponentA在ComponentB中可用,则你需要这样写:
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
模块系统
或者如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:
import ComponentA from './ComponentA.vue'
export default {
components: {
ComponentA
},
// ...
}
基础组件的自动化全局注册
可能你的许多组件只是包裹了一个输入框或按钮之类的元素,是相对通用的。我们有时候会把它们称为基础组件,它们会在各个组件中被频繁的用到。
如果你恰好使用了 webpack (或在内部使用了 webpack 的Vue CLI 3+),那么就可以使用require.context只全局注册这些非常通用的基础组件。这里有一份可以让你在应用入口文件 (比如src/main.js) 中全局导入基础组件的示例代码:
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
)
})
记住全局注册的行为必须在根 Vue 实例 (通过new Vue) 创建之前发生。
通过 Prop 向子组件传递数据
Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。
我们可以使用v-bind来动态传递 prop。
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>
监听子组件事件
父级组件可以像处理 native DOM 事件一样通过v-on监听子组件实例的任意事件:
<blog-post
...
v-on:enlarge-text="postFontSize += 0.1"
></blog-post>
同时子组件可以通过调用内建的$emit方法并传入事件名称来触发一个事件:
<button v-on:click="$emit('enlarge-text')"> Enlarge text </button>
使用事件抛出一个值
有的时候用一个事件来抛出一个特定的值是非常有用的。这时可以使用$emit的第二个参数来提供这个值:
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
然后当在父级组件监听这个事件的时候,我们可以通过$event访问到被抛出的这个值:
<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>
或者,如果这个事件处理函数是一个方法:
<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
Content Distribution with Slots(通过插槽分发内容
)
在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即v-slot指令)。它取代了slot和slot-scope这两个目前已被废弃但未被移除且仍在文档中的 attribute。新语法的由来可查阅这份RFC。
Slot Content
Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自[Web Components 规范草案](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md),将<slot>元素作为承载分发内容的出口。
<navigation-link url="/profile">
Your Profile
</navigation-link>
<a v-bind:href="url" class="nav-link">
<slot></slot>
</a>
Compilation Scope
(编译作用域) 父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
Fallback Content(后备内容
) 有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,它只会在没有提供内容的时候被渲染。
<button type="submit">
<slot></slot>
</button>
<button type="submit">
<slot>Submit</slot>
</button>
<submit-button></submit-button>
<!--渲染结果-->
<button type="submit">Submit</button>
但是如果我们提供内容,则这个提供的内容将会被渲染从而取代后备内容。
Named Slots(具名插槽 )
Updated in 2.6.0+.See herefor the deprecated syntax using theslotattribute.
For these cases, the
对于这样的情况,
<-------<base-layout> 组件:--->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
一个不带name的 现在元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有v-slot的中的内容都会被视为默认插槽的内容。 注意v-slot只能添加在上(只有一种例外情况),这一点和已经废弃的slotattribute不同。
自 2.6.0 起有所更新。已废弃的使用slot-scopeattribute 的语法在这里。 可以通过 Vue 的 在上述示例中,currentTabComponent可以包括 我们更希望那些标签的组件实例能够被在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个 在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。例如: 如你所见,这个工厂函数会收到一个resolve回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用reject(reason)来表示加载失败。这里的setTimeout是为了演示用的,如何获取组件取决于你自己。一个推荐的做法是将异步组件和webpack 的 code-splitting 功能一起配合使用: 你也可以在工厂函数中返回一个Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以这样使用动态导入: 当使用局部注册的时候,你也可以直接提供一个返回Promise的函数: 这里的异步组件工厂函数也可以返回一个如下格式的对象: 注意如果你希望在Vue Router的路由组件中使用上述语法的话,你必须使用 Vue Router 2.4.0+ 版本。 有些 HTML 元素,诸如
在向具名插槽提供内容的时候,我们可以在一个元素上使用v-slot指令,并以v-slot的参数的形式提供其名称:
<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>
然而,如果你希望更明确一些,仍然可以在一个 中包裹默认插槽的内容。
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
Scoped Slots(作用域插槽)
动态组件
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
在动态组件上使用 keep-alive
我们之前曾经在一个多标签的界面中使用 is attribute 来切换不同的组件:
当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题。<component v-bind:is="currentTabComponent"></component>
<!-- 失活的组件将会被缓存!-->
<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)
})
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
Vue.component(
'async-webpack-example',
// 这个动态导入会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
处理加载状态(2.3.0+ 新增)
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
解析 DOM 模板时的注意事项
、
、和和