组件1
组件将所构建的网页划分为独立、可重用的部分,并且可以对每个部分进行单独的思考。
定义组件
此时,一般将 Vue 文件定义在一个单独的 .vue
文件中,这个文件叫做单文件组件(SFC)
一个 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>`
// (1)
}
- 也可以使用 ID 选择器指向一个元素(通常是原生
<template>
元素),将其作为模板(template: '#my-template-element'
)
使用组件
要使用子组件,可以直接通过 import
语句来导入,如果是选项式 API,需要在 components
选项中注册它。
组件注册2
组件名称
为了方便,Vue 支持将使用短横线连字符(kebab-case)命名解析为帕斯卡(PascalCase)命名,这意味着以 MyComponent
命名的组件,可以通过 <MyComponent>
或 <my-component>
引用。
传递 props
props 传递属于单向数据流,子组件不应修改父组件传递的 props,否则会在控制台中产生警告。
向组件传递数据的方法之一是 props,可以在组件上声明注册。一个组件可以接受任意数量的 props,默认情况下,所有 prop 都接受任意类型的值。
除了使用字符串数组来声明 prop 外,还可以使用对象的形式;其中,key 是 prop 的名称,而值是 prop 预期类型的构造函数:
也可以使用没有参数的 v-bind
,在传入的时候等价于绑定了其中所有的 prop。
prop 校验
Vue 组件可以声明对传入的 props 的校验要求,可通过带有 props 校验选项的对象提供验证规则。
props = {
propA: Number, // (1)
propB: [String, Number], // (2)
propC: {
type: String,
required: true // (3)
},
propD: {
type: Number,
default: 100 // (4)
},
propE: {
type: Object,
default: function (rawProps) {
return { message: 'hello' } // (5)
}
},
propF: {
validator: function (value) {
return ['success', 'warning', 'danger'].indexOf(value) !== -1 // (6)
}
},
propG: {
type: Function,
default: () => {} // (7)
}
}
- 基础类型检查(输出
null
或undefined
会跳过任何类型检查) - 多种可能类型
- required 指定该属性必须传递
- default 指定该属性的默认值
- 对象或数组的默认值必须从工厂函数返回
- 自定义类型校验函数
- 函数类型的默认值是一个用来作为默认值的函数
运行时类型检查
校验选项的 type
可以是下列原生构造函数中的一个:
String
Number
Boolean
Array
Object
Date
Function
Symbol
也可以是自定义的类或构造函数,Vue 会通过 instanceof
检查类型是否匹配
Boolean 转换
如果一个 prop 被声明为 Boolean
类型,则该组件可以这样使用:
<!-- 等同于传入 :disabled="true" -->
<MyComponent disabled />
<!-- 等同于传入 :disabled="false" -->
<MyComponent />
监听事件
子组件可以通过 emit
方式来向父组件发送消息,父组件可以通过 v-on
监听子组件的消息。
- 在选项式 API 中,可以通过
emits
选项声明需要抛出的事件 - 在组合式 API(script setup)中,可以通过
defineEmits
宏来声明需要抛出的事件,也可以通过setup()
函数的第二个参数中访问到emit
函数setup(props, ctx)
中ctx.emit()
通过插槽分配内容
- 要向组件中传递内容,可以使用 Vue 的自定义
<slot>
元素实现。它标示了父元素提供的插槽内容将在哪里被渲染。 - 插槽内容可以是任意合法的模板内容,不局限于文本。
- 插槽内容可以访问到父组件的数据作用域,但无法访问到子组件的数据。
具名插槽
- 一个组件中可以包含多个插槽出口,可以通过
name
attribute 来定义额外的插槽,用于给各个插槽分配唯一 ID: -
没有提供
name
的<slot>
出口会隐式命名为 default -
向内部传递时,需要使用
v-slot:
指令,并以插槽名作为参数,其中v-slot
可以简写为#
HTML<base-layout> <template v-slot:header> <h1>Here might be a page title</h1> </template> <!-- (1) --> <p>A paragraph for the main content.</p> <p>And another one.</p> <template v-slot:footer> <p>Here's some contact info</p> </template> </base-layout>
- 下面的两个
<p>
元素被隐式地视为默认插槽内容
- 下面的两个
动态插槽名
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
作用域插槽
某些场景下,插槽的内容可能需要同时使用父组件域和子组件域内的数据。这时,可以向一个插槽的出口上传递 attributes:
接受 props 时,子组件默认插槽可以使用标签上的 v-slot
指令接收到插槽 props 对象:
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
<!-- (1) -->
- 这里当然支持解构写法:
v-slot="{ text, count }"
子组件具名作用域插槽中的 props 可以作为 v-slot
指令的值被访问到:v-slot:name=slotProps
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
向上述模板传入这个 props:
在 headerProps 中会接收到 { message: 'hello' }
(name
是 Vue 保留的 attribute,不会作为 props 传递给插槽)
如果同时指定了具名插槽和默认插槽,需要为默认插槽使用显式的 <template>
标签
动态组件
有的场景需要在多个组件间切换,如 Tab 界面,这是通过 Vue 的 <component>
元素加 is
attribute 实现的:
被传给 :is
的值可以是:
- 注册的组件名称
- 导入的组件对象
当使用 :is
在多个组件间切换时,被切换的组件会被卸载。如果需要保持“存活”状态,则需要使用 <KeepAlive>
组件。
DOM 模板解析注意事项
- HTML 标签和属性名称是不分大小写的。在 DOM 中书写模板时,需要使用 kebab-case 形式,并显式地关闭组件的标签。
- 某些 HTML 元素对于放在其中的元素类型有限制,在使用此类限制元素的组件时会出现问题,这时需要使用
is
attribute,并使用前缀vue:
,避免与原生的自定义内置元素混淆。