typescript - 打字稿:在将可选类型与所需类型合并时创建联合而不是交集
问题描述
当 optional 和 required 属性通过交集合并时, required 获胜
type A = { who: string }
type B = { who?: string }
// $ExpectType {who:string}
type R = A & B
这可能会导致运行时错误,例如,在处理函数中的默认参数模式时
type Params = {
who: string
greeting: string
}
const defaults: Params = {
greeting: 'Hello',
who: 'Johny 5',
}
function greeting(params: Partial<Params>){
// $ExpectType Params
const merged = {...defaults, ...params}
return `${merged.greeting.toUpperCase()} ${merged.who} !`
}
// @throws - TypeError: Cannot read property 'toUpperCase' of undefined
greeting({greeting:undefined, who: 'Chuck'})
问题:
正如我所描述的是 TS 编译器的行为方式,问题是,如何创建映射类型,将交集解析为联合
所以像:
type SafeMerge<A,B>=....
// $ExpectType {greeting?: string | undefined, who?: string | undefined }
type Result = SafeMerge<Params, Partial<Params>>
混合类型的示例:
// $ExpectType {one?: number | undefined, two: string, three: boolean }
type Result = SafeMerge<{one: number, two:string}, {one?: number, three: boolean}>
解决方案
获得一个合并类型,它是两种类型与每个属性的合并,一种可能性的联合是很简单的。我们可以在两种组成类型的键上使用映射类型:
type SafeMerge<T, U> = {
[P in keyof T | keyof U] :
| (T extends Partial<Record<P, any>> ? T[P] : never)
| (U extends Partial<Record<P, any>> ? U[P] : never)
}
type Result = SafeMerge<{one: number, two:string }, {one?: number, three: boolean }>
// Result:
// type Result = {
// one: number | undefined;
// two: string;
// three: boolean;
// }
上述解决方案的问题是我们失去了键的可选性(也是只读性,这对于这个用例可能不太关心)。同态映射类型保留修饰符,但不幸的是我们不能在这里使用一个,因为我们并不真正适合同态映射类型的任何模式({ [P in keyof T] : T[P] }
或者{[P in K]: T[P] }
其中 K 是类型参数K extends keyof T
,请参见Here和Here)。
我们可以提取可选键并使用两种映射类型,一种用于任何可选键(在至少一个组成部分中是可选的键),另一种用于必需键:
type OptionalPropertyOf<T> = Exclude<{
[K in keyof T]: T extends Record<K, T[K]>
? never
: K
}[keyof T], undefined>
type SafeMerge<T, U> = {
[P in OptionalPropertyOf<T> | OptionalPropertyOf<U>]?:
| (T extends Partial<Record<P, any>> ? T[P] : never)
| (U extends Partial<Record<P, any>> ? U[P] : never)
} & {
[P in Exclude<keyof T | keyof U, OptionalPropertyOf<T> | OptionalPropertyOf<U>>]:
| (T extends Partial<Record<P, any>> ? T[P] : never)
| (U extends Partial<Record<P, any>> ? U[P] : never)
}
type Result = SafeMerge<{one: number, two:string, three: number}, {one?: number, three: boolean}>
// Result:
// type Result = {
// one?: number | undefined;
// } & {
// two: string;
// three: number | boolean;
// }
type Id<T> = {} & { [P in keyof T]: T[P] }
type FlattenedResult = Id<SafeMerge<{one: number, two:string, three: number }, {one?: number, three: boolean}>>
// type FlattenedResult = {
// one?: number | undefined;
// two: string;
// three: number | boolean;
// }
可选地,我们可以Id
像我在上面的示例中所做的那样使交叉点变平,但这是可选的。
推荐阅读
- user-controls - 当调用其容器的 Refresh() 方法时,是否应该自动调用 UserControl 的 Refresh() 方法?
- flutter - 是否可以在 OrientationBuilder 中包装 SliverGrid(是 CustomScrollView 的子项)?
- asp.net-web-api2 - 如何在 odata 控制器中实现与常规 PUT 相同的 POST 以更新实体
- azure - 当我想在 Postman 中访问受 Azure AD 保护的 Azure 函数 API 时,为“范围”选项赋予什么价值
- xml - 需要帮助将 XML 从行转换为 XML 输出中的元素
- angular - 基于特征检测的条件角动画
- reactjs - 打字稿问题:“HelpGetResponse”类型的参数不可分配给“SetStateAction”类型的参数
' - amazon-sns - 为什么我在 SNS 控制台中看到“未授权访问此订阅”?
- excel - 使用 VLookup 查找匹配的字符串,然后在行索引处返回列
- postgresql - Postgres 循环表(从 1 开始序列并重写旧行)