深入探讨:如何在 Vue 3 中让子组件 B 获取子组件 A 的宽高在 Vue 3 的开发过程中,组件间的交互是非常常见
在 Vue 3 的开发过程中,组件间的交互是非常常见的需求。一个特别典型的场景是 子组件 B 需要获取并实时监听子组件 A 的宽高,从而根据这些数据做出一些动态的调整或渲染。
本文将深入探讨如何在 Vue 3 中实现这一需求,重点是 子组件 B 获取子组件 A 的宽高,并使用 Vue 3 的相关 API 进行动态监听。我们将分析如何通过 Vue 3 的组合式 API,以及浏览器的 ResizeObserver
,来构建一个健壮的解决方案。
场景描述
在实际应用中,常常会有这样的需求:一个子组件的尺寸发生变化,另一个子组件需要实时获取这些变化,并相应地做出反应。假设我们有两个子组件:
- 子组件 A:包含一个
div
,其尺寸可能会随着某些用户交互发生变化。 - 子组件 B:需要获取子组件 A 的尺寸,并在这些尺寸变化时做出调整。
需求分析
要实现这个功能,重点在于:
- 如何让子组件 B 获取子组件 A 的尺寸。
- 如何在子组件 A 的尺寸发生变化时,通知子组件 B 进行更新。
关键技术与实现思路
为了解决这个问题,我们需要使用以下 Vue 3 和浏览器 API:
ref
:用于在父组件中获取子组件的 DOM 引用。defineExpose
:允许我们显式地暴露子组件的内部属性或方法,使得父组件可以访问这些内容。onMounted
:确保在父组件挂载之后访问子组件的ref
。ResizeObserver
:这是浏览器提供的 API,用来监听 DOM 元素尺寸的变化。
接下来,我们将逐步展示如何通过这几个核心工具,完成子组件 B 获取并监听子组件 A 尺寸的任务。
实现步骤详解
第一步:子组件 A 暴露其 DOM 元素
首先,我们需要在子组件 A 中使用 ref
绑定其 DOM 元素(比如一个 div
),并通过 Vue 3 的 defineExpose
将该元素暴露给父组件。这样,父组件就能访问并操作子组件 A 的 DOM 元素了。
代码实现:
<template>
<div ref="childADiv" :style="{ width: `${width}px`, height: `${height}px`, backgroundColor: 'lightcoral' }">
这是子组件 A
<button @click="increaseSize">增加宽高</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
// 初始化子组件 A 的宽高
const width = ref(200);
const height = ref(100);
// 通过 ref 获取 DOM 元素
const childADiv = ref(null);
// 暴露该 DOM 元素,使父组件能访问
defineExpose({
childADiv,
});
// 动态修改宽高的函数
const increaseSize = () => {
width.value += 50;
height.value += 25;
};
</script>
<style scoped>
div {
transition: width 0.3s, height 0.3s; /* 为了更好地观察尺寸变化 */
}
</style>
解析:
ref
绑定div
:我们通过ref
绑定了子组件 A 的div
元素,以便后续可以获取该元素的宽高。defineExpose
:这是 Vue 3 中一个非常有用的 API,用于暴露子组件内部的ref
,供父组件访问。没有它,父组件无法直接访问子组件内部的 DOM 元素。
为什么要使用
defineExpose
? Vue 3 的<script setup>
语法默认是封闭的,子组件的内部状态是无法被外部直接访问的。通过defineExpose
,我们可以选择性地暴露一些属性或方法,以便父组件能访问它们。
第二步:父组件获取子组件 A 的 DOM 引用
在父组件中,我们使用 ref
来绑定子组件 A,并通过 onMounted
钩子在子组件挂载后获取其 DOM 引用。父组件需要获取这个 DOM 引用并将其传递给子组件 B。
代码实现:
<template>
<div>
<!-- 子组件 A -->
<ChildA ref="childA" />
<!-- 子组件 B,接收子组件 A 的 DOM 引用 -->
<ChildB v-if="childARef" :childARef="childARef" />
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import ChildA from './ChildA.vue';
import ChildB from './ChildB.vue';
// 用于存储子组件 A 的 DOM 引用
const childA = ref(null);
const childARef = ref(null);
// 当父组件挂载时,获取子组件 A 的 DOM 元素引用
onMounted(() => {
if (childA.value) {
childARef.value = childA.value.childADiv; // 获取暴露的 childADiv
}
});
</script>
解析:
onMounted
:确保在父组件挂载时,子组件 A 已经挂载完成,因此可以安全地获取其 DOM 元素引用。v-if
:在子组件 B 渲染时,我们使用v-if
检查childARef
是否已经获取到,确保子组件 B 能够接收到有效的 DOM 引用。
为什么不使用
nextTick
? 在这种情况下,由于 Vue 的生命周期已经确保了在onMounted
阶段,子组件已经挂载完成,因此直接访问ref
就足够了,nextTick
是多余的。
第三步:子组件 B 监听子组件 A 的宽高变化
子组件 B 的任务是获取子组件 A 的 DOM 引用,并监听其宽高变化。为了监听动态的尺寸变化,我们可以使用浏览器的 ResizeObserver
API,它能够实时检测 DOM 元素的尺寸变动。
代码实现:
<template>
<div>
<p>子组件 A 的宽度: {{ childAWidth }}px</p>
<p>子组件 A 的高度: {{ childAHeight }}px</p>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
// 接收父组件传递的子组件 A 的 DOM 引用
const props = defineProps({
childARef: Object,
});
const childAWidth = ref(0);
const childAHeight = ref(0);
let resizeObserver = null;
onMounted(() => {
if (props.childARef) {
// 使用 ResizeObserver 监听宽高变化
resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
childAWidth.value = entry.contentRect.width;
childAHeight.value = entry.contentRect.height;
}
});
resizeObserver.observe(props.childARef); // 开始监听
}
});
onBeforeUnmount(() => {
if (resizeObserver && props.childARef) {
resizeObserver.unobserve(props.childARef); // 清理监听器
}
});
</script>
解析:
ResizeObserver
:我们使用ResizeObserver
来监听子组件 A 的div
尺寸变化,并在尺寸发生变化时,更新子组件 B 中的宽高显示。onBeforeUnmount
:当组件销毁时,记得移除ResizeObserver
监听器,以避免潜在的内存泄漏。
为什么要使用
ResizeObserver
?ResizeObserver
是原生 API,它能够监听 DOM 元素的尺寸变化,并在宽高发生变化时触发回调。这比传统的window.resize
更加精细,适用于任何尺寸变化的场景,比如容器动态调整大小。
为什么这样写?
- 解耦组件间的逻辑:通过
ref
和defineExpose
,我们避免了让父组件直接操纵子组件的内部逻辑,而是通过暴露 DOM 引用的方式,让父组件和子组件保持松散耦合。 - 避免性能问题:
ResizeObserver
是专门用于监听 DOM 尺寸变化的 API,避免了频繁的轮询或window.resize
事件带来的性能开销。 - 简化的生命周期管理:通过 Vue 的
onMounted
和onBeforeUnmount
钩子,确保了在合适的时机获取 DOM 引用,并及时清理资源。
总结
在 Vue 3 中,通过 ref
、defineExpose
、onMounted
和 ResizeObserver
,我们可以轻松实现子组件 B 获取并监听子组件 A 的宽高变化的需求。具体来说:
- 子组件 A 暴露自己的 DOM 引用。
- 父组件通过
ref
获取这个引用,并将其传递给子组件 B。 - 子组件 B 使用
ResizeObserver
监听子组件 A 的尺寸变化,从而实现动态交互。
思考与延伸
- 组件间通信的实践:通过这种方式,我们避免了组件间的直接操作,而是通过显式暴露
ref
,保持了良好的组件解耦。可以思考一下,是否可以在其他场景中应用类似的设计? ResizeObserver
的性能问题:虽然ResizeObserver
比window.resize
更加精细,但如果监听的 DOM 元素很多,可能会带来性能问题。如何在项目中优化大量元素的监听?可以考虑使用节流(throttle)或防抖(debounce)技术。
希望本文能够帮助你理解 Vue 3 中的组件间交互,特别是在处理子组件的尺寸获取和监听场景中。
转载自:https://juejin.cn/post/7420718386952699919