组件可以将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构
1 组件组成
TemplateDemo.vue
<template>
<div class="container">
{{ msg }}
</div>
</template>
<script>
export default {
data(){
return {
msg: 'Hello World'
}
}
}
</script>
<style scoped>
.container{
font-size: 20px;
color: blue;
}
</style>
2 组件引用
组件引用共有三个步骤:
- 用import引入组件
- 用注入组件
- 显示组件
<template>
<!-- 显示组件 -->
<TemplateDemo />
</template>
<script>
// 引入组件
import TemplateDemo from './components/TemplateDemo.vue'
// 注册组件
export default {
components: {
TemplateDemo
}
}
</script>
<style>
</style>
3 全局注册与局部注册
一个 Vue 组件在使用前需要先被“注册”,这样 Vue 才能在渲染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册。
3.1 全局注册
可以在main.js
中创建app对象后使用 app.component()
方法,让组件在当前 Vue 应用中全局可用。
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import TemplateDemo from './components/TemplateDemo.vue'
const app = createApp(App)
// 全局注册
app.component('TemplateDemo', TemplateDemo)
// 全局样式
app.mount('#app')
app.component()
方法可以被链式调用:
app
.component('ComponentA', ComponentA)
.component('ComponentB', ComponentB)
.component('ComponentC', ComponentC)
局部注册
全局注册虽然很方便,但有以下几个问题:
- 全局注册,但并没有被使用的组件无法在生产打包时被自动移除 (也叫“tree-shaking”)。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。
- 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性。
相比之下,局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确,并且对 tree-shaking 更加友好。
在使用 <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
}
// ...
}
4 组件传递数据 prop
组件与组件之间不是完全独立的,而是有交集的,那就是组件与组件之间是可以传递数据的,传递数据的解决方案就是 props
要传递给子组件传递一个数据(比如,标题),必须在组件的 props
列表上声明它,也可以使用 defineProps
宏(当在script
标签中使用了setup
):
<!-- 子组件.vue -->
<template>
<h4>{{ title }}</h4>
</template>
<script setup>
defineProps(['title'])
</script>
如果没有使用 <script setup>
,props 必须以 props
选项的方式声明:
export default {
props: ['title'],
}
当一个 prop 被注册后,可以像这样以自定义 attribute 的形式传递数据给它:
<Child title="My journey with Vue" />
<Child title="Blogging with Vue" />
<Child title="Why Vue is so fun" />
props
只能从父组件传递给子组件
props
是只读的
4.1 动态prop
可以使用 v-bind
或缩写 :
来进行动态绑定的 props
<Child :title="title" />
4.2 适用对象绑定多个prop
如果想要将一个对象的所有属性都当作 props 传入,可以使用没有参数的 v-bind
,即只使用 v-bind
,例如这里有一个对象post
const post = {
id: 1,
title: 'My Journey with Vue'
}
以及下面的模板:
<Child v-bind="post" />
这等价于:
<Child :id="post.id" :title="post.title" />
4.3 Prop校验
要声明对 props 的校验,需要向 defineProps()
宏提供一个带有 props 校验选项的对象,也可以直接在props
中提供校验对象:
defineProps({
// 基础类型检查
// (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或数组的默认值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个
// 工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
})
5. 组件事件
在组件的模板表达式中,可以直接使用 $emit
方法触发自定义事件 (例如:在 v-on
的处理函数中),一般用于组件之间传递数据:
<!-- Child -->
<button @click="$emit('someEvent')">click me</button>
父组件可以通过 v-on
(缩写为 @
) 来监听事件:
<!-- Parent -->
<Child @some-event="callback" />
6. 透传Attributes
“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on
事件监听器。最常见的例子就是 class
、style
和 id
。
当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上。
如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false
。
<script setup>
defineOptions({
inheritAttrs: false
})
</script>
网友评论