使用 [key:string]: any 来避免泛型需要额外的功能?
Using [key:string]: any to avoid needing an extra function for generics?
我在 React 中使用重组,特别是它的 compose
函数,其签名为
function compose<TInner, TOutter>(
...functions: Function[]
): ComponentEnhancer<TInner, TOutter>;
interface ComponentEnhancer<TInner, TOutter> {
(component: Component<TInner>): ComponentClass<TOutter>;
}
我在其中传递了一些 React 高阶组件。我将 TInner
和 TOuter
类型定义为描述我的 HoC 运行所需的接口:
interface IOuterProps {
somethingYouGiveMe: string;
}
interface IInnerProps {
somethingIGiveBack: number;
}
const myEnhancer = compose<IInnerProps, IOuterProps>(myHoC, myOtherHoc);
(作为演示的愚蠢示例)。但问题是,我的组件有更多的道具,组合生成的 HoC 需要获取这些额外的道具并通过它们,例如:
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
。比如,我需要你给我这些道具,如果你给我额外的东西,那我真的不在乎那些。
这让我可以使用 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="" />
我在 React 中使用重组,特别是它的 compose
函数,其签名为
function compose<TInner, TOutter>(
...functions: Function[]
): ComponentEnhancer<TInner, TOutter>;
interface ComponentEnhancer<TInner, TOutter> {
(component: Component<TInner>): ComponentClass<TOutter>;
}
我在其中传递了一些 React 高阶组件。我将 TInner
和 TOuter
类型定义为描述我的 HoC 运行所需的接口:
interface IOuterProps {
somethingYouGiveMe: string;
}
interface IInnerProps {
somethingIGiveBack: number;
}
const myEnhancer = compose<IInnerProps, IOuterProps>(myHoC, myOtherHoc);
(作为演示的愚蠢示例)。但问题是,我的组件有更多的道具,组合生成的 HoC 需要获取这些额外的道具并通过它们,例如:
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
。比如,我需要你给我这些道具,如果你给我额外的东西,那我真的不在乎那些。
这让我可以使用 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="" />