如何在范型组件中使用 forwardRef

如何在范型组件中使用 forwardRef

English

在TypeScript中使用React的forwardRef时,存在一些限制,其中最大的限制是它禁用了对泛型组件的类型推断。

什么是泛型组件?

泛型组件的一个常见用途是Table组件:

tsx 复制代码
const Table = <T,>(props: {
  data: T[];
  renderRow: (row: T) => React.ReactNode;
}) => {
  return (
    <table>
      <tbody>
        {props.data.map((item, index) => (
          <props.renderRow key={index} {...item} />
        ))}
      </tbody>
    </table>
  );
};

这里,当我们传递一个数组到data时,它会推断出传递给renderRow函数的参数的类型。

tsx 复制代码
<Table
  {/* 1. Data is a string here... */}
  data={["a", "b"]}
  {/* 2. So ends up inferring as a string in renderRow. */}
  renderRow={(row) => {
    return <tr>{row}</tr>;
  }}
/>;
 
<Table
  {/* 3. Data is a number here... */}
  data={[1, 2]}
  {/* 4. So ends up inferring as a number in renderRow. */}
  renderRow={(row) => {
    return <tr>{row}</tr>;
  }}
/>;

这对于无需任何额外注解即可在renderRow函数上获得类型推断非常有帮助。

使用forwardRef的问题

问题出现在当我们尝试给我们的Table组件添加一个ref时:

tsx 复制代码
const Table = <T,>(
  props: {
    data: T[];
    renderRow: (row: T) => React.ReactNode;
  },
  ref: React.ForwardedRef<HTMLTableElement>
) => {
  return (
    <table ref={ref}>
      <tbody>
        {props.data.map((item, index) => (
          <props.renderRow key={index} {...item} />
        ))}
      </tbody>
    </table>
  );
};

const ForwardReffedTable = React.forwardRef(Table);

到目前为止,这一切看起来都很好,但是当我们使用我们的ForwardReffedTable组件时,之前看到的类型推断不再工作。

tsx 复制代码
<ForwardReffedTable
  {/* 1. Data is a string here... */}
  data={["a", "b"]}
  {/* 2. But ends up being inferred as unknown. */}
  renderRow={(row) => {
    return <tr />;
  }}
/>;
 
<ForwardReffedTable
  {/* 3. Data is a number here... */}
  data={[1, 2]}
  {/* 4. But still ends up being inferred as unknown. */}
  renderRow={(row) => {
    return <tr />;
  }}
/>;

这非常令人沮丧。但是,这个问题可以被解决。

解决方案

我们可以使用不同的类型定义来重新定义forwardRef,然后它就开始工作了。

这是新的定义:

ts 复制代码
function fixedForwardRef<T, P = {}>(
  render: (props: P, ref: React.Ref<T>) => React.ReactNode
): (props: P & React.RefAttributes<T>) => React.ReactNode {
  return React.forwardRef(render) as any;
}

我们可以改变我们的定义来使用fixedForwardRef

ts 复制代码
const ForwardReffedTable = fixedForwardRef(Table);

突然之间,它就开始工作了:

tsx 复制代码
<Table
  {/* 1. Data is a string here... */}
  data={["a", "b"]}
  {/* 2. So ends up inferring as a string in renderRow. */}
  renderRow={(row) => {
    return <tr>{row}</tr>;
  }}
/>;
 
<Table
  {/* 3. Data is a number here... */}
  data={[1, 2]}
  {/* 4. So ends up inferring as a number in renderRow. */}
  renderRow={(row) => {
    return <tr>{row}</tr>;
  }}
/>;

这是我的推荐解决方案 - 用实际工作的不同类型的新函数重新定义forwardRef。

Vue 3中的泛型组件示例

在Vue 3中,类型推断和引用的传递与React有所不同。Vue 3提供了Composition API,这是一种新的组件逻辑组织方式,它与TypeScript的集成让我们可以更灵活地处理类型推断和引用。以下是如何在Vue 3中处理类似问题的示例。

假设我们有一个类似的泛型Table组件,但在Vue 3的上下文中:

vue 复制代码
<template>
  <table>
    <tbody>
      <tr v-for="(item, index) in data" :key="index">
        <slot :row="item"></slot>
      </tr>
    </tbody>
  </table>
</template>

<script lang="ts">
import { defineComponent, PropType } from 'vue';

export default defineComponent({
  props: {
    data: {
      type: Array as PropType<any[]>,
      required: true,
    },
  },
});
</script>

在这个组件中,我们使用了slot来代替React示例中的renderRow函数,这样父组件就可以决定如何渲染每一行。这种方法保留了类型推断的灵活性,并且适用于Vue的模板系统。

在Vue 3中,如果你想要传递一个引用(例如,到一个DOM元素或另一个组件实例),你可能会使用ref和响应式APIprovide/inject

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

const tableRef = ref(null);
provide('tableRef', tableRef);
</script>

然后在需要的地方注入引用:

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

const tableRef = inject('tableRef');
</script>
相关推荐
阿里巴啦24 分钟前
React + Three.js + R3F + Vite 实战:可交互的三维粒子化展厅
react.js·three.js·粒子化·drei·postprocessing·三维粒子化
whyfail31 分钟前
Vue原理(暴力版)
前端·vue.js
Bigger37 分钟前
Coco AI 技术演进:Shadcn UI + Tailwind CSS v4.0 深度迁移指南 (踩坑实录)
前端·css·weui
踢球的打工仔41 分钟前
jquery的基本使用(3)
前端·javascript·jquery
前端无涯41 分钟前
Trae的使用
前端·ide·trae
WG_171 小时前
Linux:进程控制
前端·chrome
[seven]1 小时前
React Router TypeScript 路由详解:嵌套路由与导航钩子进阶指南
前端·react.js·typescript
无我Code1 小时前
前端-2025年末个人总结
前端·年终总结
文刀竹肃1 小时前
DVWA -SQL Injection-通关教程-完结
前端·数据库·sql·安全·网络安全·oracle