距离 typescript4.x 版本发布已经有一段时间,目前最新版本为 4.1.3。相较于 3.x 版本,4.x 又支持哪些新特性呢?
4.0 新特性:
可变元组类型
ts4 在 ts3 元组类型的基础上扩充了三点功能:
1.支持 spread 运算符在元组类型中作为泛型
通过这种方式,开发者可以定义元素类型不确定的数组或元组,同时编译器也可以自动推断出数组或元组的元素类型。
function tail(arr: readonly [any, ...T]) {
const [_ignored, ...rest] = arr;
return rest;
}
const myTuple = [1, 2, 3, 4] as const;
const myArray = ["hello", "world"];
// type [2, 3, 4]const r1 = tail(myTuple);
// type [2, 3, 4, ...string[]]const r2 = tail([...myTuple, ...myArray] as const);
2.spread 扩展的 rest 类型不必放到最后定义
ts4 中,可变元组类型不再要求 rest 类型放到最后定义。
type Strings = [string, string];
type Numbers = [number, number];
// [string, string, number, number, boolean]type StrStrNumNumBool = [...Strings, ...Numbers, boolean];
/*ts3 error 'A rest element must be last in a tuple type'!ts4 ok!*/
结合以上两点,开发者将不再需要编写繁琐的重载定义,以 function concat(arr1, arr2) {return [...arr1, ...arr2]} 函数为例:
在 ts3 中只能通过显示定义函数重载才能明确 concat 的返回值类型,无奈的是,这种做法无法罗列所有情况并不是一种通用实现。
function concat(arr1: [], arr2: [A2]): [A2];
function concat(arr1: [A1], arr2: [A2]): [A1, A2];
function concat(arr1: [A1, B1], arr2: [A2]): [A1, B1, A2];
function concat(arr1: [A1, B1, C1], arr2: [A2]): [A1, B1, C1, A2];
function concat(arr1: [A1, B1, C1, D1], arr2: [A2]): [A1, B1, C1, D1, A2];
function concat(arr1: [A1, B1, C1, D1, E1], arr2: [A2]): [A1, B1, C1, D1, E1, A2];
function concat(arr1: [A1, B1, C1, D1, E1, F1], arr2: [A2]): [A1, B1, C1, D1, E1, F1, A2];
更一般的做法是将 concat 定义为 function concat(arr1: T[], arr2: U[]): Array 但是对于编译器来说,它无法确认 concat 返回值数组长度和不同元素类型在数组中的位置。
有了可变元组类型以后,开发者只需简单几个语句便可定义出覆盖所有情况的 concat 类型,并且编译器可以精确推断出返回值数组长度和元素类型在数组中的位置。
type Arr = readonly any[];
function concat(arr1: [...T], arr2: [...U]): [...T, ...U] {
return [...arr1, ...arr2];
}
// type = [number, number, string, string]const arr = concat([1,2], ['hello', 'world'])
元组标签
ts4 带来的关于元组类型第三点变化是支持对元组类型打标签,类似于 js 中解构变量重命名。
type Range = [start: number, end: number];
type Foo = [first: number, second?: string, ...rest: any[]];
type Bar = [first: string, number]; // error! Tuple members must all have names or all not have names.
如果对元组类型打标签,则其中所有类型必须都标签化,不允许一部分类型标签化,一部分没有。
元组标签主要用于定义函数的入参,通过元组标签,编译器可以更好的提示函数入参类型
根据构造函数推断类属性类型
ts4 之前,如果不显示定义类属性的类型,编译器就会默认推断类属性类型为 any,ts4 开始,编译器可以通过开发者在构造函数中的赋值行为推断类属性的类型,前提是开启 noImplicitAny 选项 。
class Square {
area; // number sideLength; // number
constructor(sideLength: number) {
this.sideLength = sideLength;
this.area = sideLength ** 2;
}
}
短路运算符
在 +=,-=,*=,/= 等快捷运算式之外,ts4 还支持了 &&=,||=,??= 三个短路运算符,以 ||= 为例
obj.prop ||= foo();
// 等价于下面两种表达式 ,注意与 obj.prop = obj.prop || foo() 的区别 !!
obj.prop || (obj.prop = foo());
if (!obj.prop) {
obj.prop = foo();
}
catch 错误允许置为 unknown
在 ts4 之前,try/catch 捕获的异常被编译器视为 any 类型,然而 catch 内部的处理逻辑也可能产生非预期的错误,这就要求对 catch 捕获的异常有更严格的类型定义,ts4 允许将异常显示定义成 unknown 类型,开发者需进一步判断异常具体类型。
try {
// ...}
catch (e: unknown) {
// error! // Property 'toUpperCase' does not exist on type 'unknown'. console.log(e.toUpperCase());
if (typeof e === "string") {
// works! console.log(e.toUpperCase());
}
}
自定义 JSX 工厂函数
从 ts4 开始,通过新增的 jsxFragmentFactory 选项或内联的 @jsxFrag 注释,开发者可以自定义 jsx Fragment 语法的工厂函数, 如:
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"jsx": "react",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment"
}
}
/** @jsx h */
/** @jsxFrag Fragment */
import { h, Fragment } from "preact";
let stuff = <>
>;
/* 编译输出为import { h, Fragment } from "preact";let stuff = h(Fragment, null,h("div", null, "Hello"));*/
其他新特性--incremental 和 --noEmitOnError 同时开启时,会自动缓存前一次编译的错误到 .tsbuildinfo 文件
允许同时开启 --incremental 和 --noEmit
vscode 支持将条件判断表达式转为可选链式操作符vscode 支持提示 /** @deprecated */ 注释
vscode 支持对单份 ts 代码文件编译提速
vscode 支持更智能的自动导入
不兼容变化ts3 中当设置了 useDefineForClassFields 时,在继承类的过程中,不允许 getter/setter 属性和其他属性的互相覆盖,ts4 中不再需要设置 useDefineForClassFields,默认禁止互相覆盖
被 delete 的对象属性必须是可选的
ts4 前与 AST 节点相关的工厂函数被新的工厂函数所替代,老的完全被废弃
参考资料Announcing TypeScript 4.0 | TypeScriptdevblogs.microsoft.com
-------------------------------------------------------------------------------------------
4.1 新特性:
模版字符串类型
es6 后新增了模版字符串,用户可以通过向字符串注入变量的方式来构造新的变量,ts4 则可以通过向字符串注入类型的方式来构造新的类型。
type World = "world";
type Greeting = `hello${World}`;
// same as// type Greeting = "hello world";
模版字符串类型可以用于:
1.简化复杂字符串类型的定义
ts4 之后,开发者可以简化对复杂字符串类型的定义,事实上模版字符串类型起到了自动组合类型的效果。
// ts3type Alignment = "top-left" | "top-center" | "top-right" | "middle-left" | "middle-center" | "middle-right"| "bottom-left" | "bottom-center" | "bottom-right"
// ts4type VerticalAlignment = "top" | "middle" | "bottom";
type HorizontalAlignment = "left" | "center" | "right";
type Alignment = `${VerticalAlignment}-${HorizontalAlignment}`
2.修改动态推导的的字符串类型
假设我们想对某个对象的属性变化进行监听并且定义了一个 makeWatchedObject 方法来监听对象属性。
let person = makeWatchedObject({
firstName: "Homer",
age: 42,
location: "Springfield",
});
person.on("firstNameChanged", (newName) => {
console.log(`firstName was changed to${newName}!`);
});
person.on("ageChanged", (newAge) => {
console.log(`age was changed to${newAge}!`);
});
person.on("locationChanged", (newLocation) => {
console.log(`location was changed to${newLocation}!`);
});
属性变化的事件名格式为 ${property}Changed,采用模版字符串类型定义 makeWatchedObject 类型:
type PropEventSource = {
on(eventName: `${string & keyof T}Changed`, callback: (v: any) => void): void;
};
declare function makeWatchedObject(obj: T): T & PropEventSource;
更进一步,我们可以通过模版字符串类型来推导 callback: (v: any) => void 中 v的类型:
type PropEventSource = {
on
(eventName: `${K}Changed`, callback: (v: T[K]) => void): void;
};
declare function makeWatchedObject(obj: T): T & PropEventSource;
映射类型属性重映射
在 ts4 之前的版本中,开发者可通过映射类型从旧类型中创建新类型,如常用 Partial 类型:
type Partial = {
[P in keyof T]?: T[P];
}
interface Props {
name: string
age: number
}
type NewProps = Partial
映射类型最大的作用是将旧类型的每个属性按照相同的方式映射到新类型中,像 Partial 就是把所有属性都映射成可选,但是在 ts4 之前的版本对属性的转换方式仅仅局限在增加属性修饰符(readonly,? 等)上:
type Readonly = {
readonly [P in keyof T]: T[P];
}
type Partial = {
[P in keyof T]?: T[P];
}
从 ts4 开始,将支持对属性更进一步的映射,开发者可以通过 as 保留字重新构造想要的属性。
1.属性重命名
type Getters = {
[K in keyof T as `get${Capitalize}`]: () => T[K]
};
interface Props {
name: string;
age: number;
location: string;
}
type NewProps = Getters;
/* 等于interface NewProps {getName: () => string;getAge: () => number;getLocation: () => string;}*/
2.去除属性
type RemoveField = {
[K in keyof T as Exclude]: T[K]
};
interface Props {
foo: string;
bar: number;
}
type NewProps = RemoveField;
/* 等于type NewProps = {bar: number;};*/
3.过滤属性
type GetMethods = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
interface Props {
foo(): number,
bar: boolean
}
type NewProps = GetMethods;
/* 等于type NewProps = {foo(): number;};*/
4.衍生属性
type DoubleProps = { [P in keyof T & string as `${P}1` | `${P}2`]: T[P] }
interface Props {
a: string,
b: number
}
type NewProps = DoubleProps;
/* 等于type NewProps = { a1: string, a2: string, b1: number, b2: number }*/
递归条件类型
ts3 支持通过特定条件判断来获取类型:
type IsString = T extends string ? true : false;
type A = IsString; // truetype B = IsString; // false
为了更好的支持类型推断,ts4 在条件类型的基础上又增加了可递归的条件判断,常用递归条件类型模式为:type A = T extends B ? A : T
比如常见的数组拍平函数 deepFlatten,假设其输入为任意嵌套的未知类型数组,对应的输出为推导的某类型数组,通过递归条件类型表达为:
// 可递归条件类型type ElementType =
T extends ReadonlyArray ? ElementType : T;
function deepFlatten(x: T): ElementType[] {
throw "not implemented";
}
// 全部返回 'number[]':deepFlatten([1, 2, 3]);
deepFlatten([[1], [2, 3]]);
deepFlatten([[1], [[2]], [[[3]]]]);
再一个经典的例子便是推断 await 语句的返回值类型:
type Awaited =
T extends PromiseLike ? Awaited : T;
type P1 = Awaited>; // stringtype P2 = Awaited>>; // stringtype P3 = Awaited | undefined>>; // string | number | undefined
值得注意的是,递归条件类型虽然强大,但是会额外增加编译器在检查 ts 类型时的耗时,出于编译性能考虑,ts 编译器会对递归条件类型的层级有所限制,一旦超过了这个限制,在编译时就会报错,开发者在使用递归条件类型时也应该控制好输入的类型层级。
其他新特性新增 --noUncheckedIndexedAccess 编译选项,强制开发者显示校验属性是否存在
paths 可以不依赖 baseUrl 单独配置
支持配置 "jsx": "react-jsx|react-jsxdev" 来指定是否使用 React17 的 jsx 和 jsxs 工厂函数
编译器对 JSDoc @see 标签的支持
不兼容变化abstract 成员不能再被标记为 async
any/unknown 类型在 falsy 表达式中的类型推断方式改变
declare let foo: unknown;
declare let somethingElse: { someProp: string };
let x = foo && somethingElse;
/*ts3 x 类型为 { someProp: string }ts4 x 类型为 unknown*/Promise.resolve 函数参数不再是可选的
spread 运算符的扩展数据类型其对应属性变为可选
interface Person {
name: string;
age: number;
location: string;
}
interface Animal {
name: string;
owner: Person;
}
function copyOwner(pet?: Animal) {
return {
...(pet && pet.owner),
otherStuff: 123
}
}
/*ts3 返回类型为 { x: number } | { x: number, name: string, age: number, location: string }ts4 返回类型为 { x: number; name?: string; age?: number; location?: string;}*/
参考文章Announcing TypeScript 4.1 | TypeScriptdevblogs.microsoft.com