【问题标题】:Is it possible to make mapped type field conditionally optional?是否可以使映射类型字段有条件地可选?
【发布时间】:2019-11-14 15:20:10
【问题描述】:

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

这是此问题的简化版本。我们有两种“盒装”类型——Strong<T>Weak<T>,对应的可以认为是“持有一个值”和“可能持有一个值”。然后,有一个包含这些“盒子”的对象,我需要编写一个函数,通过将 Strong<T> 转换为 T 和 @ 987654329@ 到 T 类型的可选字段。稍后,在代码的其他部分,我会将类型Strong<T>“拆箱”为T

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

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: ''}]

Playground Link

一种尝试是合并所有对象值,对其进行转换,然后使用UnionToIntersection 进行转换,但该类型足够复杂,以至于类型推断会卡住并吐出一堆unknowns。

如何定义Unified 类型以正确派生自输入?

【问题讨论】:

标签: typescript


【解决方案1】:

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

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 类型只是两个映射类型的交集,每个映射类型都在拆箱自己的值部分:

type Unified<T extends Rec> = {
    [K in OptionalPropertyNames<T>]?: T[K] extends Weak<infer I> ? I : never
} & {
    [K in RequiredPropertyNames<T>]: T[K] extends Strong<infer I> ? I : never;
}

Playground with solution

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-29
    • 2011-03-06
    • 2021-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-27
    • 2020-10-06
    相关资源
    最近更新 更多