组件是可复用的 Vue 实例,且带有一个名字。因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。

与自定义元素的关系

你可能已经注意到 Vue 组件非常类似于自定义元素——它是Web 组件规范的一部分,这是因为 Vue 的组件语法部分参考了该规范。例如 Vue 组件实现了Slot API与isattribute。但是,还是有几个关键差别:

  1. Web Components 规范已经完成并通过,但未被所有浏览器原生实现。目前 Safari 10.1+、Chrome 54+ 和 Firefox 63+ 原生支持 Web Components。相比之下,Vue 组件不需要任何 polyfill,并且在所有支持的浏览器 (IE9 及更高版本) 之下表现一致。必要时,Vue 组件也可以包装于原生自定义元素之内。
  2. Vue 组件提供了纯自定义元素所不具备的一些重要功能,最突出的是跨组件数据流、自定义事件通信以及构建工具集成。

虽然 Vue 内部没有使用自定义元素,不过在应用使用自定义元素、或以自定义元素形式发布时,依然有很好的互操作性。Vue CLI 也支持将 Vue 组件构建成为原生的自定义元素。

组件的复用

你可以将组件进行任意次数的复用。你每用一次组件,就会有一个它的 新实例 被创建,每个组件都会各自独立维护它的 data。

data 必须是一个函数

当我们定义组件时,你可能会发现它的 data 并不是像这样直接提供一个对象:

  1. data: {
  2. count: 0
  3. }

取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝

  1. data: function () {
  2. return {
  3. count: 0
  4. }
  5. }

组件的组织

组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。通常一个应用会以一棵嵌套的组件树的形式来组织:
组件 - 图1
在 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 全局注册的。

组件名

在注册一个组件的时候,我们始终需要给它一个名字。比如在全局注册的时候我们已经看到了:

  1. Vue.component('my-component-name', { /* ... */ })
  1. 该组件名就是 Vue.component 的第一个参数。


当直接在 DOM 中使用一个组件 (而不是在字符串模板或单文件组件) 的时候,我们强烈推荐遵循W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及未来的 HTML 元素相冲突。

组件名大小写

  • 使用 kebab-case

    1. Vue.component('my-component-name', { /* ... */ })

    当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如

  • 使用 PascalCase

    1. Vue.component('MyComponentName', { /* ... */ })

    当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。

    全局注册

    到目前为止,我们只用过 Vue.component 来创建组件:

    1. Vue.component('my-component-name', {
    2. // ... 选项 ...
    3. })

    全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。 ```jsx Vue.component(‘component-a’, { // }) Vue.component(‘component-b’, { // }) Vue.component(‘component-c’, { // })

new Vue({ el: ‘#app’ })

  1. 在所有子组件中也是如此,也就是说这三个组件 _在各自内部 _也都可以相互使用。
  2. <a name="TlA4M"></a>
  3. ## 局部注册
  4. 全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
  5. <br />
  6. <br />在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:
  7. ```javascript
  8. var ComponentA = { /* ... */ }
  9. var ComponentB = { /* ... */ }
  10. var ComponentC = { /* ... */ }

然后在components选项中定义你想要使用的组件:

  1. new Vue({
  2. el: '#app',
  3. components: {
  4. 'component-a': ComponentA,
  5. 'component-b': ComponentB
  6. }
  7. })

对于components对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象。
注意局部注册的组件在其子组件中不可用。例如,如果你希望ComponentA在ComponentB中可用,则你需要这样写:

  1. var ComponentA = { /* ... */ }
  2. var ComponentB = {
  3. components: {
  4. 'component-a': ComponentA
  5. },
  6. // ...
  7. }

模块系统

或者如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:

  1. import ComponentA from './ComponentA.vue'
  2. export default {
  3. components: {
  4. ComponentA
  5. },
  6. // ...
  7. }

基础组件的自动化全局注册

