reactjs - 使用 [ key :string]: any to avoid needing an extra function for generics?

标签 reactjs typescript generics recompose

我在 React 中使用 recompose,特别是它的 compose 函数,它的签名是

function compose<TInner, TOutter>(
    ...functions: Function[]
): ComponentEnhancer<TInner, TOutter>;

interface ComponentEnhancer<TInner, TOutter> {
    (component: Component<TInner>): ComponentClass<TOutter>;
}

我在其中传递了一些 React 高阶组件。我将我的 TInnerTOuter 类型定义为接口(interface),它们描述了我的 HoC 运行所需的内容:

interface IOuterProps {
    somethingYouGiveMe: string;
}

interface IInnerProps {
    somethingIGiveBack: number;
}

const myEnhancer = compose<IInnerProps, IOuterProps>(myHoC, myOtherHoc);

(作为演示的愚蠢示例)。但问题是,我的组件有比这更多的 props,组合生成的 HoC 需要获取这些额外的 props 并传递它们,例如:

interface IMyComponentProps {
    somethingYouGiveMe: string;
    somethingElse: string;
}

因此,我不能执行 const MyEnhancedComponent = myEnhancer(MyComponent),因为编译器会提示 MyEnhancedComponent 没有 somethingElse作为 Prop 。

我找到了两个解决方法,但我都不满意。很好奇更有经验的 TypeScript 开发人员会做什么。

变通方法#1:引入一个函数

通过引入一个函数,我可以使用泛型并向编译器表达我在做什么

function myEnhancer<TInnerProps extends IInnerProps, TOuterProps extends IOuterProps>(Component: React.ComponentType<TInnerProps>) {
    return compose<TInnerProps, TOuterProps>(mYHoC, myOtherHoc)(Component);
}

我真的很不喜欢这个,引入一个函数来创建增强器的多个副本,只是为了获得泛型?我觉得不对。

变通方法 #2:使用 [key:string]: any

我可以改为将我的界面更改为

interface IOutterProps {
    somethingYouGiveMe: string;
    [key: string]: any;
}

interface IInnerProps extends IOuterProps {
    somethingIGiveBack: number;
}

我基本上使用 [key:string]: any 作为穷人的 extends IOutterProps。比如,我需要你给我这些 Prop ,如果你给我额外的东西,那么我真的不在乎那些。

这让我可以使用myEnhancer,只要我有满足要求的组件,避免添加功能,并且感觉比解决方法#1 更好。但也感觉必须添加 [key: string]: any 是不对的。

最佳答案

您可以使 ComponentEnhancer 中的函数签名通用:

declare function compose<TInner, TOutter>(
    ...functions: Function[]
): ComponentEnhancer<TInner, TOutter>;

interface ComponentEnhancer<TInner, TOutter> {
    // This is now a generic function 
    <TActualInner extends TInner, TActualOuterProps extends TOutter>(component: React.ComponentClass<TActualInner>): React.ComponentClass<TActualOuterProps>;
}

interface IOuterProps {
    somethingYouGiveMe: string;
}

interface IInnerProps {
    somethingIGiveBack: number;
}

const myEnhancer = compose<IInnerProps, IOuterProps>();
interface IMyComponentProps {
    somethingYouGiveMe: string;
    somethingElse: string;
}
interface IMyInnerComponentProps {
    somethingElse: string; 
    somethingIGiveBack: number;
}

class MyComponent extends React.Component<IMyInnerComponentProps>{ }
// We specify the actual inner and outer props
const MyEnhancedComponent = myEnhancer<IMyInnerComponentProps, IMyComponentProps>(MyComponent)

如果我们考虑转换工作的逻辑,我们甚至可以使用一些条件类型魔法来避免显式指定类型参数。如果我对 compose 的理解是正确的,那么结果组件将具有内部组件的所有属性,不包括 IInnerProps 的属性,包括 的属性>IOuterProps:

type Omit<T, TOmit> = { [P in Exclude<keyof T, keyof TOmit>] : T[P] }
type ComponentEnhancerProps<TActualInner, TInner, TOuter> = Omit<TActualInner, TInner> & TOuter;
interface ComponentEnhancer<TInner, TOutter> {
    <TActualInner extends TInner>(component: React.ComponentClass<TActualInner>): React.ComponentClass<ComponentEnhancerProps<TActualInner, TInner, TOutter>>;
}


class MyComponent extends React.Component<IMyInnerComponentProps>{ }
const MyEnhancedComponent = myEnhancer(MyComponent)

let d = <MyEnhancedComponent somethingYouGiveMe="0" somethingElse="" />

这种自动化方法的问题是可选字段成为必填项,如果启用了 strictNullChecks,则可以保留可选性

type ExcludeUndefined<T, TKeys extends keyof T> = { [P in TKeys]: undefined extends T[P]  ? never : P}[TKeys];
type Omit<T, TOmit> = 
    { [P in ExcludeUndefined<T, Exclude<keyof T, keyof TOmit>>] : T[P] } &
    { [P in Exclude<Exclude<keyof T, keyof TOmit>, ExcludeUndefined<T, Exclude<keyof T, keyof TOmit>>>] ? : T[P] }
type ComponentEnhancerProps<TActualInner, TInner, TOuter> = Omit<TActualInner, TInner> & TOuter;
interface ComponentEnhancer<TInner, TOutter> {
    <TActualInner extends TInner>(component: React.ComponentClass<TActualInner>): React.ComponentClass<ComponentEnhancerProps<TActualInner, TInner, TOutter>>;
}

interface IMyInnerComponentProps {
    somethingElse: string; 
    somethingIGiveBack: number;
    optionalProp?: number;
}


class MyComponent extends React.Component<IMyInnerComponentProps>{ }
const MyEnhancedComponent = myEnhancer(MyComponent)

let d = <MyEnhancedComponent somethingYouGiveMe="0" somethingElse="" />

关于reactjs - 使用 [ key :string]: any to avoid needing an extra function for generics?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50381005/

相关文章:

java - 泛型可以允许 Java 编译器检查映射中键和值的类型吗?

java - GenericDao<Order,capture#2-of ?> 类型中的方法 read(capture#2-of ?) 不适用于参数 (Long)

android - React Native - 共享和重用组件

reactjs - Webpack css-loader 不解析别名

javascript - 这是在钩子(Hook) react 中更新状态的正确方法吗?

javascript - 扩展 TypeScript 的控制台界面

node.js - 如何在带有 NodeJS 目标的 TypeScript 中正确使用模块和接口(interface)?

javascript - 访问容器中的Reducer返回未定义

typescript - 告诉 typescript Object.keys(foo) 的每个元素实际上是 foo 的键的最干净的方法是什么?

c# - C# 中的泛型,Dictionary<TKey,TValue>