组件允许将UI划分为独立可重用的部分,并且可以对每个部分进行单独的处理。在实际应用中,组件常常被组织成一个层层嵌套的树状结构:

定义组件

使用构建步骤

当使用构建步骤时,一般会将Vue组件定义在一个单独的.vue文件中,这被叫做单文件组件(SFC)

<script setup>
import {ref} from 'vue'

const count = ref(0)
</script>

<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>

不使用构建步骤

当不使用构建步骤时,一个Vue组件以一个包含Vue特定选项的Javascript对象来定义:

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

这个例子定义了一个组件,并在一个.js文件里面默认导出了它自己,也可以通过具名导出在一个文件中导出多个组件。

这里的模板是一个内联的Javascript字符串,Vue将在运行时编译它。也可以使用ID选择器来指向一个元素(通常是原生的<template>元素),Vue将会使用其内容作为模板来源。

组件使用

要使用一个子组件,需要在父组件中导入它。假设把计数器组件放在一个叫做ButtonCounter.vue的文件中,这个组件将会以默认导出的的形式被暴露给外部。

<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
</template>

通过<script setup>导入的组件都可以在模板中直接使用。这称之为局部注册。

当然也可以全局注册一个组件,这样它在当前应用中的任何组件上都可以使用,而不需要额外再导入。

组件可以被重用任意多次:

<h1>Here is a child component!</h1>
<ButtonCounter/>
<ButtonCounter/>
<ButtonCounter/>

经过测试可以发现,每当点击这些按钮时,每个组件都维护着自己的状态,是不同的count。这是因为每当使用一个组件,就创建了一个新的实例

组件标签名注意事项⚠️

  1. 在单文件组件中,推荐为子组件使用PascalCase标签名,以此来区分原生HTML元素。虽然原生HTML标签名是不区分大小写的,但是Vue单文件组件是可以在编译中区分大小写的。可以使用/>来关闭一个标签。

  2. 如果是直接在DOM中书写模板,那么模板的编译需要遵从浏览器中HTML的解析行为。在这种情况下,应该使用kebab-case形式并显式的关闭这些组件的标签。

<!-- 如果是在 DOM 中书写该模板 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>

组件注册

一个Vue组件在使用前需要先被注册,这样Vue才能在渲染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册。

全局注册

可以使用Vue实例对象.component()方法,让组件在当前Vue应用中全局可用。

import { createApp } from 'vue'

const app = createApp({})

app.component(
//注册的名字
'MyComponent',
//组件的实现
{
/*...*/
}
)

如果是使用单文件组件,可以注册被导入的.vue文件:

import MyComponent from './App.vue'

app.component('MyComponent', MyComponent)

.component()方法可以被链式调用

app
.component('ComponentA', ComponentA)
.component('ComponentB', ComponentB)
.component('ComponentC', ComponentC)

全局注册的组件可以在此应用的任意组件的模板中使用:

<!-- 这在当前应用的任意组件中都可用 -->
<ComponentA/>
<ComponentB/>
<ComponentC/>

所有的子组件也可以使用全局注册的组件,这意味着这三个组件也可以在彼此内部使用。

局部注册

在使用<script setup>的单文件组件中,导入的组件可以直接在模板中使用,无需注册:

<script setup>
import ComponentA from './ComponentA.vue'
</script>

<template>
<ComponentA />
</template>

如果没有使用<script setup>,则需要使用components选项来显式注册:

import ComponentA from './ComponentA.js'

export default {
components: {
ComponentA
},
setup() {
//...
}
}

对于每个components对象里的属性,它们的Key名就是注册的朱建明,而值就是相应组件的实现。上面的例子等价于:

export default {
components: {
ComponentA: ComponentA
},
//...
}

注意点⚠️:局部注册的组件在后代组件中不可用。在这个例子中,ComponentA注册后仅在当前组件可用,而在任何的子组件或更深层的子组件中都不可用。

全局注册,局部注册的区别

  1. 全局注册,但并没有被使用的组件无法在生产打包时被自动移除(也叫”tree-shaking”)。如果全局注册了一个组件,即使它没有被实际使用,它仍然会出现在打包后的js文件中。
  2. 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性。
  3. 局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。这样会让组件之间的依赖关系更加明确。
  4. 局部注册的组件对tree-shaking更加友好。