Close
升级到 Vue 3 | Vue 2 EOL

组件基础

基本示例

这是一个 Vue 组件的示例

// Define a new component called button-counter
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

组件是具有名称的可重用 Vue 实例:在本例中,<button-counter>。我们可以使用此组件作为自定义元素,在使用 new Vue 创建的根 Vue 实例中使用。

<div id="components-demo">
<button-counter></button-counter>
</div>
new Vue({ el: '#components-demo' })

由于组件是可重用 Vue 实例,因此它们接受与 new Vue 相同的选项,例如 datacomputedwatchmethods 和生命周期钩子。唯一的例外是 el 等一些根特定选项。

重用组件

组件可以根据需要重用多次。

<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>

请注意,当单击按钮时,每个按钮都保持其自己的独立 count。这是因为每次使用组件时,都会创建一个它的新 **实例**。

data 必须是函数

当我们定义 <button-counter> 组件时,您可能已经注意到 data 没有直接提供一个对象,就像这样

data: {
count: 0
}

相反,**组件的 data 选项必须是一个函数**,以便每个实例都可以维护返回的数据对象的独立副本。

data: function () {
return {
count: 0
}
}

如果 Vue 没有这个规则,单击一个按钮会影响所有其他实例的数据,如下所示。

组织组件

应用程序通常被组织成嵌套组件的树结构。

Component Tree

例如,您可能拥有用于页眉、侧边栏和内容区域的组件,每个组件通常包含用于导航链接、博客文章等的其他组件。

要在模板中使用这些组件,必须注册它们,以便 Vue 知道它们。组件注册有两种类型:**全局** 和 **局部**。到目前为止,我们只使用 Vue.component 全局注册组件。

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

全局注册的组件可以在任何随后创建的根 Vue 实例 (new Vue) 的模板中使用 - 甚至可以在该 Vue 实例的组件树的所有子组件中使用。

现在您只需要了解注册即可,但是,在您阅读完本页并对内容感到满意后,我们建议您稍后回来阅读有关 组件注册 的完整指南。

使用 Props 将数据传递给子组件

之前,我们提到过为博客文章创建组件。问题是,除非您可以向它传递数据(例如要显示的特定文章的标题和内容),否则该组件将毫无用处。这就是 Props 的作用。

Props 是您可以注册在组件上的自定义属性。当将值传递给 prop 属性时,它将成为该组件实例上的一个属性。要将标题传递给我们的博客文章组件,我们可以将其包含在该组件接受的 props 列表中,使用 props 选项。

Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})

组件可以拥有任意数量的 props,默认情况下,任何值都可以传递给任何 prop。在上面的模板中,您将看到我们可以访问组件实例上的此值,就像使用 data 一样。

注册 prop 后,您可以像这样将数据作为自定义属性传递给它。

<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>

但是,在典型的应用程序中,您可能在 data 中有一个文章数组。

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' }
]
}
})

然后想要为每个文章渲染一个组件。

<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>

在上面,您将看到我们可以使用 v-bind 动态传递 props。当您事先不知道要渲染的确切内容时(例如,当 从 API 获取文章 时),这特别有用。

现在您只需要了解 props 即可,但是,在您阅读完本页并对内容感到满意后,我们建议您稍后回来阅读有关 Props 的完整指南。

单个根元素

在构建 <blog-post> 组件时,您的模板最终将包含的不仅仅是标题。

<h3>{{ title }}</h3>

至少,您需要包含文章的内容。

<h3>{{ title }}</h3>
<div v-html="content"></div>

但是,如果您在模板中尝试这样做,Vue 会显示一条错误消息,说明 **每个组件都必须有一个单个根元素**。您可以通过将模板包装在父元素中来修复此错误,例如

<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>

随着组件的增长,我们可能不仅需要文章的标题和内容,还需要发布时间、评论等等。为每个相关信息定义一个 prop 会变得非常烦人。

<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
v-bind:content="post.content"
v-bind:publishedAt="post.publishedAt"
v-bind:comments="post.comments"
></blog-post>

因此,现在可能是将 <blog-post> 组件重构为接受单个 post prop 的好时机。

