首页 > 解决方案 > 这种 DeepPartial 类型如何在 Typescript 中工作?

问题描述

我在这里查看了这个问题:

TypeScript:深度偏?

最佳答案的 DeepPartial 类型定义如下:

type DeepPartial<T> = {
    [P in keyof T]?: DeepPartial<T[P]>;
};

我进行了测试,这种类型按预期工作,但我无法完全理解它是如何在内部工作的。

让我们从同一个问题中获取示例界面:

interface Foobar {
  foo: number;
  bar: {
    baz: boolean;
    qux: string;
  };
}

当 P=bar 时,很容易理解它会递归地进入

DeepPartial<{
    baz: boolean;
    qux: string;
  }>

并使“baz”和“qux”键可选。

但我没有得到的是原始类型的递归是如何工作的?像fookey 一样,怎么DeepPartial<number>一样number?当 T=数字时,

[P in keyof number]?: DeepPartial<number[P]>;

对我来说没有意义。

在我看来,DeepPartial 的实现应该是这样的:

type DeepPartial<T> = {
    [P in keyof T]?: isPrimitive(T[P]) ? T[P] : DeepPartial<T[P]>;
};

但原始实现也有效,我不明白如何。

我希望我能很好地解释我的问题。这是示例游乐场:

https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgGIHt0CM5WQbwChlkZMAuZEAVwFstoBuY5HKSokknAL0q0wAbCHBDMuyAI7UAHpQDOYKKADm45AF9mGwoTABPAA4oAIhAiGACrjDA4ggDwAVAHzIAvARYkA2peSgyADWEProMMhOALoA-JRmFtZQtvbOflEu2syECOggiqSYbPHmVjZ2jhjYuG6enIXolACMADQsxQSscHzIStQoOlq6uflgvRCKAJLgJYnlqTT00LXIAKzMQA

编辑: 我进一步调查发现

type DeepPartial<T> = {
    [P in keyof T]?: any;
};

type DeepPartialTest<T> = {
        [P in keyof number]?: any;
    };

现在DeepPartial<number>将返回数字类型,但DeepPartialTest<number>不会。这对我来说更奇怪。

编辑2添加截图:

这按预期工作:

在此处输入图像描述

这不会:

在此处输入图像描述

实际上,两者应该是相同的吗?

标签: typescript

解决方案


我认为问题是“为什么作用于原语的映射类型会产生相同的原语,为什么它似乎只是有时发生”而不是DeepPartial本身,对吧?


映射类型有两种“风格”。有正常映射类型只迭代属性键文字的任意联合(例如,{[P in K]: ...}),以及专门迭代另一种类型的键(例如,)的同态{[P in keyof T]: ...}映射类型,并尝试通过以下方式保留该类型的结构,比如说,将修饰符readonly?从输入类型复制到输出类型(除非您使用自己的readonlyor?+or更改修饰符-)。例子:

interface Obj {
  a?: number,
  b: string,
  readonly c: boolean
}

type Homomorphic<T> = {
  [K in keyof T]: Array<T[K]>
}
type ObjHom = Homomorphic<Obj>
/* type ObjHom = {
    a?: (number | undefined)[] | undefined;
    b: string[];
    readonly c: boolean[];
} */

type ObjKeys = keyof Obj;

type ObjNonHom = {
  [K in ObjKeys]: Array<Obj[K]>
}
/* type ObjNonHom = {
    a: (number | undefined)[];
    b: string[];
    c: boolean[];
} */

在上面的代码中,ObjHomObjNonHom是类似的,但是ObjHom' 的a属性是可选的,它的c属性是readonly(就像 in 一样Obj);whileObjNonHom的属性都是必需且可变的。


能够确定特定映射类型何时是同态的可能很棘手,因为涉及到很多启发式方法。一般来说,根据未解析的泛型参数而具有某种类型in keyof T的映射类型将是同态的。T一般来说,一个映射类型以in XwhereX是某种类型而没有keyof直接在其中的类型将是非同态的。当映射类型特征in keyof TT一些非泛型类型时,事情就变得不确定了。对于像number这样的原语往往是非同态的,但对于对象类型它往往是同态的。我可能可以通过 GitHub 找到实现每个启发式的 PR,但我不知道是否值得任何人为此花时间。


在microsoft/TypeScript#12447中介绍了原语的特定行为。在描述中,它说:

形式的映射类型({ [P in keyof T]: X }其中T是某个类型参数)被称为 [同态] 映射类型,因为它生成的类型与 具有相同的形状T。[...] [当] 在同构映射类型中替换原始类型时T,我们只需生成该原始类型。例如,当{ [P in keyof T]: X }A | undefinedfor T 实例化时,我们产生{ [P in keyof A]: X } | undefined.


所以这个问题的基本答案是:当映射类型是同态的并且作用于原始类型时,就会出现相同的原始类型。原是DeepPartial同态的,所以number变成number。您的修改版本in keyof number直接是非同态的,因此number成为一种类型,每个明显的属性都number映射到可选属性。

Playground 代码链接


推荐阅读