前言
Vue3的组件学习笔记
HTML
挂载组件
- 每个JS对象都可以被定义为一个Vue组件
- 每个组件可以通过
template属性定义EJS模板,如果传递的是选择器,可以将选中的模板作为当前组件的EJS模板
- 注册组件时,第一个参数可以是小驼峰命名、大驼峰命名、连字符命名、下划线命名,这个命名不会直接作为组件名,而是会被Vue内部做一次映射
- 使用组件时
.html文件:由于HTML不区分大小写,所以只能使用连字符命名
.vue文件:可以使用大驼峰命名或连字符命名,推荐注册组件和使用组件时都使用大驼峰命名
.jsx文件:由于JSX中会将连字符解析为减号,所以只能使用大驼峰命名
- 使用组件时,既可以使用单标签引入,也可以使用双标签引入
挂载为局部组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <div id="app"> <template id="customComponent"></template>
<custom-component> </div>
<script> const customComponent = { template: "#customComponent" };
const root = { components: { CustomComponent: customComponent } }; const app = Vue.createApp(root);
app.mount("#app"); </script>
|
挂载为全局组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <div id="app"> <template id="customComponent"></template>
<custom-component> </div>
<script> const root = {}; const app = Vue.createApp(root);
const customComponent = { template: "#customComponent" };
app.component("custom-component", customComponent);
app.mount("#app"); </script>
|
OptionsAPI
模板标签
- 在渲染模板时,不会渲染模板标签(
<template></template>)的层级,而是将模板标签内的所有子标签渲染在页面
1 2 3 4 5 6 7 8 9 10
| <div id="app"> <template> <div></div> </template> </div>
<script> const app = Vue.createApp(); app.mount("#app"); </script>
|
挂载组件
挂载为局部组件
- 定义子组件
src/component/Son.vue1 2 3 4 5 6 7
| <template></template>
<script> export default { ... } </script>
|
- 父组件引入子组件
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <template> <!-- 3. 使用组件 --> <Son></Son> <Son /> </template>
<script> // 1. 引入子组件 import Son from "@/components/Son.vue" export default { // 2. 注册局部组件 components: { Son } } </script>
|
父子组件数据传递
父组件传递数据给子组件
- 子组件接收父组件可能传递的数据
type:定义数据类型
require:定义是否必填
true:必填
false:缺省值,不必填
default:定义默认值,如果数据类型是字符串或数值,则直接定义默认值;如果数据类型是对象,则需要定义一个返回对象的函数;如果数据类型是数组,则需要定义一个返回数组的函数;如果数据类型是函数,则需要定义一个工厂函数
validator:定义数据校验函数,返回值true表示通过校验,返回false表示不通过校验
src/component/Son.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <template> <!-- 模板中使用父组件可能传递的数据 --> {{ key1 }} {{ key2 }} </template>
<script> export default { // 定义父组件可能传递的数据 props: { key1: { type: String, require: true, default: "value", validator: function (value) { return true; } }, key2: { type: Object, default: function () { return {}; } }, key3: { type: Array, default: function () { return []; } }, key4: { type: Function, default: function () { return "value"; } }, key5: Number } } </script>
|
src/component/Son.vue1 2 3 4 5 6 7 8 9 10 11 12
| <template> <!-- 模板中使用父组件可能传递的数据 --> {{ key1 }} {{ key2 }} </template>
<script> export default { // 定义父组件可能传递的数据 props: ["key1", "key2"] } </script>
|
- 父组件引入子组件时传递数据
- 父组件传递数据给子组件时,通过在引入子组件时传递数据,键名可以是小驼峰命名或连字符命名,都可以在子组件中通过小驼峰命名接收数据
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <!-- 引入子组件时传递数据 --> <Son key1="value" v-bind:key2="0" keyThree="" key-four="" /> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son } } </script>
|
非prop的attribute
- 如果父组件传递数据给子组件时,子组件没有通过
props进行接收,这些属性被称为非prop的attribute,这些属性会默认添加在子组件的根元素上
src/component/Son.vue1 2 3 4
| <template> <!-- 被父组件添加的非props的attribute会默认添加在根元素上 --> <div key="value"></div> </template>
|
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <!-- 引入子组件时传递数据 --> <Son key="value" /> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son } } </script>
|
禁用子组件的非prop的attribute的自动添加
- 在子组件中通过
inheritAttrs: false可以禁用非prop的attribute的自动添加
src/component/Son.vue1 2 3 4 5
| <script> export default { inheritAttrs: false } </script>
|
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <!-- 引入子组件时传递数据 --> <Son key="value" /> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son } } </script>
|
子组件获取所有非prop的attribute
- 在子组件中通过
$attrs获取所有非prop的attribute
src/component/Son.vue1 2 3 4
| <template> <!-- 获取非prop的attribute --> <div v-bind:sonKey="$attrs.fatherKey"></div> </template>
|
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <!-- 引入子组件时传递数据 --> <Son fatherKey="value" /> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son } } </script>
|
子组件传递事件给父组件
- 子组件发送事件给父组件
src/component/Son.vue1 2 3 4 5 6 7 8 9 10
| <script> export default { // 1. 定义事件 emits: ["event"], created: function () { // 2. 发送事件 this.$emit("event", "payload") } } </script>
|
- 定义事件时对参数进行验证
- 返回true表示通过验证,返回false表示未通过验证
src/component/Son.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script> export default { // 1. 定义事件,并对参数进行验证 emits: { event: function (payload) { return true; } }, created: function () { // 2. 发送事件 this.$emit("event", "payload") } } </script>
|
- 父组件监听子组件发送的事件
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <!-- 监听子组件发送的事件 --> <Son v-on:event="fn" /> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son }, methods: { fn: function (payload) { ... } } } </script>
|
组件双向绑定
- 子组件接收父组件可能传递的数据
src/component/Son.vue1 2 3 4 5 6 7 8 9 10 11
| <template> <!-- 模板中使用父组件可能传递的数据 --> {{ modelValue }} </template>
<script> export default { // 定义父组件可能传递的数据 props: ["modelValue"] } </script>
|
- 父组件引入子组件时传递数据
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <!-- 引入子组件时传递数据 --> <Son v-model:modelValue="key" /> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son }, data: function () { return { key: "value" } } } </script>
|
- 简写,
v-model当作v-model:modelValue使用
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <!-- 引入子组件时传递数据 --> <Son v-model="key" /> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son }, data: function () { return { key: "value" } } } </script>
|
本质
- 本质是父组件传递数据给子组件,子组件传递事件给父组件,并通过事件函数的参数携带数据给父组件
src/component/Son.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> {{ modelValue }} </template>
<script> export default { props: ["modelValue"], emits: ["update:modelValue"], watch: { modelValue(newVal) { this.$emit("update:modelValue", newVal); } }, } </script>
|
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <Son v-bind:modelValue="key" v-on:update:modelValue="key = $event" /> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son }, data: function () { return { key: "value" } } } </script>
|
插槽
- 子组件预留插槽插入位置
name:指定插槽名称
default:缺省值,默认插槽名称
src/component/Son.vue1 2 3 4 5 6 7
| <template> <!-- 预留插槽插入位置 --> <slot name="slotName"> <!-- 默认元素 --> <div></div> </slot> </template>
|
- 父组件引入子组件时向插槽插入元素
:slotName:指定插槽名称
:default:缺省值,默认插槽名称
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <Son> <!-- 引入子组件时向指定名称的插槽插入元素 --> <template v-slot:slotName> <div></div> </template> </Son> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son } } </script>
|
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <Son> <!-- 引入子组件时向指定名称的插槽插入元素 --> <template #slotName> <div></div> </template> </Son> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son } } </script>
|
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <Son> <!-- 引入子组件时向默认名称的插槽插入元素 --> <template v-slot:default> <div></div> </template> </Son> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son } } </script>
|
- 如果只有一个插槽且是默认插槽,则可以省略
<template></template>
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <Son> <!-- 引入子组件时向默认名称的插槽插入元素 --> <div></div> </Son> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son } } </script>
|
异步插槽
- 如果插槽内传递的组件是异步加载的,在加载过程中可以通过
fallback定义等待时的默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <Son> <suspense> <template v-slot:default> <async-component></async-component> </template> <template v-slot:fallback> <div>Loading...</div> </template> </suspense> </Son> </template>
<script> import { defineAsyncComponent } from "vue"
export default { components: { AsyncComponent: defineAsyncComponent(() => import("./component/AsyncComponent.vue")) } } </script>
|
动态指定插槽名称
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <Son> <!-- 引入子组件时向指定名称的插槽插入元素 --> <template v-slot[key]> <div></div> </template> </Son> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son }, data: function () { return { key: "slotName" } } } </script>
|
插槽的参数传递
src/component/Son.vue1 2 3 4
| <template> <!-- 预留插槽插入位置,并发送数据 --> <slot v-bind:key="'value'"></slot> </template>
|
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <template> <Son> <!-- 引入子组件时向插槽插入元素,并接收数据 --> <template v-slot:default="args"> <div> <!-- 使用参数 --> {{ args.key }} </div> </template> </Son> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son } } </script>
|
任意层级组件数据传递
- 发送数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <script> import { computed } from "vue" export default { provide: function () { return { key1: "value", key2: { k: "v" }, key3: computed(() => { return "value" }) }; } } </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script> export default { provide: { key1: "value", key2: { k: "v" }, key3: computed(() => { return "value" }) } } </script>
|
- 接收数据
1 2 3 4 5 6 7 8 9 10 11
| <template> {{ key1 }} {{ key2.k }} {{ key3 }} </template>
<script> export default { inject: ["key1", "key2", "key3"] } </script>
|
通过JS获取组件的原生DOM对象
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <template> <div ref="div"></div> <Son ref="son" /> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son }, mounted: function () { // 获取子元素 console.log(this.$refs.div); // 获取子组件 console.log(this.$refs.son); // 获取子组件的属性 console.log(this.$refs.son.key); // 执行子组件的方法 this.$refs.son.method(); // 获取子组件的DOM根元素 console.log(this.$refs.son.$el); // 获取父组件 console.log(this.$parent); // 获取根组件 console.log(this.$root); } } </script>
|
动态组件
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <component is="son"></component> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son } } </script>
|
保持子组件不自动销毁
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <keep-alive> <Son /> </keep-alive> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son } } </script>
|
包含名单和排除名单
include:定义包含名单,组件名在白名单内的组件不会被自动销毁
exclude:定义排除名单,组件名在白名单内的组件会被自动销毁
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <keep-alive include="son" exclude="daughter"> <Son /> <Daughter /> </keep-alive> </template>
<script> import Son from "@/components/Son.vue" import Daughter from "@/components/Daughter.vue" export default { components: { Son, Daughter } } </script>
|
名单中定义多个组件名
通过逗号分隔
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <keep-alive include="son,daughter"> <Son /> <Daughter /> </keep-alive> </template>
<script> import Son from "@/components/Son.vue" import Daughter from "@/components/Daughter.vue" export default { components: { Son, Daughter } } </script>
|
通过正则表达式
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <keep-alive v-bind:include="/son|daughter/"> <Son /> <Daughter /> </keep-alive> </template>
<script> import Son from "@/components/Son.vue" import Daughter from "@/components/Daughter.vue" export default { components: { Son, Daughter } } </script>
|
通过数组
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <keep-alive v-bind:include="['son', 'daughter']"> <Son /> <Daughter /> </keep-alive> </template>
<script> import Son from "@/components/Son.vue" import Daughter from "@/components/Daughter.vue" export default { components: { Son, Daughter } } </script>
|
监听子组件的活跃状态
状态变为活跃时的回调函数
src/component/Son.vue1 2 3 4 5
| <script> export default { name: "son" } </script>
|
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <keep-alive> <Son /> </keep-alive> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son }, activated: function () { ... } } </script>
|
状态变为不活跃时的回调函数
src/component/Son.vue1 2 3 4 5 6 7
| <template></template>
<script> export default { name: "son" } </script>
|
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <keep-alive> <Son /> </keep-alive> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son }, deactivated: function () { ... } } </script>
|
CompositionAPI
父子组件数据传递
父组件传递数据给子组件
- 子组件接收父组件可能传递的数据
type:定义数据类型
require:定义是否必填
true:必填
false:缺省值,不必填
default:定义默认值,如果数据类型是字符串或数值,则直接定义默认值;如果数据类型是对象,则需要定义一个返回对象的函数;如果数据类型是数组,则需要定义一个返回数组的函数;如果数据类型是函数,则需要定义一个工厂函数
validator:定义数据校验函数,返回值true表示通过校验,返回false表示不通过校验
src/component/Son.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <template> <!-- 模板中使用父组件可能传递的数据 --> {{ key1 }} {{ key2 }} </template>
<script setup> // 定义父组件可能传递的数据 const props = defineProps({ key1: { type: String, require: true, default: "value", validator: function (value) { return true; } }, key2: { type: Object, default: function () { return {}; } }, key3: { type: Array, default: function () { return []; } }, key4: { type: Function, default: function () { return "value"; } }, key5: Number }) </script>
|
- 父组件传递数据给子组件
- 父组件传递数据给子组件时,通过在引入子组件时传递数据,键名可以是小驼峰命名或连字符命名,都可以在子组件中通过小驼峰命名接收数据
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <!-- 引入子组件时传递数据 --> <Son key1="value" v-bind:key2="0" keyThree="" key-four="" /> </template>
<script> import Son from "@/components/Son.vue" export default { components: { Son } } </script>
|
子组件传递事件给父组件
- 子组件发送事件给父组件
src/component/Son.vue1 2 3 4 5 6 7
| <script setup> // 1. 定义事件 const emits = defineEmits(["event"]); // 2. 发送事件 emits("event", "payload"); </script>
|
- 父组件监听子组件发送的事件
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12
| <template> <!-- 监听子组件发送的事件 --> <Son v-on:event="fn" /> </template>
<script setup> import Son from "@/components/Son.vue"
function fn(payload) { ... } </script>
|
任意层级组件数据传递
- 发送数据
1 2 3 4 5 6 7 8 9 10 11
| <template></template>
<script> import { provide } from "vue" export default { setup: function () { provide("key", "value"); } } </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <template></template>
<script> import { provide, ref } from "vue" export default { setup: function () { const key = ref("value"); provide("key", key); } } </script>
|
- 接收数据
- 通过CompositionAPI接收的数据,在CompositionAPI中使用会自动解包
default:默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> {{ key }} </template>
<script> import { inject } from "vue" export default { setup: function () { const key = inject("key", "default"); console.log(key); } } </script>
|
- 通过OptionsAPI接收的数据,在CompositionAPI中使用时仍需手动解包
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> {{ key }} </template>
<script> export default { inject: ["key"], methods: { fn: function () { console.log(key.value); } } } </script>
|
暴露
- 子组件暴露变量和函数给父组件
src/component/Son.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script setup> import { ref } from "vue" const key = ref("value"); function fn() { ... } defineExpose({ key: key, fn: fn }); </script>
|
- 父组件调用子组件暴露的变量和函数
src/component/Father.vue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <Son ref="son" /> </template>
<script setup> import Son from "@/components/Son.vue" import { ref, onMounted } from "vue"
const sonComponent = ref(null); onMounted(() => { console.log(sonComponent.value); });
console.log(sonComponent.value.key); sonComponent.value.fn(); </script>
|
完成