<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
`
})

上面的示例和一些将来的示例使用 JavaScript 的 模板字面量 使多行模板更易读。这些不受 Internet Explorer (IE) 支持,因此,如果您必须支持 IE 并且没有进行转译(例如,使用 Babel 或 TypeScript),请改用 换行符转义

现在,每当向 post 对象添加新属性时,它都会自动在 <blog-post> 中可用。

监听子组件事件

在开发 <blog-post> 组件时,某些功能可能需要与父组件进行通信。例如,我们可能决定添加一个辅助功能,以放大博客文章的文本,同时保持页面其余部分的默认大小。

在父组件中,我们可以通过添加 postFontSize 数据属性来支持此功能。

new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [/* ... */],
postFontSize: 1
}
})

它可以在模板中使用,以控制所有博客文章的字体大小。

<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
</div>
</div>

现在,让我们在每篇文章内容之前添加一个按钮来放大文本。

Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button>
Enlarge text
</button>
<div v-html="post.content"></div>
</div>
`
})

问题是,此按钮没有任何作用。

<button>
Enlarge text
</button>

当我们单击按钮时,我们需要向父组件传达它应该放大所有文章的文本。幸运的是,Vue 实例提供了一个自定义事件系统来解决此问题。父组件可以使用 v-on 监听子组件实例上的任何事件,就像我们处理原生 DOM 事件一样。

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

然后,子组件可以通过调用内置的 $emit 方法 在自身上发出事件,并传递事件的名称。

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

由于 v-on:enlarge-text="postFontSize += 0.1" 监听器,父组件将接收事件并更新 postFontSize 值。

使用事件发出值

有时,使用事件发出特定值很有用。例如,我们可能希望 <blog-post> 组件负责放大文本的程度。在这些情况下,我们可以使用 $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
}
}

在组件上使用 v-model

自定义事件也可以用来创建与 v-model 配合使用的自定义输入。请记住,

<input v-model="searchText">

<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>

的作用相同。当在组件上使用时,v-model 反而会这样做。

<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>

但是,要使这真正起作用,组件内的 <input> 必须

以下是实际操作。

Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})

现在,v-model 应该与该组件完美配合。

<custom-input v-model="searchText"></custom-input>

现在您只需要了解自定义组件事件即可,但是,在您阅读完本页并对内容感到满意后,我们建议您稍后回来阅读有关 自定义事件 的完整指南。

使用插槽进行内容分发

就像使用 HTML 元素一样,能够将内容传递给组件通常很有用,例如这样。

<alert-box>
Something bad happened.
</alert-box>

它可能会渲染类似于以下内容。

发生了一些不好的事情。

幸运的是,Vue 的自定义 <slot> 元素使此任务变得非常简单。

Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})

如您在上面看到的,我们只需在想要放置插槽的位置添加它 - 就这样。我们完成了!

现在您只需要了解插槽即可,但是,在您阅读完本页并对内容感到满意后,我们建议您稍后回来阅读有关 插槽 的完整指南。

动态组件

有时,在选项卡式界面中,动态切换组件很有用。

上面是通过 Vue 的 <component> 元素以及 is 特殊属性实现的。

<!-- Component changes when currentTabComponent changes -->
<component v-bind:is="currentTabComponent"></component>

在上面的示例中,currentTabComponent 可以包含

查看这个例子来尝试完整的代码,或者这个版本来查看一个绑定到组件选项对象的例子,而不是它的注册名称。

请记住,此属性可以与常规 HTML 元素一起使用,但是它们将被视为组件,这意味着所有属性将被绑定为 DOM 属性。对于某些属性,例如value,要按预期工作,您需要使用.prop 修饰符绑定它们。

现在您只需要了解动态组件,但是,在您读完本页并熟悉其内容后,我们建议您稍后回来阅读有关动态和异步组件的完整指南。

DOM 模板解析注意事项

某些 HTML 元素,例如<ul><ol><table><select> 对其内部可以出现的元素有限制,而某些元素,例如<li><tr><option> 只能出现在某些其他元素内部。

这将在使用具有此类限制的元素的组件时导致问题。例如

<table>
<blog-post-row></blog-post-row>
</table>

自定义组件<blog-post-row> 将被提升为无效内容,导致最终渲染输出中的错误。幸运的是,is 特殊属性提供了一种解决方法

<table>
<tr is="blog-post-row"></tr>
</table>

需要注意的是,如果您使用以下来源之一的字符串模板,则适用此限制

现在您只需要了解 DOM 模板解析注意事项 - 实际上,这是 Vue 的Essentials的结尾。恭喜!还有更多内容要学习,但首先,我们建议您休息一下,自己玩玩 Vue 并构建一些有趣的东西。

一旦您对刚刚吸收的知识感到满意,我们建议您回来阅读有关动态和异步组件的完整指南,以及侧边栏中“组件深入”部分的其他页面。