问题描述
我正在尝试创建一个通用组件,用户可以在其中将自定义的 OptionType
传递给该组件,以进行类型检查.该组件还需要 React.forwardRef
.
I am trying to create a generic component where a user can pass the a custom OptionType
to the component to get type checking all the way through. This component also required a React.forwardRef
.
我可以在没有forwardRef的情况下使其正常工作.有任何想法吗?下面的代码:
I can get it to work without a forwardRef. Any ideas? Code below:
没有ForwardRef.tsx
export interface Option<OptionValueType = unknown> {
value: OptionValueType;
label: string;
}
interface WithoutForwardRefProps<OptionType> {
onChange: (option: OptionType) => void;
options: OptionType[];
}
export const WithoutForwardRef = <OptionType extends Option>(
props: WithoutForwardRefProps<OptionType>,
) => {
const { options, onChange } = props;
return (
<div>
{options.map((opt) => {
return (
<div
onClick={() => {
onChange(opt);
}}
>
{opt.label}
</div>
);
})}
</div>
);
};
WithForwardRef.tsx
import { Option } from './WithoutForwardRef';
interface WithForwardRefProps<OptionType> {
onChange: (option: OptionType) => void;
options: OptionType[];
}
export const WithForwardRef = React.forwardRef(
<OptionType extends Option>(
props: WithForwardRefProps<OptionType>,
ref?: React.Ref<HTMLDivElement>,
) => {
const { options, onChange } = props;
return (
<div>
{options.map((opt) => {
return (
<div
onClick={() => {
onChange(opt);
}}
>
{opt.label}
</div>
);
})}
</div>
);
},
);
App.tsx
import { WithoutForwardRef, Option } from './WithoutForwardRef';
import { WithForwardRef } from './WithForwardRef';
interface CustomOption extends Option<number> {
action: (value: number) => void;
}
const App: React.FC = () => {
return (
<div>
<h3>Without Forward Ref</h3>
<h4>Basic</h4>
<WithoutForwardRef
options={[{ value: 'test', label: 'Test' }, { value: 1, label: 'Test Two' }]}
onChange={(option) => {
// Does type inference on the type of value in the options
console.log('BASIC', option);
}}
/>
<h4>Custom</h4>
<WithoutForwardRef<CustomOption>
options={[
{
value: 1,
label: 'Test',
action: (value) => {
console.log('ACTION', value);
},
},
]}
onChange={(option) => {
// Intellisense works here
option.action(option.value);
}}
/>
<h3>With Forward Ref</h3>
<h4>Basic</h4>
<WithForwardRef
options={[{ value: 'test', label: 'Test' }, { value: 1, label: 'Test Two' }]}
onChange={(option) => {
// Does type inference on the type of value in the options
console.log('BASIC', option);
}}
/>
<h4>Custom (WitForwardRef is not generic here)</h4>
<WithForwardRef<CustomOption>
options={[
{
value: 1,
label: 'Test',
action: (value) => {
console.log('ACTION', value);
},
},
]}
onChange={(option) => {
// Intellisense SHOULD works here
option.action(option.value);
}}
/>
</div>
);
};
在 App.tsx
中,它表示 WithForwardRef
组件不是通用的.有没有办法做到这一点?
In the App.tsx
, it says the WithForwardRef
component is not generic. Is there a way to achieve this?
回购示例: https://github.com/jgodi/generics-with-forward-ref
谢谢!
推荐答案
不能直接将通用组件创建为 React.forwardRef
的输出(请参阅底部).不过,还有其他选择-让我们简化示例以供说明:
Creating a generic component as output of React.forwardRef
is not directly possible (see bottom). There are some alternatives though - let's simplify your example a bit for illustration:
type Option<O = unknown> = {
value: O;
label: string;
}
type Props<T extends Option<unknown>> = { options: T[] }
const options = [
{ value: 1, label: "la1", flag: true },
{ value: 2, label: "la2", flag: false }
] // just some options data
1.投放
// Given render function (input) for React.forwardRef
const FRefInputComp = <T extends Option>(p: Props<T>, ref: Ref<HTMLDivElement>) =>
<div ref={ref}> {p.options.map(o => <p>{o.label}</p>)} </div>
// Cast the output
const FRefOutputComp1 = React.forwardRef(FRefInputComp) as
<T extends Option>(p: Props<T> & { ref?: Ref<HTMLDivElement> }) => ReactElement
const Usage11 = () => <FRefOutputComp1 options={options} ref={myRef} />
// options has type { value: number; label: string; flag: boolean; }[]
// , so we have made FRefOutputComp generic!
这是可行的,因为 forwardRef
的返回类型原则上是纯函数.我们只需要一个通用的函数类型形状.您可以添加一个额外的类型来简化断言:
This works, as the return type of forwardRef
in principle is a plain function. We just need a generic function type shape. You might add an extra type to make the assertion simpler:
type ForwardRefFn<R> = <P={}>(p: P & React.RefAttributes<R>) => ReactElement |null
// `RefAttributes` is built-in type with ref and key props defined
const Comp12 = React.forwardRef(FRefInputComp) as ForwardRefFn<HTMLDivElement>
const Usage12 = () => <Comp12 options={options} ref={myRef} />
2.包起来
const FRefOutputComp2 = React.forwardRef(FRefInputComp)
// ↳ T is instantiated with base constraint `Option<unknown>` from FRefInputComp
export const Wrapper = <T extends Option>({myRef, ...rest}: Props<T> &
{myRef: React.Ref<HTMLDivElement>}) => <FRefOutputComp2 {...rest} ref={myRef} />
const Usage2 = () => <Wrapper options={options} myRef={myRef} />
3.省略
使用自定义参考道具来代替.这是我最喜欢的-最简单的替代方法,在React 中是合法的方式,不需要 forwardRef
.
3. Omit it
Use a custom ref prop instead. This one is my favorite - simplest alternative, a legitimate way in React and doesn't need forwardRef
.
const Comp3 = <T extends Option>(props: Props<T> & { myRef: Ref<HTMLDivElement> })
=> <div ref={myRef}> {props.options.map(o => <p>{o.label}</p>)} </div>
const Usage3 = () => <Comp3 options={options} myRef={myRef} />
为什么原案不起作用?
React.forwardRef 代码>
具有以下类型:
Why does the original case not work?
React.forwardRef
has following type:
function forwardRef<T, P = {}>(render: ForwardRefRenderFunction<T, P>):
ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;
因此,此函数采用通用类似于 ForwardRefRenderFunction
,并返回类型为 ForwardRefExoticComponent
的最终组件.这两个只是函数类型声明具有其他属性 displayName
, defaultProps
等.
So this function takes a generic component-like render function ForwardRefRenderFunction
, and returns the final component with type ForwardRefExoticComponent
. These two are just function type declarations with additional properties displayName
, defaultProps
etc.
现在,有一个名为高级函数类型推断的TypeScript 3.4功能类似于高等级类型.基本上,它允许您将自由类型参数(来自输入函数的泛型)传播到外部,从而在此处调用函数- React.forwardRef
-因此所生成的函数组件仍然是泛型的.
Now, there is a TypeScript 3.4 feature called higher order function type inference akin to Higher-Rank Types. It basically allows you to propagate free type parameters (generics from the input function) on to the outer, calling function - React.forwardRef
here -, so the resulting function component is still generic.
但是,此功能仅适用于普通函数类型,如Anders Hejlsberg在[1] , [2] :
But this feature can only work with plain function types, as Anders Hejlsberg explains in [1], [2]:
如果您不需要 displayName
的类型,请 defaultProps
&Co.,您可以增强 React模块类型声明.这将使 React.forwardRef
再次与泛型一起使用(有关示例,请参见注释部分).
If you don't need types for displayName
, defaultProps
& Co., you might augment React module type declarations once in your app by removing the additional property types. This will make React.forwardRef
work with generics again (see comment section for an example).
这篇关于使用Typescript进行反应-使用React.forwardRef时的泛型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!