简体   繁体   English

打字稿区分联合与交集

[英]Typescript discriminated union with intersection

Example sandbox 示例沙箱

I have a type我有一个类型

type TFormFieldFileProps = {
    componentProps: TFileUploaderProps;
    select?: never;
    checkbox?: never;
    file: true;
};

type TFormFieldSelectProps = {
    componentProps: TCustomSelectProps;
    select: true;
    checkbox?: never;
    file?: never;
};

type TFormFieldCheckboxProps = {
    componentProps: TCustomCheckboxProps;
    select?: never;
    checkbox: true;
    file?: never;
};

type TFormFieldInputProps = {
    componentProps: TCustomInputProps;
    select?: never;
    checkbox?: never;
    file?: never;
};

export type TFormFieldProps = { boxProps?: BoxProps } & (
    | TFormFieldCheckboxProps
    | TFormFieldInputProps
    | TFormFieldSelectProps
    | TFormFieldFileProps
);

I want to remove componentProps prop and instead set each type to be an intersection of componentProps prop type and the other select checkbox file type.我想删除componentProps道具,而是将每种类型设置为componentProps道具类型和其他select checkbox file类型的交集。

type TFormFieldFileProps = TFileUploaderProps & {
    select?: never;
    checkbox?: never;
    file: true;
};

type TFormFieldSelectProps = TCustomSelectProps & {
    select: true;
    checkbox?: never;
    file?: never;
};

type TFormFieldCheckboxProps = TCustomCheckboxProps & {
    select?: never;
    checkbox: true;
    file?: never;
};

type TFormFieldInputProps = TCustomInputProps & {
    select?: never;
    checkbox?: never;
    file?: never;
};

export type TFormFieldProps = { boxProps?: BoxProps } & (
    | TFormFieldCheckboxProps
    | TFormFieldInputProps
    | TFormFieldSelectProps
    | TFormFieldFileProps
);

But it doesn't work.但它不起作用。

const FormField = (props: TFormFieldProps) => {
    const { select, checkbox, file, boxProps, ...rest } = props;

    return (
        <Box
            {...boxProps}
            sx={{ '& > *': { width: 1 } }}
        >
            {select ? (
                // error: missing some property from TFormFieldCheckboxProps
                <CustomSelect {...rest} />
            ) : checkbox ? (
                // error: missing some property from TFormFieldInputProps
                <CustomCheckbox {...rest} />
            ) : file ? (
                // error: missing some property from ...
                <FileUploader {...rest} />
            ) : (
                // error: missing some property from ...
                <CustomInput {...rest} />
            )}
        </Box>
    );
};

I understand why it doesn't work but I don't understand how to solve this problem without having to specify each property on each type...我明白为什么它不起作用,但我不明白如何解决这个问题而不必指定每种类型的每个属性......

Can I make it work without writing all the props from all the types in all discriminated union types?我可以在不编写所有可区分联合类型中所有类型的所有道具的情况下使其工作吗? If so, how?如果是这样,怎么做?

For clarity, the issue here is that, while TypeScript 4.6 and above supports control flow analysis on destructured discriminated unions , this does not work for rest properties (as of TypeScript 4.7).为了清楚起见,这里的问题是,虽然 TypeScript 4.6 及更高版本支持对解构的可区分联合进行控制流分析,但这不适用于其余属性(从 TypeScript 4.7 开始)。

So, this works:所以,这有效:

interface Foo { type: "foo"; rest: { x: string } }
interface Bar { type: "bar"; rest: { y: number } }

const process = ({ type, rest }: Foo | Bar) =>
  type === "foo" ? rest.x : rest.y; // okay

but this fails:但这失败了:

interface Foo { type: "foo"; x: string }
interface Bar { type: "bar"; y: number }

const process = ({ type, ...rest }: Foo | Bar) =>
  type === "foo" ? rest.x : rest.y; // errors
// -------------------> ~ -----> ~
// Property does not exist on {x: string} | {y: number}

There's a recent open request at microsoft/TypeScript#46680 to support this, but it hasn't been implemented yet. microsoft/TypeScript#46680最近有一个开放请求来支持这一点,但它尚未实现。 You might want to give that issue a 👍 and/or mention your use case (and why it's compelling), but I don't know if it will have any effect.您可能想给这个问题一个 👍 和/或提及您的用例(以及它为什么引人注目),但我不知道它是否会产生任何影响。

Playground link to code Playground 代码链接

Currently when you shatter a type, the extracted variables are no longer associated with each other when parsed by the compiler.目前,当您分解类型时,提取的变量在编译器解析时不再相互关联。 Using if / switch /etc will no longer change the types of the other variables.使用if / switch /etc 将不再改变其他变量的类型。

