为什么 Vue3 封装 Table 组件丢失 expose 方法呢?

在实际开发中,我们通常会将某些常见组件进行二次封装,以便更好地实现特定的业务需求。然而,在封装 Table 组件时,遇到一个问题:Table 内部暴露的方法,在封装之后的组件获取不到。

代码展示为:

TypeScript 复制代码
const MyTable = defineComponent({
  name: 'MyTable',
  props: ElTable.props,
  emits: Object.keys(ElTable.emits || {}),
  setup(props, { slots, attrs, expose }) {
    const tableRef = ref<InstanceType<typeof ElTable>>();
    return () => (
      <div class='table-container'>
        <ElTable
          ref={tableRef}
          {...props}
          {...attrs}
          v-slots={slots}
        >
          {slots.default && slots.default()}
        </ElTable>
      </div>
    );
  },
});

在 Element Plus 的 ElTable 组件中,有很多暴露的方法,如下:

官方文档:Table 表格 | Element Plus

但使用上面封装的 MyTable 打印组件实例时,只有 table 本身的方法:

下面简单分析一下原因。

1. expose 未正确定义或调用

原因分析:

在 Vue 3 中,expose 用于暴露子组件的方法和属性,使父组件能够访问和操作它们。如果封装了 ElTable 组件,但是没有正确定义 expose 或者没有通过 ref 正确引用子组件实例,那么 expose 的方法无法生效。

如果在 setup 中使用 expose API 或者 expose 的位置不对,那么暴露的方法就无法通过 ref 访问到。🌰:

TypeScript 复制代码
expose({
  setCurrentRow: tableRef.value?.setCurrentRow, // 错误:此时 tableRef.value 还可能为 null
  clearSelection: tableRef.value?.clearSelection // 错误
});

解决方法:确保在 setup 中正确使用 expose 并且在暴露方法时,确保 tableRef 已经指向 ElTable 的实例。

2. ElTable 组件实例未传递给 tableRef

原因分析:

在封装代码中,ElTable 内部 expose 的方法(例如:setCurrentRow、clearSelection 等)可能会因为 ref 没有正确透传而丢失。Vue3 中,ref 的默认行为不能直接传递组件的 expose 方法到父组件中(本质)。

解决方法:手动暴露,确保 ElTable 组件通过 ref 绑定到 tableRef。

3. 组件生命周期中的调用顺序问题

原因分析:

expose 需要在 setup 函数中定义,而 tableRef 需要在组件挂载后才能被正确引用。如果在组件生命周期的某个不合适的时间调用 expose,比如,在 setup 函数之外或者组件渲染之前,可能导致tableRef 无法正确指向组件实例,从而无法暴露方法。

解决方法:使用 nextTick 来确保组件渲染完成后再执行某些操作。

综上,代码如下:

TypeScript 复制代码
import { defineComponent, ref } from 'vue';
import { ElTable } from 'element-plus';

export default defineComponent({
  name: 'MyTable',
  props: ElTable.props,
  emits: Object.keys(ElTable.emits || {}),
  setup(props, { attrs, slots, expose }) {
    const tableRef = ref<InstanceType<typeof ElTable>>();
    // 确保暴露方法时,tableRef 已经引用了正确的实例
    expose({
      setCurrentRow: (...args: Parameters<InstanceType<typeof ElTable>['setCurrentRow']>) =>
        tableRef.value?.setCurrentRow(...args),
      clearSelection: (...args: Parameters<InstanceType<typeof ElTable>['clearSelection']>) =>
        tableRef.value?.clearSelection(...args),
    });
    return () => (
      <div class="table-container">
        <ElTable ref={tableRef} {...props} {...attrs} v-slots={slots}>
          {slots.default && slots.default()}
        </ElTable>
      </div>
    );
  }
});

现在再看一下 组件实例,已经存在了需要的方法。

注意,在 table 内部存在很多暴露的方法,要想让我们封装的 MyTable 和 ElTable 可以通用,那么需要将方法全部暴露出来:

TypeScript 复制代码
export default defineComponent({
  name: 'MyTable',
  props: ElTable.props,
  emits: Object.keys(ElTable.emits || {}),
  setup(props, { attrs, slots, expose }) {
    const tableRef = ref<InstanceType<typeof ElTable>>();
    const ExposedMethods = [
      "clearSelection",
      "getSelectionRows",
      "toggleRowSelection",
      "toggleAllSelection",
      "toggleRowExpansion",
      // ...
    ];
    // 将方法透传
    const exposedMethods: Record<string, Function> = {};
    ExposedMethods.forEach(method => {
      exposedMethods[method] = (...args: any[]) => {
        return tableRef.value?.[method](...args);
      };
    });
    // 使用 expose 透传所有方法
    expose(exposedMethods);
    return () => (
      <div class="table-container">
        <ElTable ref={tableRef} {...props} {...attrs} v-slots={slots}>
          {slots.default && slots.default()}
        </ElTable>
      </div>
    );
  }
});

现在,所有的方法均存在,后续可以直接使用我们封装的 MyTable 组件。

4. 注意

有一个坑,不能将 expose 放在 onMounted 内部执行,为什么呢?

TypeScript 复制代码
onMounted(() => {
  const exposedMethods: Record<string, Function> = {};
  ExposedMethods.forEach(method => {
    exposedMethods[method] = (...args: any[]) => {
      return tableRef.value?.[method](...args);
    };
  });
  // 使用 expose 透传所有方法
  expose(exposedMethods);
});
  1. expose 在 setup 函数内部调用时,会立即暴露方法或属性给外部访问。 如果将 expose 放入 onMounted 中,setup 函数的执行已经结束,因此暴露的方法不会被 Vue 视为暴露的实例方法。

  2. onMounted 是一个生命周期钩子函数,它会在组件挂载后执行,但此时 Vue 的响应式系统和父组件的访问已经设定好了,因此暴露的方法不再能正确传递。

相关推荐
步步为营DotNet43 分钟前
深度解析CancellationToken:.NET中的优雅取消机制
java·前端·.net
gaolei_eit2 小时前
Vue3项目ES6转ES5,兼容低版本的硬件设备,React也
javascript·react.js·es6
一位搞嵌入式的 genius2 小时前
从 ES6 到 ESNext:JavaScript 现代语法全解析(含编译工具与实战)
前端·javascript·ecmascript·es6
linweidong4 小时前
C++ 模块化编程(Modules)在大规模系统中的实践难点?
linux·前端·c++
leobertlan7 小时前
2025年终总结
前端·后端·程序员
子兮曰8 小时前
OpenClaw架构揭秘:178k stars的个人AI助手如何用Gateway模式统一控制12+通讯频道
前端·javascript·github
冷雨夜中漫步8 小时前
Python快速入门(6)——for/if/while语句
开发语言·经验分享·笔记·python
百锦再8 小时前
Reactive编程入门:Project Reactor 深度指南
前端·javascript·python·react.js·django·前端框架·reactjs
莲华君8 小时前
React快速上手:从零到项目实战
前端·reactjs教程
百锦再8 小时前
React编程高级主题:测试代码
android·前端·javascript·react.js·前端框架·reactjs