Vue3 组件间相互访问(单文件 + <script setup> + 组合式 API 举例)

父组件传参数到子组件 - props

<script setup> 中用 defineProps() 声明组件的属性,在父组件模板中引用子组件时,就可以给通过属性给子组件传参数(可以是值、变量、函数、对象等)。

defineProps 是编译器宏,Vue3 编译器宏不需要导入,并且只能在 <script setup> 中使用。

ex: 子组件声明、调用属性

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- HelloWorld.vue 子组件-->
<script setup>
// 声明属性
defineProps([{
msg: String
}])
</script>

<template>
<div>
{{ msg }}
</div>
</template>

ex: 父组件使用属性

1
2
3
4
5
6
7
8
<!-- Demo.vue 父组件 -->
<script setup>
import HelloWorld from './HelloWorld.vue'
</script>

<template>
<HelloWorld msg="Welcome"/>
</template>

父组件传递事件到子组件

<script setup> 中用编译器宏 defineEmits() 声明组件提供的事件,并且触发事件后,在父组件引用子组件时就可以给子组件传递事件。

模板中用可直接用 $emit 变量触发事件,不需要用 defineEmits() 声明,但是实现触发事件的同时还需要做其他事情时,就要用 defineEmits()

ex: 子组件声明 & 触发事件

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
<!-- HelloWorld.vue 子组件-->
<script setup>
// 声明事件
const emits = defineEmits(['my-event-a', 'myEventB'])

const onEventA = () => {
// 触发事件
emits('my-event-a');
// more code
}
const onEventB = () => {
// 触发事件,并给事件传参
emits('my-event-b', '参数1', '参数2', '参数...');
// more code
}
</script>

<template>
<div>
<button @click="$emit('my-event-1')">$emit 触发事件</button>
<button @click="$emit('my-event-2', '参数1', '参数2')">$emit 触发事件并传参</button>
<button @click="onEventA">按钮a</button>
<button @click="onEventB">按钮b</button>
</div>
</template>

ex: 父组件传递事件到子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- Demo.vue 父组件 -->
<script setup>
import HelloWorld from './HelloWorld.vue';

const handle1 = () => {
console.log('hi')
}
const handle2 = (arg1, arg2) => {
console.log(arguments)
}
</script>

<template>
<HelloWorld msg="Welcome" @my-event-1="handle1" @my-event-2="handle2" @my-event-a="handle1" @my-event-b="handle2" />
</template>

父组件通过插槽给子组件分配内容

使用 <slot /> 作为一个占位符,父组件传递进来的内容就会渲染在这里。

1
2
3
4
5
6
7
<!-- AlertBox.vue 子组件 -->
<template>
<div class="alert-box">
<strong>This is an Error for Demo Purposes</strong>
<slot />
</div>
</template>
1
2
3
4
5
6
7
8
9
10
<!-- Demo.vue 父组件 -->
<script setup>
import AlertBox from './AlertBox.vue'
</script>

<template>
<AlertBox>
Something bad happened.
</AlertBox>
</template>

父组件访问子组件实例

父组件通过模板引用或者 $parent 链获取到的组件的公开实例,默认不会暴露任何在 <script setup> 中声明的绑定,我们可以通过 defineExpose 编译器宏来显式指定要暴露哪些绑定。

ex: 子组件显式暴漏绑定

1
2
3
4
5
6
7
8
9
10
11
12
<!-- Foo.vue -->
<script setup>
import { ref } from 'vue'

const a = 11
const b = ref(2)
const c = () => {
console.log('im foo')
}

defineExpose({ a, b, c })
</script>

当父组件通过模板引用的方式获取到当前组件的实例,获取到的实例会像这样 { a: number, b: number } (ref 会和在普通实例中一样被自动解包)

ex: 父组件通过模板引用访问子组件暴漏的绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- Demo.vue -->
<script setup>
import { ref } from 'vue';
import Foo from './Foo.vue';

// setup 中 用 ref(null) 声明跟变量名同名的模板引用
const fooRef = ref(); // <Foo ref="fooRef" />

const abc = () => {
// 通过模板引用获取实例暴变量值
// 注意:需要等到 mounted 后访问模板实例才有效
console.log(fooRef.value.a) // 11
fooRef.value.c()
}
</script>

<template>
<Foo ref="fooRef" />
<!-- 在模板中可以直接用 $refs 访问模板引用,不需要声明 -->
<p>{{ $refs['fooRef'].a }}</p>
</template>

子组件访问父组件

