组件通信
组件关系 | 传递方式 |
---|---|
父传子 | prop |
v-model | |
$refs | |
默认插槽、具名插槽 | |
子传父 | prop |
自定义事件 | |
v-model | |
$parent | |
作用域插槽 | |
祖传孙、孙传祖 | $attrs |
provide 、inject | |
兄弟间、任意组件间 | mitt |
pinia |
props
父→子
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div class="father">
<h3>父组件</h3>
<h4>计数:{{ count }}</h4>
<Child :count="count" />
</div>
</template>
<script setup lang="ts">
import Child from './Child.vue';
import { ref } from 'vue';
let count = ref(1);
</script>
1
2
3
4
5
6
7
8
9
10
11
<template>
<div class="child">
<h3>子组件</h3>
<h4>父给的计数:{{ count }}</h4>
</div>
</template>
<script setup lang="ts">
// 声明接收props
defineProps(['count']);
</script>
子→父
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="father">
<h3>父组件</h3>
<h4>计数:{{ count }}</h4>
<Child :addCount="addCount" />
</div>
</template>
<script setup lang="ts">
import Child from './Child.vue';
import { ref } from 'vue';
let count = ref(1);
function addCount() {
count.value++;
}
</script>
1
2
3
4
5
6
7
8
9
10
11
<template>
<div class="child">
<h3>子组件</h3>
<button @click="addCount">增加父组件计数</button>
</div>
</template>
<script setup lang="ts">
// 声明接收props
defineProps(['addCount']);
</script>
自定义事件
常用于:子→父
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div class="father">
<h3>父组件</h3>
<h4>计数:{{ count }}</h4>
<Child @add-count="count++" />
</div>
</template>
<script setup lang="ts">
import Child from './Child.vue';
import { ref } from 'vue';
let count = ref(1);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div class="child">
<h3>子组件</h3>
<button @click="addCount">增加父组件计数</button>
</div>
</template>
<script setup lang="ts">
const emits = defineEmits(['add-count']);
function addCount() {
emits('add-count');
}
</script>
mitt.js
与消息订阅与发布(pubsub
)功能类似,可以实现任意组件间通信。
安装
1
npm i mitt
创建
新建文件:src\utils\emitter.ts
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
// 引入mitt
import mitt from 'mitt';
// 调用mitt得到emitter,emitter能:绑定事件、触发事件
const emitter = mitt();
/*
// 绑定事件
emitter.on('test', () => {
console.log('test被调用了');
});
// 触发事件
setInterval(() => {
emitter.emit('test');
}, 1000);
// 清理事件
setTimeout(() => {
emitter.off('test');
// emitter.all.clear(); // 清空所有事件
}, 3000);
*/
// 暴露emitter
export default emitter;
绑定事件
接收数据的组件中:绑定事件、同时在销毁前解绑事件:
1
2
3
4
5
6
7
8
9
10
11
12
import emitter from '@/utils/emitter';
import { onUnmounted } from 'vue';
// 绑定事件
emitter.on('add-count', value => {
count.value += value as number;
console.log('add-count事件被触发', value);
});
onUnmounted(() => {
emitter.off('add-count'); // 解绑事件
});
触发事件
1
2
3
4
import emitter from '@/utils/emitter';
const addCount = () => {
emitter.emit('add-count', 3);
};
v-model
实现父与子相互通信
v-model 本质
1
2
3
4
5
<!-- 使用v-model指令 -->
<input type="text" v-model="userName" />
<!-- v-model的本质是下面这行代码 -->
<input type="text" :value="userName" @input="userName = (<HTMLInputElement>$event.target).value" />
组件标签中 v-model
1
2
3
4
5
<!-- 组件标签上使用v-model指令 -->
<MyInput v-model="username" />
<!-- 组件标签上v-model的本质 -->
<MyInput :modelValue="username" @update:modelValue="username = $event" />
MyInput
组件中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div class="box">
<!--将接收的value值赋给input元素的value属性,目的是:为了呈现数据 -->
<!--给input元素绑定原生input事件,触发input事件时,进而触发update:model-value事件-->
<input type="text" :value="modelValue" @input="emit('update:model-value', (<HTMLInputElement>$event.target).value)" />
</div>
</template>
<script setup lang="ts">
// 接收props
defineProps(['modelValue']);
// 声明事件
const emit = defineEmits(['update:model-value']);
</script>
更换 value
1
2
3
4
5
<!-- 也可以更换value,例如改成user-->
<MyInput v-model:user="username" />
<!-- 上面代码的本质如下 -->
<MyInput :user="username" @update:abc="username = $event" />
MyInput
组件中:
1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div class="box">
<input type="text" :value="user" @input="emit('update:user', (<HTMLInputElement>$event.target).value)" />
</div>
</template>
<script setup lang="ts">
// 接收props
defineProps(['user']);
// 声明事件
const emit = defineEmits(['update:user']);
</script>
组件使用多个 v-model
1
<MyInput v-model:user="username" v-model:pw="password" />
attrs
$attrs
用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。
$attrs
是一个对象,含有没有被该组件声明为 props
或 emits
的 attribute
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="father">
<h3>父组件</h3>
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<Child :a="a" :b="b" v-bind="{ x: 100, y: 200 }" :updateA="updateA" />
</div>
</template>
<script setup lang="ts">
import Child from './Child.vue';
import { ref } from 'vue';
const a = ref(1);
const b = ref(2);
function updateA(value: number) {
a.value += value;
}
</script>
1
2
3
4
5
6
7
8
9
10
<template>
<div class="child">
<h3>子组件</h3>
<GrandChild v-bind="$attrs" />
</div>
</template>
<script setup lang="ts">
import GrandChild from './GrandChild.vue';
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div class="grand-child">
<h3>孙组件</h3>
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<h4>x:{{ x }}</h4>
<h4>y:{{ y }}</h4>
<button @click="updateA(6)">更新爷爷的a</button>
</div>
</template>
<script setup lang="ts">
defineProps(['a', 'b', 'x', 'y', 'updateA']);
</script>
$refs、\$parent
$refs
用于 :父→子$parent
用于:子→父
属性 | 说明 |
---|---|
$refs | 值为对象,包含所有被ref 属性标识的DOM 元素或组件实例。 |
$parent | 值为对象,当前组件的父组件实例对象。 |
具体使用
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
<template>
<div class="father">
<h3>父组件</h3>
<h4>计数:{{ count }}</h4>
<button @click="changeChild1">修改Child1的数据</button>
<button @click="changeChild2">修改Child2的数据</button>
<button @click="changeAllChild($refs)">让所有孩子的数据改变</button>
<Child1 ref="c1" />
<Child2 ref="c2" />
</div>
</template>
<script setup lang="ts">
import Child1 from './Child1.vue';
import Child2 from './Child2.vue';
import { ref, reactive } from 'vue';
const c1 = ref();
const c2 = ref();
const count = ref(4);
function changeChild1() {
c1.value.count += 1;
}
function changeChild2() {
c2.value.count += 2;
}
function changeAllChild(refs: { [key: string]: any }) {
// console.log(refs);
for (const key in refs) {
refs[key].count += 10;
}
}
// 向外部提供数据
defineExpose({ count });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="child1">
<h3>子组件1</h3>
<h4>数据:{{ count }}</h4>
<button @click="minusCount($parent)">修改Father的数据</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const count = ref(3);
function minusCount(parent: any) {
parent.count -= 1;
}
// 把数据交给外部
defineExpose({ count });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="child2">
<h3>子组件2</h3>
<h4>数据:{{ count }}</h4>
<button @click="minusCount($parent)">修改Father的数据</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const count = ref(3);
function minusCount(parent: any) {
parent.count -= 2;
}
// 把数据交给外部
defineExpose({ count });
</script>
provide、inject
实现对后代组件通信
- 在祖先组件中通过
provide
配置向后代组件提供数据 - 在后代组件中通过
inject
配置来声明接收数据
父组件提供数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="father">
<h3>父组件</h3>
<h4>计数:{{ count }}</h4>
<Child />
</div>
</template>
<script setup lang="ts">
import Child from './Child.vue';
import { ref, reactive, provide } from 'vue';
const count = ref(100);
function updateCount(value: number) {
count.value -= value;
}
// 向后代提供数据
provide('countContext', { count, updateCount });
</script>
后代组件注入数据
1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div class="grand-child">
<h3>我是孙组件</h3>
<h4>计数:{{ count }}</h4>
<button @click="updateCount(6)">更新count</button>
</div>
</template>
<script setup lang="ts">
import { inject } from 'vue';
const { count, updateCount } = inject('countContext', { count: 0, updateCount: (param: number) => {} });
</script>
pinia
参考 pinia
文章
slot
默认插槽
父组件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="father">
<h3>父组件</h3>
<div class="content">
<Category title="title">
<ul>
<li v-for="item in data" :key="item.id">{{ item.name }}</li>
</ul>
</Category>
<Category title="other">
<a href="">更多</a>
</Category>
</div>
</div>
</template>
子组件:
1
2
3
4
5
6
<template>
<div class="category">
<h2>{{ title }}</h2>
<slot>默认内容</slot>
</div>
</template>
具名插槽
父组件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div class="father">
<h3>父组件</h3>
<div class="content">
<Category>
<template v-slot:slot2>
<ul>
<li v-for="item in data" :key="item.id">{{ item.name }}</li>
</ul>
</template>
<!-- # 也可以代替 v-slot: -->
<template #slot1>
<h2>更多</h2>
</template>
</Category>
</div>
</div>
</template>
子组件:
1
2
3
4
5
6
<template>
<div class="category">
<slot name="slot1">默认内容1</slot>
<slot name="slot2">默认内容2</slot>
</div>
</template>
作用域插槽
- 数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。
父组件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div class="father">
<h3>父组件</h3>
<div class="content">
<Game>
<template v-slot="params">
<ol>
<li v-for="item in params.data" :key="item.id">
{{ item.name }}
</li>
</ol>
</template>
</Game>
<Game>
<!-- 也可以写成 <template #default="data"></template> -->
<!-- default 换成别的名称就是具名作用域插槽 -->
<template #default="{ data }">
<h3 v-for="item in data" :key="item.id">{{ item.name }}</h3>
</template>
</Game>
</div>
</div>
</template>
子组件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div class="Category">
<slot :data="data" x="1" y="2"></slot>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
let data = reactive([
{ id: 'asgytdfats01', name: '张三' },
{ id: 'asgytdfats02', name: '李四' },
{ id: 'asgytdfats03', name: '王五' },
]);
</script>
组件通信
http://xiaowhang.github.io/archives/1162395221/