Reactjs 条件道具的TypeScript实用程序类型(基于类型中其他属性的输入值)
我经常需要定义一个类型对象,其中只有当该类型的另一个/多个属性是某个值时才接受属性键 一个简单的示例(在React的上下文中,但应适用于任何情况)是我需要一个类型Reactjs 条件道具的TypeScript实用程序类型(基于类型中其他属性的输入值),reactjs,typescript,typescript2.0,Reactjs,Typescript,Typescript2.0,我经常需要定义一个类型对象,其中只有当该类型的另一个/多个属性是某个值时才接受属性键 一个简单的示例(在React的上下文中,但应适用于任何情况)是我需要一个类型按钮对象,该对象接受以下属性: type Button = { size: 'small' | 'large'; appearance: 'solid' | 'outline' | 'minimal'; isDisabled?: boolean; hasFancyOutline?: boolean; } 现在,如果外观
按钮对象,该对象接受以下属性:
type Button = {
size: 'small' | 'large';
appearance: 'solid' | 'outline' | 'minimal';
isDisabled?: boolean;
hasFancyOutline?: boolean;
}
现在,如果外观
不是大纲
并且被禁用
是假
,我实际上不希望类型接受hasFancyOutline
正确的方法是:
type SharedButtonProps = {
size: 'small' | 'large';
}
type NonOutlineButtonProps = SharedButtonProps & {
appearance: solid' | 'minimal';
isDisabled?: boolean;
}
type OutlineButtonProps = SharedButtonProps & {
appearance: 'outline';
isDisabled: false;
hasFancyOutline?: boolean;
}
type Button = NonOutlineButtonProps | OutlineButtonProps
我想编写一个名为ConditionalProps
的速记实用程序类型,它可以智能地为我实现这一点。大概是这样的:
type Button = ConditionalProps<
{
size: 'small' | 'large';
appearance: 'solid' | 'outline' | 'minimal';
isDisabled?: boolean;
},
{
appearance: 'outline';
isDisabled: false;
hasFancyOutline?: boolean;
}
>
type ConditionalProps<BaseProps, ConditionalProps> = {
// 1. Find keys with the same name in BaseProps & ConditionalProps. Optional and non-optional types such as `isDisabled?` and `isDisabled` need to be matched.
type MatchingProps = Match<BaseProps, ConditionalProps> // { appearance: 'solid' | 'outline' | 'minimal', isDisabled?: boolean }
type SharedProps = Omit<BaseProps, MatchingProps> // { size: 'small' | 'large' }
// 2. Find what's the values of the props if they don't match the condition, e.g. 'appearance' would be either 'solid' or 'minimal'
type FailConditionProps = RemainingValues<MatchingProps, ConditionalProps> // { appearance: 'solid' | 'minimal'; isDisabled?: boolean; }
// 3. Assemble
type FailConditionPlusSharedProps = SharedProps & FailConditionProps
type PassConditionPlusSharedProps = SharedProps & ConditionalProps
return FailConditionPlusSharedProps | PassConditionPlusSharedProps
}
说我想做:
isReallyBig?
仅当size='large'
hasFancyOutline?
&outlineBackgroundColor
仅当appearance='outline'
&isDisabled=false
isLoading
只有在isDisabled=true
时才能为true
如果我想重新编写ConditionalProps
以清晰地定义此类型,我将如何做?我在想实施应该是这样的:
type Button = ConditionalProps<
{
size: 'small' | 'large';
appearance: 'solid' | 'outline' | 'minimal';
outlineBackgroundColor: string;
isDisabled?: boolean;
},
[
[
{ size: 'large' },
{ isReallyBig?: boolean }
], [
{ appearance: 'outline', isDisabled: false },
{ hasFancyOutline?: boolean }
], [
{ isDisabled: true },
{ isLoading?: boolean }
]
]
>
type按钮=ConditionalProps<
{
尺寸:'小'|'大';
外观:“实心”|“轮廓”|“最小”;
outlineBackgroundColor:字符串;
isDisabled?:布尔值;
},
[
[
{size:'large'},
{isReallyBig?:boolean}
], [
{外观:'outline',isDisabled:false},
{hasFancyOutline?:布尔值}
], [
{isDisabled:true},
{isLoading?:布尔值}
]
]
>
这样的事情是可以实现的,还是有更好的方法来处理这种情况?在实现这一点时,我遇到的问题是,不清楚为什么只有外观
应该将其值从常见情况中删除isDisabled
是true | false
的并集,因此,在默认情况下,删除普通情况下的所有值将导致从isDisabled
中删除false
。这可能不是期望的行为
如果我们添加一个属性来说明判别式是什么,我们可以构建您想要的类型
type Button = ConditionalProps<
{
size: 'small' | 'large';
appearance: 'solid' | 'outline' | 'minimal';
isDisabled?: boolean;
}, 'appearance',
{
appearance: 'outline';
isDisabled: false;
hasFancyOutline?: boolean;
}
>
type RemoveCommonValues<T, TOmit> = {
[P in keyof T]: TOmit extends Record<P, infer U> ? Exclude<T[P], U> : T[P]
}
type Omit<T, K extends PropertyKey> = Pick<T, Exclude<keyof T, K>> // not needed in 3.5
type Id<T> = {} & { [P in keyof T]: T[P] } // flatens out the types to make them more readable can be removed
type ConditionalProps<T, TKey extends keyof TCase, TCase extends Partial<T>> =
Id<Omit<T, keyof TCase> & TCase>
| Id<RemoveCommonValues<T, Pick<TCase, TKey>>>
我们可以在多种情况下通过:
type Button = ConditionalProps<
{
size: 'small' | 'large';
appearance: 'solid' | 'outline' | 'minimal';
isDisabled?: boolean;
}, 'appearance' ,{
appearance: 'outline';
isDisabled: false;
hasFancyOutline?: boolean;
} | {
appearance: 'minimal';
isDisabled: false;
useReadableFont?: boolean;
}
>
// same as
type Button = {
size: "small" | "large";
appearance: "outline";
isDisabled: false;
hasFancyOutline?: boolean | undefined;
} | {
size: "small" | "large";
appearance: "minimal";
isDisabled: false;
useReadableFont?: boolean | undefined;
} | {
size: "small" | "large";
appearance: "solid";
isDisabled?: boolean | undefined;
}
type按钮=ConditionalProps<
{
尺寸:'小'|'大';
外观:“实心”|“轮廓”|“最小”;
isDisabled?:布尔值;
}“外观”{
外观:“轮廓”;
isDisabled:错误;
hasFancyOutline?:布尔值;
} | {
外观:“最小”;
isDisabled:错误;
useReadableFont?:布尔值;
}
>
//同
类型按钮={
尺寸:“小”|“大”;
外观:“轮廓”;
isDisabled:错误;
hasFancyOutline?:布尔值|未定义;
} | {
尺寸:“小”|“大”;
外观:“最小”;
isDisabled:错误;
useReadableFont?:布尔值|未定义;
} | {
尺寸:“小”|“大”;
外观:“实心”;
isDisabled?:布尔值|未定义;
}
如果我们想有更多的鉴别键,它不清楚如何工作,虽然这并不构成好。您可以传入多个键,但必须确保传入的案例涵盖所有可能的组合,因为任何值都将从结果中删除:
type Button = ConditionalProps<
{
size: 'small' | 'large';
appearance: 'solid' | 'outline' | 'minimal';
isDisabled?: boolean;
}, 'appearance' | 'size' ,{
appearance: 'outline';
size: 'small'
isDisabled: false;
hasFancyOutline?: boolean;
} | {
appearance: 'minimal';
size: 'small'
isDisabled: false;
hasFancyOutline?: boolean;
}
>
// same as
type Button = {
appearance: "outline";
size: "small";
isDisabled: false;
hasFancyOutline?: boolean | undefined;
} | {
appearance: "minimal";
size: "small";
isDisabled: false;
hasFancyOutline?: boolean | undefined;
} | {
size: "large";
appearance: "solid";
isDisabled?: boolean | undefined;
}
type按钮=ConditionalProps<
{
尺寸:'小'|'大';
外观:“实心”|“轮廓”|“最小”;
isDisabled?:布尔值;
},“外观”|“尺寸”{
外观:“轮廓”;
尺寸:“小”
isDisabled:错误;
hasFancyOutline?:布尔值;
} | {
外观:“最小”;
尺寸:“小”
isDisabled:错误;
hasFancyOutline?:布尔值;
}
>
//同
类型按钮={
外观:“轮廓”;
大小:“小”;
isDisabled:错误;
hasFancyOutline?:布尔值|未定义;
} | {
外观:“最小”;
大小:“小”;
isDisabled:错误;
hasFancyOutline?:布尔值|未定义;
} | {
大小:“大”;
外观:“实心”;
isDisabled?:布尔值|未定义;
}
没有最小的大的按钮是可能的。嗨,提香,太神奇了!非常感谢你的全面回答。如果我想使ConditionalProps
更加灵活和可重用,您有什么建议?理想情况下,我想让它成为一种基于各种值添加多个条件道具的简单方法。因此,以上面的例子为基础:``类型按钮={size:'small'|'large';外观:'solid'|'outline'|'minimal';isDisabled?:boolean;hasFancyOutline?:boolean;isAllCaps?:boolean;loadingText?:string;}``欢迎您。让我知道如果你发现任何问题,我可以帮助。对不起,我把回答弄糟了-我可以PM你问多一点关于你的答案吗?我正试图在高级TS类型方面做得更好。@StephenKoo你可以在gitter上PM我,我更喜欢IM:)但不确定如何使它更通用
type Button = ConditionalProps<
{
size: 'small' | 'large';
appearance: 'solid' | 'outline' | 'minimal';
isDisabled?: boolean;
}, 'appearance' ,{
appearance: 'outline';
isDisabled: false;
hasFancyOutline?: boolean;
} | {
appearance: 'minimal';
isDisabled: false;
useReadableFont?: boolean;
}
>
// same as
type Button = {
size: "small" | "large";
appearance: "outline";
isDisabled: false;
hasFancyOutline?: boolean | undefined;
} | {
size: "small" | "large";
appearance: "minimal";
isDisabled: false;
useReadableFont?: boolean | undefined;
} | {
size: "small" | "large";
appearance: "solid";
isDisabled?: boolean | undefined;
}
type Button = ConditionalProps<
{
size: 'small' | 'large';
appearance: 'solid' | 'outline' | 'minimal';
isDisabled?: boolean;
}, 'appearance' | 'size' ,{
appearance: 'outline';
size: 'small'
isDisabled: false;
hasFancyOutline?: boolean;
} | {
appearance: 'minimal';
size: 'small'
isDisabled: false;
hasFancyOutline?: boolean;
}
>
// same as
type Button = {
appearance: "outline";
size: "small";
isDisabled: false;
hasFancyOutline?: boolean | undefined;
} | {
appearance: "minimal";
size: "small";
isDisabled: false;
hasFancyOutline?: boolean | undefined;
} | {
size: "large";
appearance: "solid";
isDisabled?: boolean | undefined;
}