Vue3 组件基础

基本示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建一个Vue 应用
const app = Vue.createApp({})

// 定义一个名为 button-counter 的新全局组件
app.component('button-counter', {
data() {
return {
count: 0
}
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
})

组件是带有名称的可复用实例,在这个例子中是 <button-counter>。我们可以把这个组件作为一个根实例中的自定义元素来使用:

1
2
3
<div id="components-demo">
<button-counter></button-counter>
</div>
1
app.mount('#components-demo')

因为组件是可复用的实例,所以它们与根实例接收相同的选项,例如 datacomputedwatchmethods 以及生命周期钩子等。

组件的复用

可以将组件进行任意次数的复用。

你每用一次组件,就会有一个它的新实例被创建。

组件的组织

一个应用会以一棵嵌套的组件树的形式来组织Component Tree为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册局部注册

全局注册的组件可以在应用中的任何组件的模板中使用。

通过 Prop 向子组件传递数据

Prop 是你可以在组件上注册的一些自定义 attribute。

当一个值被传递给一个 prop attribute 时,它就成为该组件实例中的一个 property。该 property 的值可以在模板中访问,就像任何其他组件 property 一样。

一个组件可以拥有任意数量的 prop,并且在默认情况下,无论任何值都可以传递给 prop。

我们可以使用 v-bind 来动态传递 prop。这在你一开始不清楚要渲染的具体内容,是非常有用的。

监听子组件事件

组件实例提供了一个自定义事件的系统来解决与父级组件进行沟通的问题。

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

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

我们可以在组件的 emits 选项中列出已抛出的事件:

1
2
3
4
app.component('blog-post', {
props: ['title'],
emits: ['enlargeText']
})

这将允许我们检查组件抛出的所有事件,还可以选择验证它们

使用事件抛出一个值

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

1
2
3
<button @click="$emit('enlargeText', 0.1)">
Enlarge text
</button>

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

1
<blog-post ... @enlarge-text="postFontSize += $event"></blog-post>

如果这个事件处理函数是一个方法,那么这个值将会作为第一个参数传入这个方法:

1
2
3
4
5
methods: {
onEnlargeText(enlargeAmount) {
this.postFontSize += enlargeAmount
}
}

在组件上使用 v-model

自定义事件也可以用于创建支持 v-model 的自定义输入组件。

1
2
3
<input v-model="searchText" />
// 等价于
<input :value="searchText" @input="searchText = $event.target.value" />

当用在组件上时,v-model 则会这样:

1
2
3
4
<custom-input
:model-value="searchText"
@update:model-value="searchText = $event"
></custom-input>

为了让它正常工作,这个组件内的 <input> 必须:

  • 将其 value attribute 绑定到一个名叫 modelValue 的 prop 上
  • 在其 input 事件被触发时,将新的值通过自定义的 update:modelValue 事件抛出
1
2
3
4
5
6
7
8
9
10
app.component('custom-input', {
props: ['modelValue'],
emits: ['update:modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`
})

在该组件中实现 v-model 的另一种方法是使用 computed property 的功能来定义 getter 和 setter。get 方法应返回 modelValue property,set 方法应该触发相应的事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.component('custom-input', {
props: ['modelValue'],
emits: ['update:modelValue'],
template: `
<input v-model="value">
`,
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
}
})

通过插槽分发内容

和 HTML 元素一样,我们经常需要向一个组件传递内容

可以通过使用 Vue 的自定义 <slot> 元素来实现

1
2
3
4
5
6
7
8
app.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})

使用 <slot> 作为我们想要插入内容的占位符

动态组件

有的时候,在不同组件之间进行动态切换是非常有用的

  • 已注册组件的名字,或
  • 一个组件选项对象

也可以使用 is attribute 来创建常规的 HTML 元素。

解析 DOM 模板时的注意事项

如果想在 DOM 中直接书写 Vue 模板,Vue 将不得不从 DOM 中获取字符串。这会因为浏览器的原生 HTML 解析行为而导致一些小问题。

TIP

应该注意的是,下面讨论的限制仅适用于直接在 DOM 中编写模板的情况。它们不适用于以下来源的字符串模板:

  • 字符串模板 (比如 template: '...')
  • 单文件组件
  • <script type="text/x-template">

元素位置受限

有些 HTML 元素,诸如 <ul><ol><table><select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li><tr><option>,只能出现在其它某些特定的元素内部。

1
2
3
<table>
<blog-post-row></blog-post-row>
</table>

这个自定义组件 <blog-post-row> 会被作为无效的内容提升到外部,并导致最终渲染结果出错。我们可以使用特殊的 is attribute 作为一个变通的办法:

1
2
3
<table>
<tr is="vue:blog-post-row"></tr>
</table>

大小写不敏感

另外,HTML attribute 名不区分大小写,因此浏览器将所有大写字符解释为小写。这意味着当你在 DOM 模板中使用时,驼峰 prop 名称和 event 处理器参数需要使用它们的 kebab-cased (横线字符分隔) 等效值:

1
2
3
4
5
6
7
8
//  在 JavaScript 中是驼峰式

app.component('blog-post', {
props: ['postTitle'],
template: `
<h3>{{ postTitle }}</h3>
`
})
1
2
3
<!-- 在 HTML 中则是横线字符分割 -->

<blog-post post-title="hello!"></blog-post>