为什么 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 的响应式系统和父组件的访问已经设定好了,因此暴露的方法不再能正确传递。

相关推荐
Freak嵌入式7 分钟前
MicroPython LVGL基础知识和概念:时序与动态效果
开发语言·python·github·php·gui·lvgl·micropython
_院长大人_30 分钟前
Vue + ECharts 实现价格趋势分析图
前端·vue.js·echarts
疯笔码良31 分钟前
【Vue】自适应布局
javascript·vue.js·css3
浩星36 分钟前
electron系列2:搭建专业Electron开发环境
javascript·typescript·electron
2501_9333295537 分钟前
企业媒体发布与舆情管理实战:Infoseek舆情系统技术架构与落地解析
大数据·开发语言·人工智能·数据库开发
"菠萝"39 分钟前
C#知识学习-021(文字关键字)
开发语言·学习·c#
minji...40 分钟前
Linux 线程同步与互斥(二) 线程同步,条件变量,pthread_cond_init/wait/signal/broadcast
linux·运维·开发语言·jvm·数据结构·c++
zhangzeyuaaa41 分钟前
Python 中的 Map 和 Reduce 详解
开发语言·python
游乐码1 小时前
c#HashTable
开发语言·c#