映射类型如何保留原语而不是迭代其属性?

问题描述 投票:0回答:2

我在这里查看这个问题:

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”键可选。

但是我不明白的是,递归对于原始类型是如何工作的? 就像

foo
键一样,
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/JYOwLgpgTgZghgYwgAgGIHt0CM5WQbwChlkZMAuZEAVwFstoBuY5HKSokknAL0q0wAbCHBDMuyAI7UAHpQDOYKKADm45AF9mGwoTABPAA4oAIhAiGACrjDA4ggDwaAVAHzIAvARYkA2peSgy ADWEProMMHOALoA-JRmFtZQtvbOflEu2syECOggiqSYbPHmVjZ2jhjYuG6enIXolACMADQsxQSscHzIStQoOlq6uflgvRCKAJLgJYnlqTT00LXIAKzMQA

编辑: 我进一步调查后发现

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

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

现在

DeepPartial<number>
将返回 number 类型,但
DeepPartialTest<number>
不会。这让我觉得更奇怪了。

编辑2添加截图:

这按预期工作:

这不是:

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

typescript
2个回答
3
投票

我认为问题是“为什么作用于原语的映射类型会产生相同的原语,以及为什么它似乎只是有时发生”,而不是关于

DeepPartial
本身,对吗?


映射类型有两种“风格”。有些法线映射类型只是迭代属性键文字的任意联合(例如,

{[P in K]: ...}
),而同态映射类型专门迭代另一种类型的键(例如,
{[P in keyof T]: ...}
),并尝试通过将 readonly
?
 等修饰符从输入类型复制到输出类型来保留该类型的结构
(除非您使用自己的
readonly
?
+
更改修饰符)或
-
)。示例:

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[];
} */

在上面的代码中,

ObjHom
ObjNonHom
类似,但是
ObjHom
a
属性是可选的,并且它的
c
属性是
readonly
(就像
Obj
中一样);而
ObjNonHom
的属性都是必需的且可变的。


能够确定特定映射类型何时同态可能很棘手,因为涉及很多启发式方法。一般来说,具有

in keyof T
特征的映射类型(其中
T
是依赖于未解析的泛型参数的某种类型)将是同态的。一般来说,具有
in X
的映射类型(其中
X
是不直接包含
keyof
的类型)将是非同态的。当映射类型特征
in keyof T
(其中
T
)是某种非泛型类型时,事情就变得不确定了。对于像
number
这样的基元,这往往是非同态的,但对于对象类型,它往往是同态的。我可能可以浏览 GitHub 并找到实现每个启发式的 PR,但我不知道这是否值得任何人花时间。


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

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


所以这个问题的基本答案是:当映射类型是同态的并且作用于原始类型时,会产生相同的原始类型。原来的

DeepPartial
是同态的,所以
number
变成了
number
。直接使用
in keyof number
的修改版本是非同态的,因此
number
成为一种类型,其中
number
的每个明显属性都映射到可选属性。

Playground 代码链接


2
投票

这实际上是 TypeScript 中类型参数的内部机制,其中指出:

当在同构映射中用原始类型替换 T 时 类型,我们只需生成该原始类型即可。

您可以在这个拉取请求中阅读更多相关信息。

© www.soinside.com 2019 - 2024. All rights reserved.