const { select, checkbox, file, ...rest } = props;

/*
select is true | undefined
checkbox is true | undefined
file is true | undefined
rest is { checked: boolean; } | { variant: "filled" | "outlined"; } | { options: string[]; } | { ext: string[]; maxSize: number; }
*/

With the way your types are defined, you would have to use the following logic to type guard in a way the compiler understands:通过定义类型的方式,您必须使用以下逻辑以编译器理解的方式进行类型保护:

const FormField = (props: TFormFieldProps) => {
  let inputElement;
  if (props.select) {
    const { select, checkbox, file, options, ...rest } = props;
    inputElement = (
      <select { ...rest }>
        { options.map(o => (<option value={o}>{o}</option>)) }
      </select>
    );
  } else if (props.checkbox) {
    const { select, checkbox, file, ...rest } = props;
    inputElement = (<input type="checkbox" {...rest} />);
  } else if (props.file) {
    const { select, checkbox, file, ...rest } = props;
    inputElement = (<input type="file" {...rest} />);
  } else {
    const { select, checkbox, file, variant, ...rest } = props;
    // TODO: do something with variant as its not a valid <input> prop
    inputElement = (<input {...rest} />);
  }
  
  return inputElement;
}

Although, I would instead use a single type: "select" | "checkbox" | "file" | "custom"虽然,我会改为使用单一type: "select" | "checkbox" | "file" | "custom" type: "select" | "checkbox" | "file" | "custom" type: "select" | "checkbox" | "file" | "custom" property instead of select , file and checkbox . type: "select" | "checkbox" | "file" | "custom"属性而不是selectfilecheckbox

// FormField.ts
const FormField = (props: TFormFieldProps) => {
  switch (props.type) {
    case "select": { // <-- this brace is a container for the below const statement, not part of the switch statement
      const { type, options, ...rest } = props;
      return (
        <select { ...rest }>
          { options.map(val => (<option value={val}>{val}</option>)) }
        </select>
      );
    }
    case "checkbox":
      return (<input {...props} />);
    case "file": { // <-- same with this one
      const { ext, ...rest } = props;
      return (<input accept={ext.join(",")} {...props} />);
    }
    default: { // <-- and this one
      const { type, variant, ...rest } = props;
      // TODO: do something with variant as its not a valid <input> prop
      return (<input {...rest} />);
    }
  }
}
// types.ts
type TFileUploaderProps = {
  ext: string[];
  maxSize: number;
  type: "file"
};

type TCustomSelectProps = {
  options: string[];
  type: "select"
};

type TCustomCheckboxProps = {
  checked: boolean;
  type: "checkbox"
};

type TCustomInputProps = {
  variant: "filled" | "outlined";
  type: "custom"
};

export type TFormFieldProps =
  | TCustomCheckboxProps
  | TCustomInputProps
  | TCustomSelectProps
  | TFileUploaderProps;
// Usage:
(<FormField type="checkbox" checked />)
(<FormField type="custom" />)
(<FormField type="file" ext={["png", "jpg", "jpeg"]} maxSize=1024 />)
(<FormField type="select" options={["a", "b", "c"]} />)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 TypeScript 带可选判别式的判别联合 - TypeScript Discriminated Union with Optional Discriminant TypeScript 中的条件泛型类型使用 Discriminated Union - Conditional Generic Types in TypeScript use Discriminated Union TypeScript 区分联合不会为不存在的字段提供错误 - TypeScript discriminated union does not give error for non-existing fields React Typescript 可区分联合给出编译错误,但适用于 IDE - React Typescript discriminated union gives compiling error but works on IDE Typescript:具有 function 参数值的可区分联合解析为“任意” - Typescript: Discriminated Union with value in function parameter resolves to 'any' 对于具有扩展的可区分联合的 React 组件,TypeScript 错误“不可分配给类型‘IntrinsicAttributes’” - TypeScript error "not assignable to type 'IntrinsicAttributes'" for React component with extended discriminated union Typescript - 如何组合 Union 和 Intersection 类型 - Typescript - how to combine Union and Intersection types 记录和受歧视的工会访问 - Records and discriminated union access 当第一个属性在枚举内时,Typescript 可区分联合类型不强制执行所需的第二个属性 - Typescript discriminated union type not enforcing required second property, when first property is within enum 有什么方法可以添加鉴别器属性以使用 Axios + Typescript 将数据读入鉴别联合对象中吗? - Is there any way to add a discriminator property to read data into a discriminated union object with Axios + Typescript?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM