Pri*_*imm 37 generics typescript reactjs
我正在尝试创建一个通用组件,用户可以在其中将自定义传递OptionType
给组件以进行类型检查。该组件还需要一个React.forwardRef
.
我可以让它在没有 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>
);
};
Run Code Online (Sandbox Code Playgroud)
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>
);
},
);
Run Code Online (Sandbox Code Playgroud)
应用程序.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>
);
};
Run Code Online (Sandbox Code Playgroud)
在 中App.tsx
,它表示该WithForwardRef
组件不是通用的。有没有办法实现这一目标?
示例存储库:https : //github.com/jgodi/generics-with-forward-ref
谢谢!
for*_*d04 58
React.forwardRef
不能直接创建通用组件作为输出1(见底部)。虽然有一些替代方案 - 让我们稍微简化一下您的示例以进行说明:
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 }
]
Run Code Online (Sandbox Code Playgroud)
为简单起见,选择变体 (1) 或 (2)。(3) 将替换forwardRef
为通常的道具。使用 (4) 您可以forwardRef
在应用程序中全局设置一次类型定义。
// 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!
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为forwardRef
原则上的返回类型是一个普通的 function。我们只需要一个通用的函数类型形状。您可以添加额外的类型以使断言更简单:
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} />
Run Code Online (Sandbox Code Playgroud)
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} />
Run Code Online (Sandbox Code Playgroud)
forwardRef
产品总数改用自定义 ref 道具。这是我最喜欢的 - 最简单的替代方法,是React 中的一种合法方式,不需要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} />
Run Code Online (Sandbox Code Playgroud)
在您的应用程序中添加以下代码一次,最好在单独的模块中react-augment.d.ts
:
import React from "react"
declare module "react" {
function forwardRef<T, P = {}>(
render: (props: P, ref: ForwardedRef<T>) => ReactElement | null
): (props: P & RefAttributes<T>) => ReactElement | null
}
Run Code Online (Sandbox Code Playgroud)
这将增加React 模块类型声明,覆盖forwardRef
一个新的函数重载类型签名。权衡:像displayName
现在这样的组件属性需要类型断言。
React.forwardRef
有以下类型:
function forwardRef<T, P = {}>(render: ForwardRefRenderFunction<T, P>):
ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;
Run Code Online (Sandbox Code Playgroud)
所以这个函数接受一个类似组件的通用渲染函数 ForwardRefRenderFunction
,并返回类型为的最终组件ForwardRefExoticComponent
。这两个都只是函数的类型声明与附加属性 displayName
,defaultProps
等等。
现在,有一个 TypeScript 3.4 功能称为高阶函数类型推断,类似于Higher-Rank Types。它基本上允许您将自由类型参数(来自输入函数的泛型)传播到外部调用函数 -React.forwardRef
此处 -,因此生成的函数组件仍然是泛型的。
但是这个特性只能用于普通的函数类型,正如 Anders Hejlsberg 在[1]、[2] 中解释的那样:
当源类型和目标类型都是纯函数类型时,我们只进行高阶函数类型推断,即具有单个调用签名且没有其他成员的类型。
以上解决方案将React.forwardRef
再次使用泛型。
sma*_*c89 21
我通过阅读这篇博文发现了这个问题,我认为有一种比当前接受的答案提出的更直接的方法来实现这一点:
首先,我们定义一个接口来使用打字稿中称为调用签名的东西来保存组件的类型:
interface WithForwardRefType extends React.FC<WithForwardRefProps<Option>> {
<T extends Option>(props: WithForwardRefProps<T>): ReturnType<React.FC<WithForwardRefProps<T>>>
}
Run Code Online (Sandbox Code Playgroud)
请注意函数签名本身是如何声明为通用的,而不是接口 - 这是实现此功能的关键。该接口还扩展了 React.FC,以公开一些有用的组件属性,例如displayName
、defaultProps
等。
接下来,我们只需提供该接口作为组件的类型,并且无需指定 props 的类型,我们就可以将该组件传递给forwardRef
,剩下的就是历史了......
export const WithForwardRef: WithForwardRefType = forwardRef((
props,
ref?: React.Ref<HTMLDivElement>,
) => {
const { options, onChange } = props;
return (
<div ref={ref}>
{options.map((opt) => {
return (
<div
onClick={() => {
onChange(opt);
}}
>
{opt.label}
</div>
);
})}
</div>
);
});
Run Code Online (Sandbox Code Playgroud)
沙盒链接在这里
参考:
归档时间: |
|
查看次数: |
13196 次 |
最近记录: |