最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Why doesn't discriminated union work when I omitrequire props? - Stack Overflow

programmeradmin0浏览0评论

I have a ponent which renders different elements based on a specific property, called type. It could have type definitions like this:

interface CommonProps {
  size: 'lg' | 'md' | 'sm';
}

interface SelectInputProps extends CommonProps {
  type: 'select';
  options: readonly Option[];
  selected: string;
}

interface TextInputProps extends CommonProps {
  type: 'text';
  value: string;
};

type InputProps = (SelectInputProps | TextInputProps) & ExtraProps;

function Field(props: InputProps): JSX.Element;

Now in my own ponent, I will access the properties of this ponent, like so:

import { ComponentProps } from 'react';

type FieldProps = ComponentProps<typeof Field>;

function MySpecialField(props: FieldProps) {
  if (props.type === 'select') {
    // this works
    const { options, selected } = props;
  }
  return <Field {...props} />
}

This works absolutely fine. It knows in my if block that props is SelectInputProps. However, I made one small change and it appeared to pletely break this mode of using the discriminated union.

type FieldProps = Omit<ComponentProps<typeof Field>, 'size'>;

In practice, here is what is happening

Why is this happening? Is there a way to fix it?

I have a ponent which renders different elements based on a specific property, called type. It could have type definitions like this:

interface CommonProps {
  size: 'lg' | 'md' | 'sm';
}

interface SelectInputProps extends CommonProps {
  type: 'select';
  options: readonly Option[];
  selected: string;
}

interface TextInputProps extends CommonProps {
  type: 'text';
  value: string;
};

type InputProps = (SelectInputProps | TextInputProps) & ExtraProps;

function Field(props: InputProps): JSX.Element;

Now in my own ponent, I will access the properties of this ponent, like so:

import { ComponentProps } from 'react';

type FieldProps = ComponentProps<typeof Field>;

function MySpecialField(props: FieldProps) {
  if (props.type === 'select') {
    // this works
    const { options, selected } = props;
  }
  return <Field {...props} />
}

This works absolutely fine. It knows in my if block that props is SelectInputProps. However, I made one small change and it appeared to pletely break this mode of using the discriminated union.

type FieldProps = Omit<ComponentProps<typeof Field>, 'size'>;

In practice, here is what is happening

Why is this happening? Is there a way to fix it?

Share Improve this question asked Jun 1, 2021 at 18:36 corvidcorvid 11.2k12 gold badges71 silver badges134 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 10

It's because the Omit<T, K> utility type does not distribute over union types in T. The implementation uses keyof T, and when T is a union, keyof T is only those keys that exist in all members of the union (keyof T is contravariant in T, so keyof (A | B) is equivalent to (keyof A) & (keyof B)). This is working as intended as per microsoft/TypeScript#31501.

Luckily, if you want a distributive version of Omit, you can write it yourself using a distributive conditional type:

type DistributiveOmit<T, K extends PropertyKey> =  
  T extends any ? Omit<T, K> : never;

And then you can see the change in behavior:

type MyFieldProps = DistributiveOmit<React.ComponentProps<typeof Field>, 'a'>;
/* type MyFieldProps = Omit<{
    type: 'select';
    options: Option[];
} & ExtraProps, "a"> | Omit<{
    type: 'text';
    value: string;
} & ExtraProps, "a"> */

which makes your code start working:

function MyField(props: MyFieldProps) {
  if (props.type === 'select') {
    const options = props.options;    // okay
  }
  return <input />
}

Playground link to code

function MyField(props: MyFieldProps) {
  if (props.type === 'select') {
    // Here's the problem
    const options = props.options;
  }
  return <input />
}
发布评论

评论列表(0)

  1. 暂无评论