From 7cdc72ba2e909da394d4aa0153066b40e765b666 Mon Sep 17 00:00:00 2001 From: Jinjiang Date: Sun, 15 Apr 2018 21:43:59 +0800 Subject: [PATCH 1/7] rewrite components.md --- src/v2/guide/components.md | 1727 +++++++++--------------------------- 1 file changed, 423 insertions(+), 1304 deletions(-) diff --git a/src/v2/guide/components.md b/src/v2/guide/components.md index 512457671..5073167e1 100644 --- a/src/v2/guide/components.md +++ b/src/v2/guide/components.md @@ -1,1476 +1,595 @@ --- -title: 组件 +title: Components Basics type: guide order: 11 --- -## 什么是组件? +## Base Example -组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 `is` 特性进行了扩展的原生 HTML 元素。 - -所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。 - -## 使用组件 - -### 全局注册 - -我们已经知道,可以通过以下方式创建一个 Vue 实例: +Here's an example of a Vue component: ``` js -new Vue({ - el: '#some-element', - // 选项 +// Define a new component called button-counter +Vue.component('button-counter', { + data: function () { + return { + count: 0 + } + }, + template: '' }) ``` -要注册一个全局组件,可以使用 `Vue.component(tagName, options)`。例如: +Components are reusable Vue instances with a name: in this case, ``. We can use this component as a custom element inside a root Vue instance created with `new Vue`: -``` js -Vue.component('my-component', { - // 选项 -}) +```html +
+ +
``` -

请注意,对于自定义标签的命名 Vue.js 不强制遵循 [W3C 规则](https://www.w3.org/TR/custom-elements/#concepts) (小写,并且包含一个短杠),尽管这被认为是最佳实践。

- -组件在注册之后,便可以作为自定义元素 `` 在一个实例的模板中使用。注意确保在初始化根实例**之前**注册组件: - -``` html -
- -
+```js +new Vue({ el: '#components-demo' }) ``` -``` js -// 注册 -Vue.component('my-component', { - template: '
A custom component!
' +{% raw %} +
+ +
+ +{% endraw %} -// 创建根实例 -new Vue({ - el: '#example' -}) -``` +Since components are reusable Vue instances, they accept the same options as `new Vue`, such as `data`, `computed`, `watch`, `methods`, and lifecycle hooks. The only exceptions are a few root-specific options like `el`. -渲染为: +## Reusing Components -``` html -
-
A custom component!
+Components can be reused as many times as you want: + +```html +
+ + +
``` {% raw %} -
- +
+ + +
{% endraw %} -### 局部注册 +Notice that when clicking on the buttons, each one maintains its own, separate `count`. That's because each time you use a component, a new **instance** of it is created. -你不必把每个组件都注册到全局。你可以通过某个 Vue 实例/组件的实例选项 `components` 注册仅在其作用域中可用的组件: +### `data` Must Be a Function -``` js -var Child = { - template: '
A custom component!
' +When we defined the `` component, you may have noticed that `data` wasn't directly provided an object, like this: + +```js +data: { + count: 0 } +``` -new Vue({ - // ... - components: { - // 将只在父组件模板中可用 - 'my-component': Child +Instead, **a component's `data` option must be a function**, so that each instance can maintain an independent copy of the returned data object: + +```js +data: function () { + return { + count: 0 } -}) +} ``` -这种封装也适用于其它可注册的 Vue 功能,比如指令。 +If Vue didn't have this rule, clicking on one button would affect the data of _all other instances_, like below: + +{% raw %} +
+ + + +
+ +{% endraw %} -### DOM 模板解析注意事项 +## Organizing Components -当使用 DOM 作为模板时 (例如,使用 `el` 选项来把 Vue 实例挂载到一个已有内容的元素上),你会受到 HTML 本身的一些限制,因为 Vue 只有在浏览器解析、规范化模板**之后**才能获取其内容。尤其要注意,像 `
    `、`
      `、``、`
      - ... -
      -``` +For example, you might have components for a header, sidebar, and content area, each typically containing other components for navigation links, blog posts, etc. -自定义组件 `` 会被当作无效的内容,因此会导致错误的渲染结果。变通的方案是使用特殊的 `is` 特性: +To use these components in templates, they must be registered so that Vue knows about them. There are two types of component registration: **global** and **local**. So far, we've only registered components globally, using `Vue.component`: -``` html - - -
      +```js +Vue.component('my-component-name', { + // ... options ... +}) ``` -**应当注意,如果使用来自以下来源之一的字符串模板,则没有这些限制:** +Globally registered components can be used in the template of any root Vue instance (`new Vue`) created afterwards -- and even inside all subcomponents of that Vue instance's component tree. -- ` {% endraw %} -由于这三个组件实例共享了同一个 `data` 对象,因此递增一个 counter 会影响所有组件!这就错了。我们可以通过为每个组件返回全新的数据对象来修复这个问题: +In a typical app, however, you'll likely have an array of posts in `data`: -``` js -data: function () { - return { - counter: 0 +```js +new Vue({ + el: '#blog-post-demo', + data: { + posts: [ + { id: 1, title: 'My journey with Vue' }, + { id: 2, title: 'Blogging with Vue' }, + { id: 3, title: 'Why Vue is so fun' }, + ] } -} +}) ``` -现在每个 counter 都有它自己内部的状态了: +Then want to render a component for each one: -{% raw %} -
      - - - -
      - -{% endraw %} +```html + +``` -### 组件组合 +Above, you'll see that we can use `v-bind` to dynamically pass props. This is especially useful when you don't know the exact content you're going to render ahead of time, like when [fetching posts from an API](https://jsfiddle.net/chrisvfritz/sbLgr0ad). -组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。然而,通过一个良好定义的接口来尽可能将父子组件解耦也是很重要的。这保证了每个组件的代码可以在相对隔离的环境中书写和理解,从而提高了其可维护性和复用性。 +That's all you need to know about props for now, but once you've finished reading this page and feel comfortable with its content, we recommend coming back later to read the full guide on [Props](components-props.html). -在 Vue 中,父子组件的关系可以总结为 **prop 向下传递,事件向上传递**。父组件通过 **prop** 给子组件下发数据,子组件通过**事件**给父组件发送消息。看看它们是怎么工作的。 +## A Single Root Element -

      - prop 向下传递,事件向上传递 -

      +When building out a `` component, your template will eventually contain more than just the title: -## Prop +```html +

      {{ post.title }}

      +``` -### 使用 Prop 传递数据 +At the very least, you'll want to include the post's content: -组件实例的作用域是**孤立的**。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。父组件的数据需要通过 **prop** 才能下发到子组件中。 +```html +

      {{ post.title }}

      +
      +``` -子组件要显式地用 [`props` 选项](../api/#props)声明它预期的数据: +If you try this in your template however, Vue will show an error, explaining that **every component must have a single root element**. You can fix this error by wrapping the template in a parent element, such as: -``` js -Vue.component('child', { - // 声明 props - props: ['message'], - // 就像 data 一样,prop 也可以在模板中使用 - // 同样也可以在 vm 实例中通过 this.message 来使用 - template: '{{ message }}' -}) +```html +
      +

      {{ post.title }}

      +
      +
      ``` -然后我们可以这样向它传入一个普通字符串: +## Sending Messages to Parents with Events -``` html - -``` +As we develop our `` component, some features may require communicating back up to the parent. For example, we may decide to include an accessibility feature to enlarge the text of blog posts, while leaving the rest of the page its default size: -结果: +In the parent, we can support this feature by adding a `postFontSize` data property: -{% raw %} -
      - -
      - -{% endraw %} - -### camelCase vs. kebab-case +``` -HTML 特性是不区分大小写的。所以,当使用的不是字符串模板时,camelCase (驼峰式命名) 的 prop 需要转换为相对应的 kebab-case (短横线分隔式命名): +Which can be used in the template to control the font size of all blog posts: -``` js -Vue.component('child', { - // 在 JavaScript 中使用 camelCase - props: ['myMessage'], - template: '{{ myMessage }}' -}) +```html +
      +
      + +
      +
      ``` -``` html - - -``` +Now let's add a button to enlarge the text right before the content of every post: -如果你使用字符串模板,则没有这些限制。 +```js +Vue.component('blog-post', { + props: ['post'], + template: ` +
      +

      {{ post.title }}

      + +
      +
      + ` +}) +``` -### 动态 Prop +

      The above example and some future ones use JavaScript's [template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) to make multi-line templates more readable. These are not supported by Internet Explorer (IE), so if you must support IE and are not transpiling (e.g. with Babel or TypeScript), use [newline escapes](https://css-tricks.com/snippets/javascript/multiline-string-variables-in-javascript/) instead.

      -与绑定到任何普通的 HTML 特性相类似,我们可以用 `v-bind` 来动态地将 prop 绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件: +The problem is, this button doesn't do anything: -``` html -
      - -
      - -
      +```html + ``` -``` js -new Vue({ - el: '#prop-example-2', - data: { - parentMsg: 'Message from parent' - } -}) +When we click on the button, we need to communicate to the parent that it should enlarge the text of all posts. Fortunately, Vue instances provide a custom events system to solve this problem. To emit an event to the parent, we can call the built-in [**`$emit`** method](../api/#Instance-Methods-Events), passing the name of the event: + +```html + ``` -你也可以使用 `v-bind` 的缩写语法: +Then on our blog post, we can listen for this event with `v-on`, just as we would with a native DOM event: -``` html - +```html + ``` -结果: - {% raw %} -
      - -
      - +
      +
      + +
      {% endraw %} -如果你想把一个对象的所有属性作为 prop 进行传递,可以使用不带任何参数的 `v-bind` (即用 `v-bind` 而不是 `v-bind:prop-name`)。例如,已知一个 `todo` 对象: - -``` js -todo: { - text: 'Learn Vue', - isComplete: false -} -``` +### Emitting a Value With an Event -然后: +It's sometimes useful to emit a specific value with an event. For example, we may want the `` component to be in charge of how much to enlarge the text by. In those cases, we can use `$emit`'s 2nd parameter to provide this value: -``` html - +```html + ``` -将等价于: +Then when we listen to the event in the parent, we can access the emitted event's value with `$event`: -``` html - +```html + ``` -### 字面量语法 vs 动态语法 - -初学者常犯的一个错误是使用字面量语法传递数值: +Or, if the event handler is a method: -``` html - - +```html + ``` -因为它是一个字面量 prop,它的值是字符串 `"1"` 而不是一个数值。如果想传递一个真正的 JavaScript 数值,则需要使用 `v-bind`,从而让它的值被当作 JavaScript 表达式计算: +Then the value will be passed as the first parameter of that method: -``` html - - +```js +methods: { + onEnlargeText: function (enlargeAmount) { + this.postFontSize += enlargeAmount + } +} ``` -### 单向数据流 - -Prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是反过来不会。这是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得难以理解。 - -另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你**不应该**在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。 - -在两种情况下,我们很容易忍不住想去修改 prop 中数据: - -1. Prop 作为初始值传入后,子组件想把它当作局部数据来用; - -2. Prop 作为原始数据传入,由子组件处理成其它数据输出。 +### Using `v-model` on Components -对这两种情况,正确的应对方式是: +Custom events can also be used to create custom inputs that work with `v-model`. Remember that: -1. 定义一个局部变量,并用 prop 的值初始化它: +```html + +``` - ``` js - props: ['initialCounter'], - data: function () { - return { counter: this.initialCounter } - } - ``` +does the same thing as: -2. 定义一个计算属性,处理 prop 的值并返回: +```html + +``` - ``` js - props: ['size'], - computed: { - normalizedSize: function () { - return this.size.trim().toLowerCase() - } - } - ``` +When used on a component, `v-model` instead does this: -

      注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它**会影响**父组件的状态。

      +``` html + +``` -### Prop 验证 +For this to actually work though, the `` inside the component must: -我们可以为组件的 prop 指定验证规则。如果传入的数据不符合要求,Vue 会发出警告。这对于开发给他人使用的组件非常有用。 +- Bind the `value` attribute to a `value` prop +- On `input`, emit its own custom `input` event with the new value -要指定验证规则,需要用对象的形式来定义 prop,而不能用字符串数组: +Here's that in action: -``` js -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 - } - } - } +```js +Vue.component('custom-input', { + props: ['value'], + template: ` + +``` -所谓非 prop 特性,就是指它可以直接传入组件,而不需要定义相应的 prop。 +That's all you need to know about custom component events for now, but once you've finished reading this page and feel comfortable with its content, we recommend coming back later to read the full guide on [Custom Events](components-custom-events.html). -尽管为组件定义明确的 prop 是推荐的传参方式,组件的作者却并不总能预见到组件被使用的场景。所以,组件可以接收任意传入的特性,这些特性都会被添加到组件的根元素上。 +## Content Distribution with Slots -例如,假设我们使用了第三方组件 `bs-date-input`,它包含一个 Bootstrap 插件,该插件需要在 `input` 上添加 `data-3d-date-picker` 这个特性。这时可以把特性直接添加到组件上 (不需要事先定义 `prop`): +Just like with HTML elements, it's often useful to be able to pass content to a component, like this: ``` html - + + Something bad happened. + ``` -添加属性 `data-3d-date-picker="true"` 之后,它会被自动添加到 `bs-date-input` 的根元素上。 +Which might render something like: -### 替换/合并现有的特性 +{% raw %} +
      + + Something bad happened. + +
      + + +{% endraw %} -假设这是 `bs-date-input` 的模板: +Fortunately, this task is made very simple by Vue's custom `` element: -``` html - +```js +Vue.component('alert-box', { + template: ` +
      + Error! + +
      + ` +}) ``` -为了给该日期选择器插件增加一个特殊的主题,我们可能需要增加一个特殊的 class,比如: +As you'll see above, we just add the slot where we want it to go -- and that's it. We're done! -``` html - -``` +That's all you need to know about slots for now, but once you've finished reading this page and feel comfortable with its content, we recommend coming back later to read the full guide on [Slots](components-slots.html). -在这个例子当中,我们定义了两个不同的 `class` 值: +## Dynamic Components -- `form-control`,来自组件自身的模板 -- `date-picker-theme-dark`,来自父组件 +Sometimes, it's useful to dynamically switch between components, like in a tabbed interface: -对于多数特性来说,传递给组件的值会覆盖组件本身设定的值。即例如传递 `type="large"` 将会覆盖 `type="date"` 且有可能破坏该组件!所幸我们对待 `class` 和 `style` 特性会更聪明一些,这两个特性的值都会做合并 (merge) 操作,让最终生成的值为:`form-control date-picker-theme-dark`。 +{% raw %} +
      + + +
      + + +{% endraw %} -## 自定义事件 +The above is made possible by Vue's `` element with the `is` special attribute: -我们知道,父组件使用 prop 传递数据给子组件。但子组件怎么跟父组件通信呢?这个时候 Vue 的自定义事件系统就派得上用场了。 +```html + + +``` -### 使用 `v-on` 绑定自定义事件 +In the example above, `currentTabComponent` can contain either: -每个 Vue 实例都实现了[事件接口](../api/#实例方法-事件),即: +- the name of a registered component, or +- a component's options object -- 使用 `$on(eventName)` 监听事件 -- 使用 `$emit(eventName, optionalPayload)` 触发事件 +See [this fiddle](https://jsfiddle.net/chrisvfritz/o3nycadu/) to experiment with the full code, or [this version](https://jsfiddle.net/chrisvfritz/b2qj69o1/) for an example binding to a component's options object, instead of its registered name. -

      Vue 的事件系统与浏览器的 [EventTarget API](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) 有所不同。尽管它们运行起来类似,但是 `$on` 和 `$emit` __并不是__`addEventListener` 和 `dispatchEvent` 的别名。

      +That's all you need to know about dynamic components for now, but once you've finished reading this page and feel comfortable with its content, we recommend coming back later to read the full guide on [Dynamic & Async Components](components-dynamic-async.html). -另外,父组件可以在使用子组件的地方直接用 `v-on` 来监听子组件触发的事件。 +## DOM Template Parsing Caveats -

      不能用 `$on` 监听子组件释放的事件,而必须在模板里直接用 `v-on` 绑定,参见下面的例子。

      +Some HTML elements, such as `
        `, `
          `, `` and ``, and `
          + +
          ``` -``` js -Vue.component('button-counter', { - template: '', - data: function () { - return { - counter: 0 - } - }, - methods: { - incrementCounter: function () { - this.counter += 1 - this.$emit('increment') - } - }, -}) +The custom component `` will be hoisted out as invalid content, causing errors in the eventual rendered output. Fortunately, the `is` special attribute offers a workaround: -new Vue({ - el: '#counter-event-example', - data: { - total: 0 - }, - methods: { - incrementTotal: function () { - this.total += 1 - } - } -}) +``` html + + +
          ``` -{% raw %} -
          -

          {{ total }}

          - - -
          - -{% endraw %} - -在本例中,子组件已经和它外部完全解耦了。它所做的只是报告自己的内部事件,因为父组件可能会关心这些事件。请注意这一点很重要。 - -这里有一个如何使用载荷 (payload) 数据的示例: - -``` html -
          -

          {{ msg }}

          - -
          -``` - -``` js -Vue.component('button-message', { - template: `
          - - -
          `, - 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) - } - } -}) -``` - -{% raw %} -
          -

          {{ msg }}

          - -
          - -{% endraw %} - -第二个示例的重点在于子组件仍然是完全和外界解耦的。它做的事情全都是记录其自身的活动,活动记录是包括一份传入事件触发器的载荷数据在内的,只是为了展示父组件可以不关注的一个场景。 - -### 给组件绑定原生事件 - -有时候,你可能想在某个组件的根元素上监听一个原生事件。可以使用 `v-on` 的修饰符 `.native`。例如: - -``` html - -``` - -### `.sync` 修饰符 - -> 2.3.0+ - -在一些情况下,我们可能会需要对一个 prop 进行“双向绑定”。事实上,这正是 Vue 1.x 中的 `.sync` 修饰符所提供的功能。当一个子组件改变了一个带 `.sync` 的 prop 的值时,这个变化也会同步到父组件中所绑定的值。这很方便,但也会导致问题,因为它破坏了单向数据流。由于子组件改变 prop 的代码和普通的状态改动代码毫无区别,当光看子组件的代码时,你完全不知道它何时悄悄地改变了父组件的状态。这在 debug 复杂结构的应用时会带来很高的维护成本。 - -上面所说的正是我们在 2.0 中移除 `.sync` 的理由。但是在 2.0 发布之后的实际应用中,我们发现 `.sync` 还是有其适用之处,比如在开发可复用的组件库时。我们需要做的只是**让子组件改变父组件状态的代码更容易被区分**。 - -从 2.3.0 起我们重新引入了 `.sync` 修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 `v-on` 监听器。 - -如下代码 - -``` html - -``` - -会被扩展为: - -``` html - -``` - -当子组件需要更新 `foo` 的值时,它需要显式地触发一个更新事件: - -``` js -this.$emit('update:foo', newValue) -``` - -当使用一个对象一次性设置多个属性的时候,这个 `.sync` 修饰符也可以和 `v-bind` 一起使用: - -```html - -``` - -这个例子会为 `foo` 和 `bar` 同时添加用于更新的 `v-on` 监听器。 - -### 使用自定义事件的表单输入组件 - -自定义事件可以用来创建自定义的表单输入组件,使用 `v-model` 来进行数据双向绑定。要牢记: - -``` html - -``` - -这不过是以下示例的语法糖: - -``` html - -``` - -所以在组件中使用时,它相当于下面的简写: - -``` html - - -``` - -所以要让组件的 `v-model` 生效,它应该 (从 2.2.0 起是可配置的): - -- 接受一个 `value` prop -- 在有新的值时触发 `input` 事件并将新值作为参数 - -我们来看一个非常简单的货币输入的自定义控件: - -``` html - -``` - -``` js -Vue.component('currency-input', { - template: '\ - \ - $\ - \ - \ - ', - 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)) - } - } -}) -``` - -{% raw %} -
          - -
          - -{% endraw %} - -当然,上面的例子还是比较初级的。比如,用户输入多个小数点或句号也是允许的,好恶心吧!因此我们需要一个复杂一些的例子,下面是一个更加完善的货币过滤器: - - - -### 自定义组件的 `v-model` - -> 2.2.0 新增 - -默认情况下,一个组件的 `v-model` 会使用 `value` prop 和 `input` 事件。但是诸如单选框、复选框之类的输入类型可能把 `value` 用作了别的目的。`model` 选项可以避免这样的冲突: - -``` js -Vue.component('my-checkbox', { - model: { - prop: 'checked', - event: 'change' - }, - props: { - checked: Boolean, - // 这样就允许拿 `value` 这个 prop 做其它事了 - value: String - }, - // ... -}) -``` - -``` html - -``` - -上述代码等价于: - -``` html - - -``` - -

          注意你仍然需要显式声明 `checked` 这个 prop。

          - -### 非父子组件的通信 - -有时候,非父子关系的两个组件之间也需要通信。在简单的场景下,可以使用一个空的 Vue 实例作为事件总线: - -``` js -var bus = new Vue() -``` - -``` js -// 触发组件 A 中的事件 -bus.$emit('id-selected', 1) -``` - -``` js -// 在组件 B 创建的钩子中监听事件 -bus.$on('id-selected', function (id) { - // ... -}) -``` - -在复杂的情况下,我们应该考虑使用专门的[状态管理模式](state-management.html)。 - -## 使用插槽分发内容 - -在使用组件时,我们常常要像这样组合它们: - -``` html - - - - -``` - -注意两点: - -1. `` 组件不知道它会收到什么内容。这是由使用 `` 的父组件决定的。 - -2. `` 组件很可能有它自己的模板。 - -为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为**内容分发** (即 Angular 用户熟知的“transclusion”)。Vue.js 实现了一个内容分发 API,参照了当前 [Web Components 规范草案](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md),使用特殊的 `` 元素作为原始内容的插槽。 - -### 编译作用域 - -在深入内容分发 API 之前,我们先明确内容在哪个作用域里编译。假定模板为: - -``` html - - {{ message }} - -``` - -`message` 应该绑定到父组件的数据,还是绑定到子组件的数据?答案是父组件。组件作用域简单地说是: - -> 父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。 - -一个常见错误是试图在父组件模板内将一个指令绑定到子组件的属性/方法: - -``` html - - -``` - -假定 `someChildProperty` 是子组件的属性,上例不会如预期那样工作。父组件模板并不感知子组件的状态。 - -如果要绑定子组件作用域内的指令到一个组件的根节点,你应当在子组件自己的模板里做: - -``` js -Vue.component('child-component', { -  // 有效,因为是在正确的作用域内 - template: '
          Child
          ', - data: function () { - return { - someChildProperty: true - } - } -}) -``` - -类似地,被分发的内容会在父作用域内编译。 - -### 单个插槽 - -除非子组件模板包含至少一个 `` 插口,否则父组件的内容将会被**丢弃**。当子组件模板只有一个没有属性的插槽时,父组件传入的整个内容片段将插入到插槽所在的 DOM 位置,并替换掉插槽标签本身。 - -最初在 `` 标签中的任何内容都被视为**备用内容**。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。 - -假定 `my-component` 组件有如下模板: - -``` html -
          -

          我是子组件的标题

          - - 只有在没有要分发的内容时才会显示。 - -
          -``` - -父组件模板: - -``` html -
          -

          我是父组件的标题

          - -

          这是一些初始内容

          -

          这是更多的初始内容

          -
          -
          -``` - -渲染结果: - -``` html -
          -

          我是父组件的标题

          -
          -

          我是子组件的标题

          -

          这是一些初始内容

          -

          这是更多的初始内容

          -
          -
          -``` - -### 具名插槽 - -`` 元素可以用一个特殊的特性 `name` 来进一步配置如何分发内容。多个插槽可以有不同的名字。具名插槽将匹配内容片段中有对应 `slot` 特性的元素。 - -仍然可以有一个匿名插槽,它是**默认插槽**,作为找不到匹配的内容片段的备用插槽。如果没有默认插槽,这些找不到匹配的内容片段将被抛弃。 - -例如,假定我们有一个 `app-layout` 组件,它的模板为: - -``` html -
          -
          - -
          -
          - -
          -
          - -
          -
          -``` - -父组件模板: - -``` html - -

          这里可能是一个页面标题

          - -

          主要内容的一个段落。

          -

          另一个主要段落。

          - -

          这里有一些联系信息

          -
          -``` - -渲染结果为: - -``` html -
          -
          -

          这里可能是一个页面标题

          -
          -
          -

          主要内容的一个段落。

          -

          另一个主要段落。

          -
          -
          -

          这里有一些联系信息

          -
          -
          -``` - -在设计组合使用的组件时,内容分发 API 是非常有用的机制。 - -### 作用域插槽 - -> 2.1.0 新增 - -作用域插槽是一种特殊类型的插槽,用作一个 (能被传递数据的) 可重用模板,来代替已经渲染好的元素。 - -在子组件中,只需将数据传递到插槽,就像你将 prop 传递给组件一样: - -``` html -
          - -
          -``` - -在父级中,具有特殊特性 `slot-scope` 的 `