[英]Typescript discriminated union with intersection
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.
您可能想给这个问题一个 👍 和/或提及您的用例(以及它为什么引人注目),但我不知道它是否会产生任何影响。
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"
属性而不是select
、 file
和checkbox
。
// 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.