【笔记】Vue3的组件

前言

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">
<!-- 1. 定义组件的EJS模板 -->
<template id="customComponent"></template>

<!-- 4. 使用组件 -->
<custom-component>
</div>

<script>
// 2. 定义一个组件对象
const customComponent = {
template: "#customComponent"
};

const root = {
// 3. 注册局部组件
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">
<!-- 1. 定义组件的EJS模板 -->
<template id="customComponent"></template>

<!-- 4. 使用组件 -->
<custom-component>
</div>

<script>
const root = {};

const app = Vue.createApp(root);

// 2. 定义一个组件对象
const customComponent = {
template: "#customComponent"
};

// 3. 注册全局组件
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>

挂载组件

挂载为局部组件

  1. 定义子组件
src/component/Son.vue
1
2
3
4
5
6
7
<template></template>

<script>
export default {
...
}
</script>
  1. 父组件引入子组件
src/component/Father.vue
1
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>

父子组件数据传递

父组件传递数据给子组件

  1. 子组件接收父组件可能传递的数据

type:定义数据类型
require:定义是否必填

true:必填
false:缺省值,不必填

default:定义默认值,如果数据类型是字符串或数值,则直接定义默认值;如果数据类型是对象,则需要定义一个返回对象的函数;如果数据类型是数组,则需要定义一个返回数组的函数;如果数据类型是函数,则需要定义一个工厂函数
validator:定义数据校验函数,返回值true表示通过校验,返回false表示不通过校验

src/component/Son.vue
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
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.vue
1
2
3
4
5
6
7
8
9
10
11
12
<template>
<!-- 模板中使用父组件可能传递的数据 -->
{{ key1 }}
{{ key2 }}
</template>

<script>
export default {
// 定义父组件可能传递的数据
props: ["key1", "key2"]
}
</script>
  1. 父组件引入子组件时传递数据
  • 父组件传递数据给子组件时,通过在引入子组件时传递数据,键名可以是小驼峰命名或连字符命名,都可以在子组件中通过小驼峰命名接收数据
src/component/Father.vue
1
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.vue
1
2
3
4
<template>
<!-- 被父组件添加的非props的attribute会默认添加在根元素上 -->
<div key="value"></div>
</template>
src/component/Father.vue
1
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.vue
1
2
3
4
5
<script>
export default {
inheritAttrs: false
}
</script>
src/component/Father.vue
1
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.vue
1
2
3
4
<template>
<!-- 获取非prop的attribute -->
<div v-bind:sonKey="$attrs.fatherKey"></div>
</template>
src/component/Father.vue
1
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>

子组件传递事件给父组件

  1. 子组件发送事件给父组件
src/component/Son.vue
1
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.vue
1
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>
  1. 父组件监听子组件发送的事件
src/component/Father.vue
1
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>

组件双向绑定

  1. 子组件接收父组件可能传递的数据
src/component/Son.vue
1
2
3
4
5
6
7
8
9
10
11
<template>
<!-- 模板中使用父组件可能传递的数据 -->
{{ modelValue }}
</template>

<script>
export default {
// 定义父组件可能传递的数据
props: ["modelValue"]
}
</script>
  1. 父组件引入子组件时传递数据
src/component/Father.vue
1
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.vue
1
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.vue
1
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.vue
1
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>

插槽

  1. 子组件预留插槽插入位置

name:指定插槽名称

default:缺省值,默认插槽名称

src/component/Son.vue
1
2
3
4
5
6
7
<template>
<!-- 预留插槽插入位置 -->
<slot name="slotName">
<!-- 默认元素 -->
<div></div>
</slot>
</template>
  1. 父组件引入子组件时向插槽插入元素

:slotName:指定插槽名称

:default:缺省值,默认插槽名称

src/component/Father.vue
1
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>
  • 简写,#当作v-slot:使用
src/component/Father.vue
1
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>
  • 默认插槽名称
    • 没有定义插槽名称时,默认插槽名称为default
src/component/Father.vue
1
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.vue
1
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.vue
1
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.vue
1
2
3
4
<template>
<!-- 预留插槽插入位置,并发送数据 -->
<slot v-bind:key="'value'"></slot>
</template>
src/component/Father.vue
1
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. 发送数据
  • 发送响应式数据时,需要配合计算属性发送响应式数据
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. 接收数据
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.vue
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
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.vue
1
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.vue
1
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.vue
1
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.vue
1
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.vue
1
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.vue
1
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.vue
1
2
3
4
5
<script>
export default {
name: "son"
}
</script>
src/component/Father.vue
1
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.vue
1
2
3
4
5
6
7
<template></template>

<script>
export default {
name: "son"
}
</script>
src/component/Father.vue
1
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

父子组件数据传递

父组件传递数据给子组件

  1. 子组件接收父组件可能传递的数据

type:定义数据类型
require:定义是否必填

true:必填
false:缺省值,不必填

default:定义默认值,如果数据类型是字符串或数值,则直接定义默认值;如果数据类型是对象,则需要定义一个返回对象的函数;如果数据类型是数组,则需要定义一个返回数组的函数;如果数据类型是函数,则需要定义一个工厂函数
validator:定义数据校验函数,返回值true表示通过校验,返回false表示不通过校验

src/component/Son.vue
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
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>
  1. 父组件传递数据给子组件
  • 父组件传递数据给子组件时,通过在引入子组件时传递数据,键名可以是小驼峰命名或连字符命名,都可以在子组件中通过小驼峰命名接收数据
src/component/Father.vue
1
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>

子组件传递事件给父组件

  1. 子组件发送事件给父组件
src/component/Son.vue
1
2
3
4
5
6
7
<script setup>
// 1. 定义事件
const emits = defineEmits(["event"]);

// 2. 发送事件
emits("event", "payload");
</script>
  1. 父组件监听子组件发送的事件
src/component/Father.vue
1
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. 发送数据
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>
  1. 接收数据
  • 通过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>

暴露

  • 子组件暴露变量和函数给父组件直接调用
  1. 子组件暴露变量和函数给父组件
src/component/Son.vue
1
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>
  1. 父组件调用子组件暴露的变量和函数
src/component/Father.vue
1
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>

完成