使用联合类型时,通常最简单的做法是使用带标签的联合创建ADT。但是,有时这是不可能的。
一个典型的例子是React Router的<Route/>
组件。这里有三个可选的道具:component
,render
和children
,但必须有1个,并且必须正确传递这3个中的1个。
为简单起见,我在这里简化了类型,但是当前发布的类型看起来像这样:
interface Route {
render?: string
component?: string
children?: string
someOtherProps?: any
}
他们应该真的更像这样:
interface BetterRouteRender extends Route {
render: string
component?: undefined
children?: undefined
}
interface BetterRouteComponent extends Route {
render?: undefined
component: string
chldren?: undefined
}
interface BetterRouteChildren extends Route {
render?: undefined
component?: undefined
Children: string
}
type BetterRoute =
| BetterRouteRender
| BetterRouteComponent
| BetterRouteChildren
const invalidRouteBecauseEmpty: BetterRoute = {}
const invalidRouteBecauseDupe: BetterRoute = { render: 'render', component: 'component' }
const validRoute: BetterRoute = { render: 'render' }
因此,有两个问题:
type BetterOptionalTypeConstructor<T, KS extends Array<keyof T>> = unknown
...将使用类似BetterOptionalTypeConstructor<Route, ['render', 'component', 'children']>
的方式,并吐出上面显示的BetterRoute
类型。
我还没有为此付出很多努力,但是据我所知,Typescript目前似乎不支持“映射联合类型”。在这方面的任何见识将不胜感激!
答案 0 :(得分:0)
在@Mickey发布的链接上进行了一些研究之后,我想到了以下解决方案。具体来说,this Github discussion很有启发性。
/**
* @typedef Without
*
* Takes two record types `T` and `U`, and outputs a new type where the keys
* are `keyof T - keyof U` and the values are `undefined | never`.
*
* Meant to be used as one operand of a product type to produce an XOR type.
*/
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
/**
* @typedef XOR
*
* Takes two record types `T` and `U`, and produces a new type that allows only
* the keys of T without U or the keys of U without T.
*/
type XOR<T, U> = (T | U) extends object
? (Without<T, U> & U) | (Without<U, T> & T)
: T | U;
// XOR is associative so we can compose it
type BetterRouteProps = RoutePropsCommon &
XOR<RoutePropsComponent, XOR<RoutePropsRender, RoutePropsChildren>>;
// TypeError: missing properties
const invalidRouteMissingProps: BetterRouteProps = {};
// TypeError: too many properties
const invalidRouteTooManyProps: BetterRouteProps = {
render: () => <div>Hello</div>,
component: () => <div>Hello</div>
};
// Valid!
const validRoute: BetterRouteProps = {
render: () => <div>Hello</div>,
component: undefined // this is allowed
};
如果有人感兴趣,我会写出更完整的分析书here。