likes
comments
collection
share

深入探讨:如何在 Vue 3 中让子组件 B 获取子组件 A 的宽高在 Vue 3 的开发过程中,组件间的交互是非常常见

作者站长头像
站长
· 阅读数 12

在 Vue 3 的开发过程中,组件间的交互是非常常见的需求。一个特别典型的场景是 子组件 B 需要获取并实时监听子组件 A 的宽高,从而根据这些数据做出一些动态的调整或渲染。

本文将深入探讨如何在 Vue 3 中实现这一需求,重点是 子组件 B 获取子组件 A 的宽高,并使用 Vue 3 的相关 API 进行动态监听。我们将分析如何通过 Vue 3 的组合式 API,以及浏览器的 ResizeObserver,来构建一个健壮的解决方案。

场景描述

在实际应用中,常常会有这样的需求:一个子组件的尺寸发生变化,另一个子组件需要实时获取这些变化,并相应地做出反应。假设我们有两个子组件:

  • 子组件 A:包含一个 div,其尺寸可能会随着某些用户交互发生变化。
  • 子组件 B:需要获取子组件 A 的尺寸,并在这些尺寸变化时做出调整。

需求分析

要实现这个功能,重点在于:

  1. 如何让子组件 B 获取子组件 A 的尺寸
  2. 如何在子组件 A 的尺寸发生变化时,通知子组件 B 进行更新

关键技术与实现思路

为了解决这个问题,我们需要使用以下 Vue 3 和浏览器 API:

  1. ref:用于在父组件中获取子组件的 DOM 引用。
  2. defineExpose:允许我们显式地暴露子组件的内部属性或方法,使得父组件可以访问这些内容。
  3. onMounted:确保在父组件挂载之后访问子组件的 ref
  4. 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>
解析:
  1. ref 绑定 div:我们通过 ref 绑定了子组件 A 的 div 元素,以便后续可以获取该元素的宽高。
  2. 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>
解析:
  1. onMounted:确保在父组件挂载时,子组件 A 已经挂载完成,因此可以安全地获取其 DOM 元素引用。
  2. 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>
解析:
  1. ResizeObserver:我们使用 ResizeObserver 来监听子组件 A 的 div 尺寸变化,并在尺寸发生变化时,更新子组件 B 中的宽高显示。
  2. onBeforeUnmount:当组件销毁时,记得移除 ResizeObserver 监听器,以避免潜在的内存泄漏。

为什么要使用 ResizeObserverResizeObserver 是原生 API,它能够监听 DOM 元素的尺寸变化,并在宽高发生变化时触发回调。这比传统的 window.resize 更加精细,适用于任何尺寸变化的场景,比如容器动态调整大小。


为什么这样写?

  1. 解耦组件间的逻辑:通过 refdefineExpose,我们避免了让父组件直接操纵子组件的内部逻辑,而是通过暴露 DOM 引用的方式,让父组件和子组件保持松散耦合。
  2. 避免性能问题ResizeObserver 是专门用于监听 DOM 尺寸变化的 API,避免了频繁的轮询或 window.resize 事件带来的性能开销。
  3. 简化的生命周期管理:通过 Vue 的 onMountedonBeforeUnmount 钩子,确保了在合适的时机获取 DOM 引用,并及时清理资源。

总结

在 Vue 3 中,通过 refdefineExposeonMountedResizeObserver,我们可以轻松实现子组件 B 获取并监听子组件 A 的宽高变化的需求。具体来说:

  • 子组件 A 暴露自己的 DOM 引用。
  • 父组件通过 ref 获取这个引用,并将其传递给子组件 B。
  • 子组件 B 使用 ResizeObserver 监听子组件 A 的尺寸变化,从而实现动态交互。

思考与延伸

  • 组件间通信的实践:通过这种方式,我们避免了组件间的直接操作,而是通过显式暴露 ref,保持了良好的组件解耦。可以思考一下,是否可以在其他场景中应用类似的设计?
  • ResizeObserver 的性能问题:虽然 ResizeObserverwindow.resize 更加精细,但如果监听的 DOM 元素很多,可能会带来性能问题。如何在项目中优化大量元素的监听?可以考虑使用节流(throttle)或防抖(debounce)技术。

希望本文能够帮助你理解 Vue 3 中的组件间交互,特别是在处理子组件的尺寸获取和监听场景中。

转载自:https://juejin.cn/post/7420718386952699919
评论
请登录