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 方案。

相关推荐
踩着两条虫26 分钟前
如何评价VTJ.PRO?
前端·架构·ai编程
Mh1 小时前
鼠标跟随倾斜动效
前端·css·vue.js
小码哥_常2 小时前
Kotlin类型魔法:Any、Unit、Nothing 深度探秘
前端
Web极客码3 小时前
深入了解WordPress网站访客意图
服务器·前端·wordpress
幺风4 小时前
Claude Code 源码分析 — Tool/MCP/Skill 可扩展工具系统
前端·javascript·ai编程
vjmap4 小时前
唯杰地图CAD图层加高性能特效扩展包发布
前端·gis
ZC跨境爬虫4 小时前
3D 地球卫星轨道可视化平台开发 Day7(AI异步加速+卫星系列精简+AI Agent自动评论)
前端·人工智能·3d·html·json
ID_180079054734 小时前
淘宝 API 上货 / 商品搬家 业务场景实现 + JSON 返回示例
前端·javascript·json
M ? A4 小时前
Vue 动态组件在 React 中,VuReact 会如何实现?
前端·javascript·vue.js·经验分享·react.js·面试·vureact
vipbic5 小时前
独立开发复盘:我用 Uni-app + Strapi v5 肝了一个“会上瘾”的打卡小程序
前端·微信小程序