当我有一个类型属性,要求该类型中的另一个属性为特定值时,我的类型会变得过于复杂。我想写一个抽象逻辑的实用程序类型。
示例。假设我写了一个与正在构建的Text(React)组件相关的类型:
type Props = {
children: React.ReactNode; // Just a React type, not pertinent to the question
as: "p" | "span" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; // The html tag for the text
truncate?: boolean; // Whether to truncate the text when it is long
expanded?: boolean; // The truncated text is expanded when this is true
bigHeading?: boolean; // When `as` is `"h1" | "h2" | "h3" | "h4" | "h5" | "h6"`, this property makes a normal heading extra large.
}
除非expanded?
是truncate?
,否则 true
不会执行任何操作,除非bigHeading?
是as
,否则"h1" | "h2" | "h3" | "h4" | "h5" | "h6"
不会执行任何操作。因此,除非满足这些条件,否则我希望Props
类型禁止expanded
和bigHeading
。
我的解决方法是:
type BaseProps = {
children: React.ReactNode;
};
type NoTruncateProps = BaseProps & {
as: "p" | "span";
truncate?: false;
};
type TruncateProps = BaseProps & {
as: "p" | "span";
truncate: true;
expanded?: boolean;
};
type NoTruncateHeadingProps = BaseProps & {
as: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
bigHeading?: boolean;
truncate?: false;
};
type TruncateHeadingProps = BaseProps & {
as: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
bigHeading?: boolean;
truncate: true;
expanded?: boolean;
};
type Props = NoTruncateProps | TruncateProps | NoTruncateHeadingProps | TruncateHeadingProps
这显然太复杂了。我将如何简化和抽象这种逻辑以实现可重用性?
答案 0 :(得分:2)
这种类型的逻辑非常复杂,因此无论哪种方式,该类型都将非常复杂。我将使用的方法是将每个逻辑分解为单独的类型,然后将它们相交。
对于truncate
和expanded
,我们将得到:
type Truncatable = {
truncate: true;
expanded?: boolean
} | {
truncate?: false;
}
对于as
和bigHeading
,我们将得到:
type HeadingOrText = {
as: "p" | "span"
} | {
as: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
bigHeading?: boolean;
}
然后我们可以将它们相交以获得所需的最终结合。尽管here概述了过多的属性检查,但有一个复杂之处,我们将使用此处提供的解决方案。我们还可以使用Id
类型展平道具,以获得更好的错误和工具提示:
type UnionKeys<T> = T extends any ? keyof T : never;
type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>
type Id<T> = {} & { [P in keyof T]: T[P] }
type Truncateable = {
truncate: true;
expanded?: boolean
} | {
truncate?: false;
}
type HeadingOrText = {
as: "p" | "span"
} | {
as: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
bigHeading?: boolean;
}
type WithChildren = {
children: React.ReactNode;
}
type Props = Id<StrictUnion<HeadingOrText & WithChildren & Truncateable>>
let h1: Props = {
as: "h1",
bigHeading: true,
truncate: true,
expanded: false,
children: []
}
let hErr: Props = {
as: "h1",
bigHeading: true,
truncate: false,
expanded: false, // causes error
children: []
}
let p: Props = { // ok
as: "p",
children: []
}
let pErr: Props = {
as: "p",
children: [],
bigHeading: true // causes error
}
虽然不一定需要更少的代码,但我认为此解决方案可以很好地组合在一起,并允许在联合中添加其他类似mixin的类型。