Reactjs 条件道具的TypeScript实用程序类型(基于类型中其他属性的输入值)

Reactjs 条件道具的TypeScript实用程序类型(基于类型中其他属性的输入值),reactjs,typescript,typescript2.0,Reactjs,Typescript,Typescript2.0,我经常需要定义一个类型对象,其中只有当该类型的另一个/多个属性是某个值时才接受属性键 一个简单的示例(在React的上下文中,但应适用于任何情况)是我需要一个类型按钮对象,该对象接受以下属性: type Button = { size: 'small' | 'large'; appearance: 'solid' | 'outline' | 'minimal'; isDisabled?: boolean; hasFancyOutline?: boolean; } 现在,如果外观

我经常需要定义一个类型对象,其中只有当该类型的另一个/多个属性是某个值时才接受属性键

一个简单的示例(在React的上下文中,但应适用于任何情况)是我需要一个类型
按钮
对象,该对象接受以下属性:

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;
    }