如何在范型组件中使用 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>
相关推荐
大前端爱好者1 小时前
React 19 新特性详解
前端
随云6321 小时前
WebGL编程指南之着色器语言GLSL ES(入门GLSL ES这篇就够了)
前端·webgl
无知的小菜鸡1 小时前
路由:ReactRouter
react.js
寻找09之夏2 小时前
【Vue3实战】:用导航守卫拦截未保存的编辑,提升用户体验
前端·vue.js
多多米10053 小时前
初学Vue(2)
前端·javascript·vue.js
柏箱3 小时前
PHP基本语法总结
开发语言·前端·html·php
新缸中之脑3 小时前
Llama 3.2 安卓手机安装教程
前端·人工智能·算法
hmz8563 小时前
最新网课搜题答案查询小程序源码/题库多接口微信小程序源码+自带流量主
前端·微信小程序·小程序
看到请催我学习3 小时前
内存缓存和硬盘缓存
开发语言·前端·javascript·vue.js·缓存·ecmascript
blaizeer4 小时前
深入理解 CSS 浮动(Float):详尽指南
前端·css