可能你的许多组件只是包裹了一个输入框或按钮之类的元素,是相对通用的。我们有时候会把它们称为基础组件,它们会在各个组件中被频繁的用到。
如果你恰好使用了 webpack (或在内部使用了 webpack 的Vue CLI 3+),那么就可以使用require.context只全局注册这些非常通用的基础组件。这里有一份可以让你在应用入口文件 (比如src/main.js) 中全局导入基础组件的示例代码:

  1. import Vue from 'vue'
  2. import upperFirst from 'lodash/upperFirst'
  3. import camelCase from 'lodash/camelCase'
  4. const requireComponent = require.context(
  5. // 其组件目录的相对路径
  6. './components',
  7. // 是否查询其子目录
  8. false,
  9. // 匹配基础组件文件名的正则表达式
  10. /Base[A-Z]\w+\.(vue|js)$/
  11. )
  12. requireComponent.keys().forEach(fileName => {
  13. // 获取组件配置
  14. const componentConfig = requireComponent(fileName)
  15. // 获取组件的 PascalCase 命名
  16. const componentName = upperFirst(
  17. camelCase(
  18. // 获取和目录深度无关的文件名
  19. fileName
  20. .split('/')
  21. .pop()
  22. .replace(/\.\w+$/, '')
  23. )
  24. )
  25. // 全局注册组件
  26. Vue.component(
  27. componentName,
  28. // 如果这个组件选项是通过 `export default` 导出的,
  29. // 那么就会优先使用 `.default`,
  30. // 否则回退到使用模块的根。
  31. componentConfig.default || componentConfig
  32. )
  33. })

记住全局注册的行为必须在根 Vue 实例 (通过new Vue) 创建之前发生

通过 Prop 向子组件传递数据

Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。
我们可以使用v-bind来动态传递 prop。

  1. <blog-post
  2. v-for="post in posts"
  3. v-bind:key="post.id"
  4. v-bind:title="post.title"
  5. ></blog-post>

监听子组件事件

父级组件可以像处理 native DOM 事件一样通过v-on监听子组件实例的任意事件:

  1. <blog-post
  2. ...
  3. v-on:enlarge-text="postFontSize += 0.1"
  4. ></blog-post>

同时子组件可以通过调用内建的$emit方法并传入事件名称来触发一个事件:

  1. <button v-on:click="$emit('enlarge-text')"> Enlarge text </button>

使用事件抛出一个值

有的时候用一个事件来抛出一个特定的值是非常有用的。这时可以使用$emit的第二个参数来提供这个值:

  1. <button v-on:click="$emit('enlarge-text', 0.1)">
  2. Enlarge text
  3. </button>

然后当在父级组件监听这个事件的时候,我们可以通过$event访问到被抛出的这个值:

  1. <blog-post
  2. ...
  3. v-on:enlarge-text="postFontSize += $event"
  4. ></blog-post>

或者,如果这个事件处理函数是一个方法:

  1. <blog-post
  2. ...
  3. v-on:enlarge-text="onEnlargeText"
  4. ></blog-post>
  5. methods: {
  6. onEnlargeText: function (enlargeAmount) {
  7. this.postFontSize += enlargeAmount
  8. }
  9. }

Content Distribution with Slots(通过插槽分发内容

)

在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即v-slot指令)。它取代了slot和slot-scope这两个目前已被废弃但未被移除且仍在文档中的 attribute。新语法的由来可查阅这份RFC

Slot Content

  1. Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自[Web Components 规范草案](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md),将<slot>元素作为承载分发内容的出口。
  1. <navigation-link url="/profile">
  2. Your Profile
  3. </navigation-link>
  4. <a v-bind:href="url" class="nav-link">
  5. <slot></slot>
  6. </a>

插槽内可以包含任何模板代码。

Compilation Scope

(编译作用域) 父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

Fallback Content(后备内容

) 有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,它只会在没有提供内容的时候被渲染。

  1. <button type="submit">
  2. <slot></slot>
  3. </button>
  4. <button type="submit">
  5. <slot>Submit</slot>
  6. </button>
  7. <submit-button></submit-button>
  8. <!--渲染结果-->
  9. <button type="submit">Submit</button>

但是如果我们提供内容,则这个提供的内容将会被渲染从而取代后备内容。

Named Slots(具名插槽 )

Updated in 2.6.0+.See herefor the deprecated syntax using theslotattribute.

For these cases, the element has a special attribute, name, which can be used to define additional slots:
对于这样的情况, 元素有一个特殊的 attribute:name。这个 attribute 可以用来定义额外的插槽:

  1. <-------<base-layout> 组件:--->
  2. <div class="container">
  3. <header>
  4. <slot name="header"></slot>
  5. </header>
  6. <main>
  7. <slot></slot>
  8. </main>
  9. <footer>
  10. <slot name="footer"></slot>
  11. </footer>
  12. </div>

一个不带name的出口会带有隐含的名字“default”。
在向具名插槽提供内容的时候,我们可以在一个