如何在范型组件中使用 forwardRef
在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>