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

相关推荐
B站_计算机毕业设计之家5 分钟前
豆瓣电影数据采集分析推荐系统 | Python Vue Flask框架 LSTM Echarts多技术融合开发 毕业设计源码 计算机
vue.js·python·机器学习·flask·echarts·lstm·推荐算法
吃杠碰小鸡1 小时前
高中数学-数列-导数证明
前端·数学·算法
kingwebo'sZone1 小时前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_09011 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农2 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king2 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
跳动的梦想家h3 小时前
环境配置 + AI 提效双管齐下
java·vue.js·spring
夏幻灵3 小时前
HTML5里最常用的十大标签
前端·html·html5
Mr Xu_3 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝3 小时前
RBAC前端架构-01:项目初始化
前端·架构