组件基础
组件基础 {#components-basics}
组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成一个层层嵌套的树状结构:

这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。Vue 同样也能很好地配合原生 Web Component。如果你想知道 Vue 组件与原生 Web Components 之间的关系,可以阅读此章节。
定义一个组件 {#defining-a-component}
当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 .vue 文件中,这被叫做单文件组件 (简称 SFC):
vue
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
当不使用构建步骤时,一个 Vue 组件以一个包含 Vue 特定选项的 JavaScript 对象来定义:
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
// 也可以针对一个 DOM 内联模板:
// template: '#my-template-element'
}
这里的模板是一个内联的 JavaScript 字符串,Vue 将会在运行时编译它。你也可以使用 ID 选择器来指向一个元素 (通常是原生的 <template> 元素),Vue 将会使用其内容作为模板来源。
上面的例子中定义了一个组件,并在一个 .js 文件里默认导出了它自己,但你也可以通过具名导出在一个文件中导出多个组件。
使用组件 {#using-a-component}
💡 提示
我们会在接下来的指引中使用单文件组件语法,无论你是否使用构建步骤,组件相关的概念都是相同的。示例一节中展示了两种场景中的组件使用情况。
要使用一个子组件,我们需要在父组件中导入它。假设我们把计数器组件放在了一个叫做 ButtonCounter.vue 的文件中,这个组件将会以默认导出的形式被暴露给外部。
vue
<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
</template>
通过 `
{{ title }}
```当一个值被传递给 prop 时,它将成为该组件实例上的一个属性。该属性的值可以像其他组件属性一样,在模板和组件的 this 上下文中访问。
vue [BlogPost.vue]
<template>
<h4>{{ title }}</h4>
</template>
defineProps 是一个仅 `
</div>
<div class="composition-api">
```vue{3} [BlogPost.vue]
这声明了一个组件可能触发的所有事件,还可以对事件的参数进行验证。同时,这还可以让 Vue 避免将它们作为原生事件监听器隐式地应用于子组件的根元素。
和 defineProps 类似,defineEmits 仅可用于 `
TypeScript 用户请参考:[为组件 emits 标注类型](/guide/typescript/composition-api#typing-component-emits)<sup class="vt-badge ts" />
如果你没有在使用 `<script setup>`,你可以通过 `emits` 选项定义组件会抛出的事件。你可以从 `setup()` 函数的第二个参数,即 setup 上下文对象上访问到 `emit` 函数:
```js
export default {
emits: ['enlarge-text'],
setup(props, ctx) {
ctx.emit('enlarge-text')
}
}
以上就是目前你需要了解的关于组件自定义事件的所有知识了。如果你看完本章节后还想知道更多细节,请深入阅读组件事件章节。
通过插槽来分配内容 {#content-distribution-with-slots}
一些情况下我们会希望能和 HTML 元素一样向组件中传递内容:
vue-html
<AlertBox>
Something bad happened.
</AlertBox>
我们期望能渲染成这样:
:::danger This is an Error for Demo Purposes
Something bad happened.
这可以通过 Vue 的自定义
<slot>元素来实现:
vue{4} [AlertBox.vue]<template> <div class="alert-box"> <strong>This is an Error for Demo Purposes</strong> <slot /> </div> </template> <style scoped> .alert-box { /* ... */ } </style>如上所示,我们使用
<slot>作为一个占位符,父组件传递进来的内容就会渲染在这里。以上就是目前你需要了解的关于插槽的所有知识了。如果你看完本章节后还想知道更多细节,请深入阅读组件插槽章节。
动态组件 {#dynamic-components}
有些场景会需要在两个组件间来回切换,比如 Tab 界面:
上面的例子是通过 Vue 的
<component>元素和特殊的isattribute 实现的:
vue-html<!-- currentTab 改变时组件也改变 --> <component :is="tabs[currentTab]"></component>在上面的例子中,被传给
:is的值可以是以下几种:
- 被注册的组件名
- 导入的组件对象
你也可以使用
isattribute 来创建一般的 HTML 元素。当使用
<component :is="...">来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过<KeepAlive>组件强制被切换掉的组件仍然保持“存活”的状态。DOM 内模板解析注意事项 {#in-dom-template-parsing-caveats}
如果你想在 DOM 中直接书写 Vue 模板,Vue 则必须从 DOM 中获取模板字符串。由于浏览器的原生 HTML 解析行为限制,有一些需要注意的事项。tip
请注意下面讨论只适用于直接在 DOM 中编写模板的情况。如果你使用来自以下来源的字符串模板,就不需要顾虑这些限制了:
- 单文件组件
- 内联模板字符串 (例如
template: '...') <script type="text/x-template">
大小写区分 {#case-insensitivity}
HTML 标签和属性名称是不分大小写的,所以浏览器会把任何大写的字符解释为小写。这意味着当你使用 DOM 内的模板时,无论是 PascalCase 形式的组件名称、camelCase 形式的 prop 名称还是 v-on 的事件名称,都需要转换为相应等价的 kebab-case (短横线连字符) 形式:
js// JavaScript 中的 camelCase const BlogPost = { props: ['postTitle'], emits: ['updatePost'], template: ` <h3>{{ postTitle }}</h3> ` }
vue-html<!-- HTML 中的 kebab-case --> <blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>闭合标签 {#self-closing-tags}
我们在上面的例子中已经使用过了闭合标签 (self-closing tag):
vue-html<MyComponent />这是因为 Vue 的模板解析器支持任意标签使用
/>作为标签关闭的标志。然而在 DOM 内模板中,我们必须显式地写出关闭标签:
vue-html<my-component></my-component>这是由于 HTML 只允许一小部分特殊的元素省略其关闭标签,最常见的就是
<input>和<img>。对于其他的元素来说,如果你省略了关闭标签,原生的 HTML 解析器会认为开启的标签永远没有结束,用下面这个代码片段举例来说:
vue-html<my-component /> <!-- 我们想要在这里关闭标签... --> <span>hello</span>将被解析为:
vue-html<my-component> <span>hello</span> </my-component> <!-- 但浏览器会在这里关闭标签 -->元素位置限制 {#element-placement-restrictions}
某些 HTML 元素对于放在其中的元素类型有限制,例如
<ul>,<ol>,<table>和<select>,相应的,某些元素仅在放置于特定元素中时才会显示,例如<li>,<tr>和<option>。这将导致在使用带有此类限制元素的组件时出现问题。例如:
vue-html<table> <blog-post-row></blog-post-row> </table>自定义的组件
<blog-post-row>将作为无效的内容被忽略,因而在最终呈现的输出中造成错误。我们可以使用特殊的isattribute 作为一种解决方案:
vue-html<table> <tr is="vue:blog-post-row"></tr> </table> ```tip
当使用在原生 HTML 元素上时,is 的值必须加上前缀 vue: 才可以被解析为一个 Vue 组件。这一点是必要的,为了避免和原生的自定义内置元素相混淆。
:::
以上就是你需要了解的关于 DOM 内模板解析的所有注意事项,同时也是 Vue 基础部分的所有内容。祝贺你!虽然还有很多需要学习的,但你可以先暂停一下,去用 Vue 做一些有趣的东西,或者研究一些示例。
完成了本页的阅读后,回顾一下你刚才所学到的知识,如果还想知道更多细节,我们推荐你继续阅读关于组件的完整指引。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