方式1 使用 defineExpose

父组件用 defineExpose() 显式暴露绑定后,在子组件的 <script setup> 中用 getCurrentInstance().parent.exposed 访问,在子组件的模板中用 $parent 访问。

推荐在子件的模板中调用父组件时使用该方式,在子组件的 <script setup> 中推荐用依赖注入更方便。

ex: 父组件引用子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- Demo.vue 父组件 -->
<script setup>
import { ref } from 'vue';
import Foo from './Foo.vue';

const a = 111
const bar = () => {
console.log('im done.')
}
defineExpose({ a, bar }) // 显式暴露 bar 绑定
</script>

<template>
<!-- 父组件引用子组件 -->
<Foo ref="fooRef" />
</template>

ex: 子组件访问父组件显式暴露的绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Foo.vue 子组件 -->
<script setup>
import { getCurrentInstance } from 'vue';
// 访问父组件 defineExpose() 暴漏的绑定
const parentExposed = getCurrentInstance().parent.exposed;
console.log(parentExposed.a)
parentExposed.bar()
</script>

<template>
<!-- 模板中直接用 $parent 就可以调用父组件暴漏的绑定 -->
{{ $parent.a }}
</template>

方式2 使用 Vue3 的依赖注入

provide() 在父组件提供依赖,用 inject() 在子组件 <script setup> 中注入。

setup 中访问父组件,使用依赖注入更便捷。
inject() 必须在组件的 setup() 阶段同步调用。

ex: 父组件提供依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Demo.vue 父组件 -->
<template>
<Foo />
</template>

<script setup>
import { provide } from 'vue'
import Foo from './Foo.vue';

provide('bar', () => {
console.log('Hello')
})
</script>

ex: 子组件注入依赖

1
2
3
4
5
6
<!-- Foo.vue 子组件 -->
<script setup>
import { inject } from 'vue';
const abc = inject('bar');
abc()
</script>

兄弟组件互相访问

兄弟组件加上 ref 属性后,通过父组件的 refs 就可以调用到兄弟组件用 defineExpose() 暴漏的绑定,

ex: 父组件显式暴漏绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- Demo.vue 父组件 -->
<template>
<Foo ref="fooRef" />
</template>

<script setup>
import Foo from './Foo.vue';

const a = 1;
const bar = () => {
console.log('im bar')
}

defineExpose({ a, bar })
</script>

ex: 子组件访问父组件暴漏的绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Foo.vue 子组件 -->
<script setup>
import { getCurrentInstance } from 'vue';
const parent = getCurrentInstance().parent; // 获取父组件的模板引用
parent.refs.fooRef.a // 调用暴漏的变量
parent.refs.fooRef.bar() // 调用暴漏的方法
</script>

<template>
<!-- 在模板中用预置的 $parent 访问覆膜板暴漏的绑定 -->
{{ $parent.$refs.fooRef.a }}
<button @click="$parent.$refs.fooRef.bar()">点我</button>
</template>

使用状态管理

比较复杂的应用,强烈推荐使用该方法。
我们可以用 响应式 API 做简单状态管理。

原理

根据 js 变量的作用域的特性,在一个单页面应用(SPA)中,如果有一个文件定义了全局变量,SPA 中任意一个文件 import 该文件,都会共享该变量。代码如下:

1
2
3
4
5
6
// test.js
let a1 = 0;
export function pr() {
++a1;
console.log(a1)
}

如果你有一部分状态需要在多个组件实例间共享,你可以使用 reactive()ref() 来创建一个响应式对象,并将它导入到多个组件中:

1
2
3
4
5
6
7
8
9
// todoListStore.js
import { ref } from 'vue';
export const todoList = ref([]);
export function loadData() (list) {
// 从服务器端加载数据
fetch('/todo').then(function (res) {
todoList.value = res.json().items;
})
}

父组件或子组件都可以引入 todoListStore.js,共同维护其中的响应式变量 todoList 的状态。

更复杂的状态管理,推荐使用 Pinia

使用 window 变量

在组件中把变量赋值给 windows 变量,父组件、兄弟组件、子组件都能访问到,子组件 setup() 之外也能访问到。此方法违背了 Vue3 的设计思想,容易引起混乱,慎用。

Vue3 组件间相互访问(单文件 + <script setup> + 组合式 API 举例)

https://coderpan.com/front-end/vue3-component-transfer-value.html

作者

CoderPan

发布于

2023-02-01

更新于

2024-05-08

许可协议

评论