Vue 3 调用深层组件方法,这 3 招够用了!

大家好,我是 前端架构师 - 大卫

更多优质内容请关注微信公众号 @程序员大卫

初心为助前端人🚀,进阶路上共星辰✨,

您的点赞👍与关注❤️,是我笔耕不辍的灯💡。

背景

假设有 A、B、C 三个组件,A 组件想要调用最内层 C 组件的方法。由于组件之间是逐层嵌套的,A 并不能直接访问 C,这就需要我们通过一些手段将 C 的方法"暴露"到 A。

下面介绍三种常见的实现方式。

方法一:传统方式 ------ defineExpose 层层传递

通过 defineExpose 将方法逐层向上传递,让 A 组件最终可以访问到 C 的方法。

C 组件

html 复制代码
<script setup lang="ts">
import { ref } from "vue";

const num = ref(0);

defineExpose({
  increment: () => num.value++,
});
</script>

<template>
  <h1>C: {{ num }}</h1>
</template>

B 组件

html 复制代码
<script setup lang="ts">
import { shallowRef } from "vue";
import C from "./C.vue";

const cRef = shallowRef<InstanceType<typeof C>>();

defineExpose({
  increment: () => cRef.value?.increment(),
});
</script>

<template>
  <h1>B</h1>
  <C ref="cRef" />
</template>

A 组件

html 复制代码
<script setup lang="ts">
import { shallowRef } from "vue";
import B from "./B.vue";

const bRef = shallowRef<InstanceType<typeof B>>();
</script>

<template>
  <h1 @click="bRef?.increment">A</h1>
  <B ref="bRef" />
</template>

✅ 优点:写法直观,类型推导良好。

❌ 缺点:每层组件都要处理 refdefineExpose,不够灵活。

方法二:使用 Proxy 自动透传

通过封装 useRefExpose 工具方法,将内部组件实例通过 Proxy 代理出去,无需每层手动暴露方法。

A 组件和 C 组件不变。

B 组件

html 复制代码
<script setup lang="ts">
import { ref } from "vue";
import C from "./C.vue";
import { useRefExpose } from "@/hooks/useRefExpose";

const childRef = ref<InstanceType<typeof C>>();

defineExpose(useRefExpose(childRef));
</script>

<template>
  <h1>B</h1>
  <C ref="childRef" />
</template>

⚠️注意:如果 B 组件还想把自己的方法暴露出去,那么可以这么写:

ts 复制代码
defineExpose({
  ...useRefExpose(childRef),
  otherMethod: () => {
    console.log("xxxxx");
  },
});

useRefExpose 实现

ts 复制代码
import { Ref, ComponentPublicInstance } from "vue";

export function useRefExpose<T extends ComponentPublicInstance>(
  ref: Ref<T | undefined | null>
) {
  return new Proxy({} as T, {
    get(_, prop) {
      return ref.value?.[prop as keyof T];
    },
    has(_, prop) {
      return ref.value ? prop in ref.value : false;
    },
  });
}

✅ 优点:无需一层层暴露,只需要封装一次代理逻辑。

方法三:使用注册回调的方式传递实例

通过事件注册的方式,将 C 组件实例通过 B 传递给 A。

C 组件不变。

B 组件

html 复制代码
<script setup lang="ts">
import { ComponentPublicInstance } from "vue";
import C from "./C.vue";

type CInstance = InstanceType<typeof C>;

const emit = defineEmits<{
  (e: "register", el: CInstance): void;
}>();

const setRef = (c: Element | ComponentPublicInstance | null) => {
  emit("register", c as CInstance);
};
</script>

<template>
  <h1>B</h1>
  <C :ref="setRef" />
</template>

可以看到 B 组件的 ts 类型不够友好,setRef 函数使用了 c as CInstance,所以 B 组件可以改成:

html 复制代码
<script setup lang="ts">
import { shallowRef, watchEffect } from "vue";
import C from "./C.vue";

type CInstance = InstanceType<typeof C>;

const emit = defineEmits<{
  (e: "register", el: CInstance): void;
}>();

const cRef = shallowRef<CInstance | null>(null);

watchEffect(() => {
  if (cRef.value) {
    emit("register", cRef.value);
  }
});
</script>

<template>
  <h1>B</h1>
  <C ref="cRef" />
</template>

A 组件

A 组件还需要引入 C 的类型,所以 register 的这种方式,感觉不够好。

html 复制代码
<script setup lang="ts">
import { shallowRef } from "vue";
import B from "./B.vue";
import type C from "./C.vue";

type CInstance = InstanceType<typeof C>;

const cInstance = shallowRef<CInstance | null>(null);

const register = (el: CInstance) => {
  cInstance.value = el;
};
</script>

<template>
  <h1 @click="cInstance?.increment">A</h1>
  <B @register="register" />
</template>

✅ 优点:组件解耦,不需要使用 defineExpose

❌ 缺点:A 需要依赖 C 的类型,B 组件的 ref 类型也需手动指定,TS 写法略显繁琐。

总结

方法 优点 缺点 适用场景
方法一 defineExpose 简单直观,TS 类型友好 每层都要处理 refexpose 层级不深或可控时
方法二 Proxy 极简,封装后复用性高 组件链复杂,想省事时
方法三 register 解耦组件间依赖 类型书写繁琐,耦合事件逻辑 ref 管理和注册有需求时

根据项目复杂度和团队偏好选择合适方式,简单场景优先用 defineExpose,复杂嵌套推荐 Proxy 方案。

相关推荐
coding随想1 小时前
JavaScript ES6 解构:优雅提取数据的艺术
前端·javascript·es6
小小小小宇1 小时前
一个小小的柯里化函数
前端
灵感__idea1 小时前
JavaScript高级程序设计(第5版):无处不在的集合
前端·javascript·程序员
小小小小宇1 小时前
前端双Token机制无感刷新
前端
小小小小宇1 小时前
重提React闭包陷阱
前端
小小小小宇1 小时前
前端XSS和CSRF以及CSP
前端
UFIT1 小时前
NoSQL之redis哨兵
java·前端·算法
超级土豆粉1 小时前
CSS3 的特性
前端·css·css3
星辰引路-Lefan1 小时前
深入理解React Hooks的原理与实践
前端·javascript·react.js
wyn200011282 小时前
JavaWeb的一些基础技术
前端