是否可以有条件地使映射类型字段为可选?

时间:2019-11-14 15:20:10

标签: typescript

在构建某些库的类型安全版本时,我遇到了涉及复杂映射类型的问题并陷入困境。

这是此问题的简化版本。我们有两种“装箱”类型-Strong<T>Weak<T>,可以分别认为它们是“持有价值”和“可能持有价值”。然后,有一个包含这些“框”的对象,我需要编写一个函数,通过将Strong转换为类型为{的必填字段,将其统一到一个容纳对象的大Strong<T>框中{1}}和T到类型Weak<T>的可选字段。稍后,在代码的另一部分中,我将“ T”到“ Strong<T>”的类型“拆箱”。

问题是我无法使这些字段成为条件可选的。有问题的代码如下:

T

Playground Link

一种尝试是获取所有对象值的并集,对其进行转换,然后使用UnionToIntersection进行转换,但是类型足够复杂,以至于类型推断陷入困境并吐出一堆type Strong<T> = { __phantom?: T, marker: 'strong' }; type Weak<T> = { __phantom?: T, marker: 'weak' }; // helper functions, to be used later function strong<T>(): Strong<T> { return { marker: 'strong' }; } function weak<T>(): Weak<T> { return { marker: 'weak' }; } // input type for my function type Rec = { [index: string]: Strong<unknown> | Weak<unknown> }; // output type for my function; how to define it? type Unified<T extends Rec> = { __phantom?: T, marker: 'unified' }; // put there something, just so that this type is unique function unify<T extends Rec>(input: T): Strong<Unified<T>> { // implementation is irrelevant now return {} as any; } // when we have the Strong value, we can 'unbox' the type type Unboxed<T> = T extends Strong<infer I> ? I : never; // ...so, how to make this compile? const unified = unify({ strong: strong<string>(), weak: weak<string>() }); const valid: Array<Unboxed<typeof unified>> = [{ strong: '' }, { strong: '', weak: '' }]; // ...and at the same time forbid this? const invalid: Array<Unboxed<typeof unified>> = [{}, {weak: ''}, {unknown: ''}]

如何定义unknown类型以正确地从输入中派生?

1 个答案:

答案 0 :(得分:0)

在讨论反向问题的Github issue中找到了关键点:如何确定类型的字段是必填字段还是可选字段。这是那里提供的解决方案:

Unified

使用类似的技术,我能够解决此问题。首先,我们需要两种类似于上面的帮助器类型:

export type OptionalPropertyNames<T> = {
    [K in keyof T]-?: undefined extends T[K] ? K : never
}[keyof T];
export type RequiredPropertyNames<T> = {
    [K in keyof T]-?: undefined extends T[K] ? never : K
}[keyof T];

这里的关键点在于,当我们为该类型建立索引时,在对象类型上定义的type OptionalPropertyNames<T> = { [K in keyof T]: T[K] extends Weak<any> ? K : never }[keyof T]; type RequiredPropertyNames<T> = { [K in keyof T]: T[K] extends Weak<any> ? never : K }[keyof T]; 值会有效消失。

然后,never类型只是两个映射类型的交集,每个映射类型都将其自身的一部分值拆箱:

Unified

Playground with solution