“同态映射类型”是什么意思?

时间:2020-01-17 15:41:46

标签: typescript

我在一些TypeScript PR中看到了“同态映射类型”一词。这是一个示例:https://github.com/microsoft/TypeScript/pull/21919

在--strictNullChecks模式下,当同态映射类型删除?时。基础类型的属性中的修饰符,它也会从该属性的类型中删除未定义

什么是同态映射类型?同态到底是什么?是否有一个很好的例子,说明同态映射类型

我感到困惑的原因是homomorphism是两个保留特定操作的结构之间的映射。这里有什么问题?也就是说,在f是映射的情况下,以下等式中的op是什么:

f(x op y) = f(x) op f(y)

我尝试过的

我尝试假设op&,该操作与两种类型相交。

同构映射将是这样的:

F<T & U> = F<T> & F<U>

同态映射(来自the TS handbook)的示例是:

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

因为Partial<T & U>始终与Partial<T> & Partial<U>相同。

问题是我无法提出使映射类型非同态的任何方法!

即使是这样的傻子,也似乎是同态的:

type Silly<T> = { [P in "foo"]: number}

让我感到困惑的是,Silly似乎是同构的(Silly <T & U> = Silly<T> & Silly<U>)。

这个似乎handbook所说的同态映射类型相反:

...同态的,这意味着映射仅适用于T的属性,而不适用于其他属性。编译器知道在添加任何新的属性修饰符之前,它可以复制所有现有的属性修饰符

Silly保留&,但根据手册中的定义不是同态映射类型。

1 个答案:

答案 0 :(得分:11)

在TypeScript中,同态映射类型特别是一种类型,编译器会在该类型中识别出您正在映射现有对象类型的属性。在这种情况下,输出对象类型的属性将具有与输入类型相同的readonly和/或可选(?)属性修饰符。我知道有几种使映射类型同态的方法,还有一些其他方法可以使之同构。

接下来,让我们将此类型用作映射的对象:

type Foo = {
    norm: string,
    opt?: string,
    readonly ro: string,
    readonly both?: string
};

主要同态映射类型技术,in keyof

type Hom1<T> = { [P in keyof T]: number };
type Hom2<T, U> = { [K in keyof (T & U)]: K extends keyof T ? "L" : "R" };
type Hom3 = { [Q in keyof { readonly a: string, b?: number }]: Q };

在上面,您明确地遍历了keyof某件事。让我们看看在Foo类型上使用它们会得到什么:

type Hom1Foo = Hom1<Foo>;
/* type Hom1Foo = {
    norm: number;
    opt?: number | undefined;
    readonly ro: number;
    readonly both?: number | undefined;
}*/

type Hom2FooDate = Hom2<Foo, { z: boolean }>
/*type Hom2FooDate = {
    norm: "L";
    opt?: "L" | undefined;
    readonly ro: "L";
    readonly both?: "L" | undefined;
    z: "R";
} */

type Hom3Itself = Hom3
/* type Hom3Itself = {
    readonly a: "a";
    b?: "b" | undefined;
} */

您可以看到在所有输出中,只读和可选标记是从输入中复制过来的。这是产生同态映射类型的主要技术,也是迄今为止最常见的技术。


次同态映射类型技术,in K,其中K extends keyof T是通用类型参数,T是通用类型参数:

// <K extends keyof T, T> ... {[P in K]: ...}
type Hom4<T, K extends keyof T> = { [P in K]: 1 };

这特别使我们能够从对象的键的 some 复制属性修饰符,并且implemented here主要是为了使the Pick<T, K> utility type同构。让我们看看Hom4Foo的行为:

type Hom4AllKeys = Hom4<Foo, keyof Foo>;
/* type Hom4AllKeys = {
    norm: 1;
    opt?: 1 | undefined;
    readonly ro: 1;
    readonly both?: 1 | undefined;
}*/

type Hom4SomeKeys = Hom4<Foo, "opt" | "ro">;
/* type Hom4SomeKeys = {
    opt?: 1 | undefined;
    readonly ro: 1;
}*/

现在几乎所有其他使用映射类型的方法都提供了 non 同态类型。如果您不认为自己映射到其他对象类型的键上,那么这并不是真正的问题。例如:

type NonHom0 = { [P in "a" | "b" | "c"]: 0 };
/* type NonHom0 = {
    a: 0;
    b: 0;
    c: 0;
}*/

NonHom0的属性既不是可选的也不是只读的。为什么会这样?没有其他类型可以使用键abc进行复制。如果您开始想象要从其他对象类型复制属性,事情会变得有些棘手,但是编译器不会这样看:

type NonHom1 = { [P in "norm" | "opt" | "ro" | "both"]: Foo[P] };
/* type NonHom = {
    norm: string;
    opt: string | undefined;
    ro: string;
    both: string | undefined;
}*/

type KeysOfFoo = keyof Foo
type NonHom2 = { [K in KeysOfFoo]: 1 }
/* type NonHom2 = {
    norm: 1;
    opt: 1;
    ro: 1;
    both: 1;
} */

type NonHom3 = { [Q in Extract<keyof Foo, string>]: Foo[Q] };
/* type NonHom3 = {
    norm: string;
    opt: string | undefined;
    ro: string;
    both: string | undefined;
}*/

在这些情况下,映射是非同态的;输出类型既没有只读属性也没有可选属性。 (| undefined仍然存在于以前是可选的属性上,但是属性本身不是可选的)。的确,您仍在遍历Foo的键,但是编译器不再看到与Foo的关系。在NonHom1中,键恰好是相同的,但是没有keyof,因此编译器不会将映射识别为同态。在NonHom2中,您使用的是keyof,但是编译器会急切地求值KeysOfFoo,以便到NonHom2时,它与{{1 }}。在NonHom1中,您仅遍历NonHom3的{​​{1}}键(全部),但是再次,编译器丢失了线程并且不再识别{{1} }作为同态映射的触发器。有workarounds, see microsoft/TypeScript#24679,但是这里的要点是,如果您偏离stringFoo-其中-in Extract<keyof Foo, string>-和两者-in keyof-并且- in K-通用,您将获得非同态映射。


我完成了。在此之后的几天里,我不想写“同态”一词。无论如何,希望能有所帮助;祝你好运!

Playground link to code