在具有默认值的反应道具中使用区分联合
Using discriminating unions in react props with a default
我有一个具有 3 个变体的 React 组件。我的道具是这样输入的:
enum VariantType {
VARIANT_1 = "variant_1",
VARIANT_2 = "variant_2",
VARIANT_3 = "variant_3",
}
type BaseProps = {
a: string;
}
type Variant1Props = BaseProps & {
variant: VariantType.VARIANT_1;
b: never;
}
type Variant2Props = BaseProps & {
variant: VariantType.VARIANT_2;
b: number;
}
type Variant3Props = BaseProps & {
variant: VariantType.VARIANT_3;
b: boolean;
}
export const FancyComponent: FunctionComponent<Variant1Props | Variant2Props | Variant3Props> = (props) => {
const someNumber = props.variant === VariantType.VARIANT_2 ? props.b : 123;
...
}
在someNumber的赋值中TS知道是定义了b
因为我检查了变体。当使用组件时,当 b
存在时 TS 抱怨 VARIANT_1
并且当 b
不是 present/not 数字时它抱怨 VARIANT_2
(与 VARIANT_3
).
我现在正在寻找的是一种在使用组件时使 variant
选项可选的方法,它应该默认为 VARIANT_1
,您可以将其设置为 VARIANT_2
或 VARIANT_3
而不会失去我现在拥有的任何类型安全性。
我尝试了泛型等等,但无法正常工作。
我正在使用 Typescript 4.5。
一旦你破坏了你的 props
就太晚了,因为它们不再相互关联。相反,您需要区分对象本身,然后进行解构。您可以通过以下几种方式做到这一点:
type AllVariants = Variant1Props | Variant2Props | Variant3Props;
// Example 1
export const FancyComponent1 = (props: AllVariants): ReactElement => {
const someNumber = props.variant === VariantType.VARIANT_2 ? props.b : 123; // number
return <div></div>;
}
// Example 2: Using conditional
export const FancyComponent2 = (props: AllVariants): ReactElement => {
if (props.variant === VariantType.VARIANT_2) {
const {
a, // string
b, // number
variant, // VariantType.VARIANT_2
} = props;
}
return <div></div>;
}
// Example 3: Extracting conditional in #2 to predicate:
// https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates
function isVariant2 (props: AllVariants): props is Variant2Props {
return props.variant === VariantType.VARIANT_2;
}
export const FancyComponent3 = (props: AllVariants): ReactElement => {
if (isVariant2(props)) {
const {
a, // string
b, // number
variant, // VariantType.VARIANT_2
} = props;
}
return <div></div>;
}
只需将 Variant1Props
中的 variant
属性 设为可选,一切正常:
enum VariantType {
VARIANT_1 = "variant_1",
VARIANT_2 = "variant_2",
VARIANT_3 = "variant_3",
}
type BaseProps = {
a: string;
}
type Variant1Props = BaseProps & {
variant?: VariantType.VARIANT_1;
b: never;
}
type Variant2Props = BaseProps & {
variant: VariantType.VARIANT_2;
b: number;
}
type Variant3Props = BaseProps & {
variant: VariantType.VARIANT_3;
b: boolean;
}
// to check that props.b actually has type never
declare function expectTheUnexpected(wtf: never): void;
export const FancyComponent = (props: Variant1Props | Variant2Props | Variant3Props) => {
const someNumber = props.variant === VariantType.VARIANT_2 ? props.b : 123;
if (!props.variant) expectTheUnexpected(props.b);
// ...
}
但实际上,您不希望 b: never
——这是告诉 TS 期望(并要求)实际的 属性 b
,但 属性 是 never
。这意味着 { a: 'foo' }
不是有效的 Variant1Props
,因此 <FancyComponent a="foo" />
将不起作用。相反,有效的 Variant1Props
需要类似于 { a: 'foo', b: someVariableWithTypeNever }
,¹,这是一团糟。真的,never
很少是您实际期望使用的类型,它应该表示出现了问题,而我们现在处于类型系统认为不可能发生的情况。
你真正想要的Variant1Props
定义是
type Variant1Props = BaseProps & {
variant?: VariantType.VARIANT_1;
b?: undefined;
}
现在 b
可能实际存在也可能不存在,但如果您包括它,它必须只是 undefined
(这意味着它也可能不存在)。所以 { a: 'foo' }
有效,但 { a: 'foo', b: 42 }
无效——42
不是 undefined
。要允许输入 b: 42
,我们需要输入 variant: VariantType.VARIANT_2
.
- 即该组件是用
<FancyComponent a="foo" b={someVariableWithTypeNever} />
生成的——我将停止将 React 引入其中,它根本不会影响问题。
我有一个具有 3 个变体的 React 组件。我的道具是这样输入的:
enum VariantType {
VARIANT_1 = "variant_1",
VARIANT_2 = "variant_2",
VARIANT_3 = "variant_3",
}
type BaseProps = {
a: string;
}
type Variant1Props = BaseProps & {
variant: VariantType.VARIANT_1;
b: never;
}
type Variant2Props = BaseProps & {
variant: VariantType.VARIANT_2;
b: number;
}
type Variant3Props = BaseProps & {
variant: VariantType.VARIANT_3;
b: boolean;
}
export const FancyComponent: FunctionComponent<Variant1Props | Variant2Props | Variant3Props> = (props) => {
const someNumber = props.variant === VariantType.VARIANT_2 ? props.b : 123;
...
}
在someNumber的赋值中TS知道是定义了b
因为我检查了变体。当使用组件时,当 b
存在时 TS 抱怨 VARIANT_1
并且当 b
不是 present/not 数字时它抱怨 VARIANT_2
(与 VARIANT_3
).
我现在正在寻找的是一种在使用组件时使 variant
选项可选的方法,它应该默认为 VARIANT_1
,您可以将其设置为 VARIANT_2
或 VARIANT_3
而不会失去我现在拥有的任何类型安全性。
我尝试了泛型等等,但无法正常工作。
我正在使用 Typescript 4.5。
一旦你破坏了你的 props
就太晚了,因为它们不再相互关联。相反,您需要区分对象本身,然后进行解构。您可以通过以下几种方式做到这一点:
type AllVariants = Variant1Props | Variant2Props | Variant3Props;
// Example 1
export const FancyComponent1 = (props: AllVariants): ReactElement => {
const someNumber = props.variant === VariantType.VARIANT_2 ? props.b : 123; // number
return <div></div>;
}
// Example 2: Using conditional
export const FancyComponent2 = (props: AllVariants): ReactElement => {
if (props.variant === VariantType.VARIANT_2) {
const {
a, // string
b, // number
variant, // VariantType.VARIANT_2
} = props;
}
return <div></div>;
}
// Example 3: Extracting conditional in #2 to predicate:
// https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates
function isVariant2 (props: AllVariants): props is Variant2Props {
return props.variant === VariantType.VARIANT_2;
}
export const FancyComponent3 = (props: AllVariants): ReactElement => {
if (isVariant2(props)) {
const {
a, // string
b, // number
variant, // VariantType.VARIANT_2
} = props;
}
return <div></div>;
}
只需将 Variant1Props
中的 variant
属性 设为可选,一切正常:
enum VariantType {
VARIANT_1 = "variant_1",
VARIANT_2 = "variant_2",
VARIANT_3 = "variant_3",
}
type BaseProps = {
a: string;
}
type Variant1Props = BaseProps & {
variant?: VariantType.VARIANT_1;
b: never;
}
type Variant2Props = BaseProps & {
variant: VariantType.VARIANT_2;
b: number;
}
type Variant3Props = BaseProps & {
variant: VariantType.VARIANT_3;
b: boolean;
}
// to check that props.b actually has type never
declare function expectTheUnexpected(wtf: never): void;
export const FancyComponent = (props: Variant1Props | Variant2Props | Variant3Props) => {
const someNumber = props.variant === VariantType.VARIANT_2 ? props.b : 123;
if (!props.variant) expectTheUnexpected(props.b);
// ...
}
但实际上,您不希望 b: never
——这是告诉 TS 期望(并要求)实际的 属性 b
,但 属性 是 never
。这意味着 { a: 'foo' }
不是有效的 Variant1Props
,因此 <FancyComponent a="foo" />
将不起作用。相反,有效的 Variant1Props
需要类似于 { a: 'foo', b: someVariableWithTypeNever }
,¹,这是一团糟。真的,never
很少是您实际期望使用的类型,它应该表示出现了问题,而我们现在处于类型系统认为不可能发生的情况。
你真正想要的Variant1Props
定义是
type Variant1Props = BaseProps & {
variant?: VariantType.VARIANT_1;
b?: undefined;
}
现在 b
可能实际存在也可能不存在,但如果您包括它,它必须只是 undefined
(这意味着它也可能不存在)。所以 { a: 'foo' }
有效,但 { a: 'foo', b: 42 }
无效——42
不是 undefined
。要允许输入 b: 42
,我们需要输入 variant: VariantType.VARIANT_2
.
- 即该组件是用
<FancyComponent a="foo" b={someVariableWithTypeNever} />
生成的——我将停止将 React 引入其中,它根本不会影响问题。