如何在范型组件中使用 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>
相关推荐
前端之虎陈随易16 小时前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·vue.js·人工智能·typescript·node.js
一路向北he16 小时前
字节钢铁军团--“提供情境,而非控制”
java·开发语言·前端
kyriewen17 小时前
豆包和千问同时关了智能体,我用它们搭的 3 个自动化全废了——迁移方案整理
前端·javascript·ai编程
前端一小卒17 小时前
我用 TypeScript 从零手写了一个 Claude Code,然后发现它的核心只有 30 行
前端·agent
大圣编程18 小时前
Python中continue语句的用法是什么?
开发语言·前端·python
yuhaiqiang18 小时前
随手 vibecoding 的浏览器插件已经 6000 多次下载,聊聊他的产品设计
前端·后端·面试
之歆19 小时前
Vue商品详情与放大镜组件
前端·javascript·vue.js
再吃一根胡萝卜20 小时前
如何把小米 MiMo 接入 CodeBuddy,打造私有 Agent
前端
负责的蛋挞21 小时前
异步HttpModule的实现方式
java·服务器·前端
YFF菲菲兔1 天前
其他 Hooks 解析
react.js