TS4-1更新速报

Template Literal Types

当我们将字符串字面量当作一种独立的类型而不是字符串类型的一个实例时,注定着,围绕着字符串字面量这种类型,会有一系列的高阶类型可供我们使用.当我们考虑其与现有TS机制(in,|)的正交性时,反而能迸发出有趣的更强大的表达力.

组合 (construct)

就像类型可以组合一样,字符串字面量类型也可以组合来构成新的字符串字面量类型,在ts中这种组合类型的能力借助我们熟悉的的语法暴露出来

type str1 = "aaaa";

type str2 = `${str1}-bbb`
function a(m:str2) {

}
a("aaaa-bbb")
a("aaaa") # this will report a error

object propriety (union)

字符串字面量的union就像枚举一样也可以作为object的propriety

type o1 = {
    [k in  "a"|"v"]:number
}

/*
type o1 = {
    a: number;
    v: number;
}
*/

union and construct

当我们把union和construct结合到一起时会是怎么样的语义?

type A = "A1"|"A2";
type B = "B1"|"B2";
type C = "C1"|"C2";

type a = `[${A}|${B}|${C}]`

是笛卡尔积! 有人在里面加了笛卡尔!

type a = "A1:B1:C1" | "A1:B1:C2" | "A1:B2:C1" | "A1:B2:C2" | "A2:B1:C1" | "A2:B1:C2" | "A2:B2:C1" | "A2:B2:C2"

为了防止组合爆炸的问题,union的乘积最大是100,000

generic type (keyof,generic)

恩.. 总的来讲就是也能够在范型中使用字符串字面量类型

type PropEventSource<T> = {
    on(eventName: `${string & keyof T}Changed`, callback: () => void): void;
};
type PropEventSource<T> = {
    on<K extends string & keyof T>
        (eventName: `${K}Changed`, callback: (newValue: T[K]) => void ): void;
};


declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;

let person = makeWatchedObject({
    firstName: "Homer",
    age: 42,
    location: "Springfield",
});
// works! 'newName' is typed as 'string'
person.on("firstNameChanged", newName => {
    // 'newName' has the type of 'firstName'
    console.log(`new name is ${newName.toUpperCase()}`);
});

// works! 'newAge' is typed as 'number'
person.on("ageChanged", newAge => {
    if (newAge < 0) {
        console.log("warning! negative age");
    }
})

Utility Type

就像普通类型有Partial,Readonly等各种工具类型帮我们转换类型一样,对于literal string type也是同样,这次新加了Uppercase,Lowercase,Capitalize,Uncapitalize这几种顾名思义的工具类型,顺带一提这几个类型的实现都编译器处理的,看来我们离花式把玩字符串还是有点距离.

remap

考虑范型+keyof这个场景,

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

我们还能做什么? 当然是对K再次的进行操作

type MappedTypeWithNewKeys<T> = {
    [K in keyof T as NewKeyType]: T[K]
    //            ^^^^^^^^^^^^^
    //            This is the new syntax!
}

于是这里引入了一个as表达式
有了as就使用上来说我们是有了一个能够进行类型操作的场所

type RemoveKindField<T> = {
    [K in keyof T as Exclude<K, "kind">]: T[K]
};

Recursive Conditional Types

一个很经典的TS type问题是: 给定一个范型类型,要怎么样拿到那个被包裹的范型.

type Wrapper<T> = {
  inner: T;
};

function wrap<T>(a: T): Wrapper<T> {
  return { inner: a };
}

type Unwrap<T> = ?? 

const a = wrap(0);
type a_t = Unwrap<typeof a>; //how to get number?

这实际上就是conditional-types

type Unwrap<T> = T extends Wrapper<infer U> ? U : never;

问题在于假如我是多层嵌套的Wrapper呢?

const a = wrap(0);
type a_t = Unwrap<typeof a>; //number

const b = wrap(a);
type c  = Unwrap<typeof b>; // Wrapper<number> instead of number

这这种情况下很明显我们的Unwrap要能够自指,也就是递归(Recursive Conditional Types).

在之前版本的TS中对于这种Recursive Conditional Types,做了限制,这次的更新放宽了这种限制,使得我们能够在Conditional Type中自指.

type SuperUnwrap<T> = T extends Wrapper<infer U>? SuperUnwrap<U>:never

type d = SuperUnwrap<typeof b> ;// number

上述代码在Ts4.1之前会报错Type alias 'SuperUnwrap' circularly references itself.(2456)

Recursive Conditional Types Demo