TypeScript 版本特性及技巧
2020-09-02 本文已影响0人
风之化身呀
一、版本特性
4.1
- 模板字符串中可以使用字符串类型
type World = "world";
type Greeting = `hello ${World}`;
// example1
type VerticalAlignment = "top" | "middle" | "bottom";
type HorizontalAlignment = "left" | "center" | "right";
declare function setAlignment(value: `${VerticalAlignment}-${HorizontalAlignment}`): void;
// example2
let person = makeWatchedObject({
firstName: "Homer",
age: 42, // give-or-take
location: "Springfield",
});
person.on("firstNameChanged", () => {
console.log(`firstName was changed!`);
});
type PropEventSource<T> = {
on(eventName: `${string & keyof T}Changed`, callback: () => void): void;
};
declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;
- Key Remapping in Mapped Types
// 新语法 k in keyof T as NewKeyType
type MappedTypeWithNewKeys<T> = {
[K in keyof T as NewKeyType]: T[K]
}
// example1
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
// equal to
type LazyPerson = {
getName: () => string;
getAge: () => number;
getLocation: () => string;
}
// example2
type RemoveKindField<T> = {
[K in keyof T as Exclude<K, "kind">]: T[K]
};
interface Circle {
kind: "circle";
radius: number;
}
type KindlessCircle = RemoveKindField<Circle>;
- Recursive Conditional Types
type ElementType<T> = T extends ReadonlyArray<infer U> ? ElementType<U> : T;
function deepFlatten<T extends readonly unknown[]>(x: T): ElementType<T>[] {
throw "not implemented";
}
// All of these return the type 'number[]':
deepFlatten([1, 2, 3]);
deepFlatten([[1], [2, 3]]);
deepFlatten([[1], [[2]], [[[3]]]]);
- --noUncheckedIndexedAccess 默认关闭,不随 --strictNullChecks 打开
interface Options {
path: string;
permissions: number;
[propName: string]: string | number;
}
const opt:Options
opt.xxx string|number|undefined
// 影响 for 循环,不影响for-of,forEach
function screamLines(strs: string[]) {
// This will have issues
for (let i = 0; i < strs.length; i++) {
console.log(strs[i].toUpperCase());
Object is possibly 'undefined'.
}
}
- paths without baseUrl
- checkJs Implies allowJs
- Breaking Changes
// resolve’s Parameters Are No Longer Optional in Promises
new Promise((resolve) => {
resolve() // error
});
// fix
new Promise<void>((resolve) => {
resolve() // work
});
4.0
- 可变元祖类型
1、元祖类型可以使用扩展运算符了,之前只能对数组类型用
2、扩展元算符可以用在任意位置了,之前只能用在最后一个参数
type IT = [string,boolean]
function tail<T extends any[]>(arr: readonly [any, ...IT]) {
const [_ignored, ...rest] = arr;
return rest;
}
type Strings = [string, string];
type Numbers = [number, number];
type StrStrNumNumBool = [...Strings, ...Numbers, boolean];
// 不限参数长度
type Strings = [string, string];
type Numbers = number[];
type Unbounded = [...Strings, ...Numbers, boolean];
// ==> type Unbounded = [string, string, ...(number | boolean)[]]
- 标记的元祖类型
下面这种写法
function foo(...args: [string, number]): void {
// ...
}
是这种提示:
而这种写法的提示语意上更为明确
type IRange = [start: string, end: number];
function foo(...args: IRange): void {
// ...
}
foo()
- 构造函数的类属性推断
现在类属性在构造函数中被初始化后会被推断出类型,之前一直是 any - 短路分配运算符
新加了&&=,||=,和??=
if(!a){
a=b
}
===>
a||=b
- catch 的 error 参数类型由 any 转为了 unknown
- 启动时的部分编译功能
4.0 支持在 VSCode 启动时优先编译当前工作区打开的文件,而不是等所有的项目都编译一遍才开始提供类型等信息
3.9
- 修复 Promise.all 类型推断错误问题
interface Lion {
roar(): void;
}
interface Seal {
singKissFromARose(): void;
}
async function visitZoo(
lionExhibit: Promise<Lion>,
sealExhibit: Promise<Seal | undefined>
) {
let [lion, seal] = await Promise.all([lionExhibit, sealExhibit]);
lion.roar(); // uh oh
// lion is possibly 'undefined'.
}
- 提供 ts-expect-error 注释
与 ts-ignore 的区别在于,如果下一行没有 ts 错误,ts-ignore 啥也不干,但 ts-expect-error 本身会报错 - 条件表达式中的未调用函数检查
条件表达式中只写函数名而没加括号会报错 - 解析可选链接和非null断言中的差异
// before, this transform is a bug
foo?.bar!.baz; => (foo?.bar).baz;
// now
foo?.bar!.baz; => foo?.bar.baz;
- 交叉类型和可选属性的更严格检查
interface A {
a: number; // notice this is 'number'
}
interface B {
b: string;
}
interface C {
a?: boolean; // notice this is 'boolean'
b: string;
}
declare let x: A & B;
declare let y: C;
// before is ok , now is error
y = x;
- getter/setter 不再可枚举
- 类型参数any不再扩展any
function foo<T extends any>(arg: T) {
arg.spfjgerijghoied; // before is ok , now is error!
}
3.x
- 支持 reference 配置
- ReadonlyArray 类型
数组内元素不能增加、修改和删除
function foo(arr: ReadonlyArray<string>) {
arr.slice(); // okay
arr.push("hello!"); // error!
}
也可写成 readonly string[],同时也支持 tuples 类型:readonly [string,number]
- const 断言
as const 可以将一些类型变为只读:
// Type '"hello"'
let x = "hello" as const;
// Type 'readonly [10, 20]'
let y = [10, 20] as const;
// Type '{ readonly text: "hello" }'
let z = { text: "hello" } as const;
- 3.5 引入 Omit 类型
type Person = {
name: string;
age: number;
location: string;
};
type QuantumPerson = Omit<Person, "location">;
// equivalent to
type QuantumPerson = {
name: string;
age: number;
};
- 3.7 引入可选链
// Before
if (foo && foo.bar && foo.bar.baz) {
// ...
}
// After
if (foo?.bar?.baz) {
// ...
}
- 3.7 引入空位合并
let x = foo ?? bar();
// equal to
let x = foo !== null && foo !== undefined ? foo : bar();
注意和 || 运算不同,|| 包含了更多 falsy 类的值,如 false,'',0 等
- 3.8 引入类型导入和导出
import type { SomeThing } from "./some-module.js";
export type { SomeThing };
- 3.8 真-私有字段
之前都是 private,但这只在编译阶段有约束,编译成JS后仍可访问。3.8 的私有字段语法为: #valuableName,编译成JS后无法访问,实现上用到了 WeakMap
class C {
#foo = 10;
cHelper() {
return this.#foo;
}
}
二、小技巧
- 枚举 key-value 互取
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green; // 2
let colorName: string = Color[Color.Green]; // 'Green'
- Record
如果定义一个对象类型,直接 Record<string,unknown>,而不是用 interface,interface写起来麻烦 - Partial
将一个已定义好的接口必选属性改为可选属性,类似的还有 Readonly,Required,Omit<T,K>,Pick<T,K> - 回调函数类型
type Cb = ()=>void
- 当为一个默认值定义类型时,可以用 type,而不是 interface。能少写代码
// bad
interface IState{
loading: boolean;
data:any[];
}
const defalutState:IState={
loading:true,
data:[]
}
// good
const defalutState = {
loading:true,
data:[]
}
type IState = typeof defalutState
- 导入其它非 js 类的文件,需要自己声明模块
declare module "*!text" {
const content: string;
export default content;
}
// Some do it the other way around.
declare module "json!*" {
const value: any;
export default value;
}
import fileContent from "./xyz.txt!text";
import data from "json!http://example.com/data.json";
- 类型冲突时,可以用 reference 指定使用哪个类型文件,比如,@types/node 和 @types/webpack-env 这两个文件都声明了 NodeJS,但有时 ts 找不到 webpack-env 里对 NodeJS 的 Module 接口扩展,导致 module.hot 会报错
- 巧用查找类型
interface Person {
addr: {
city: string,
street: string,
num: number,
}
}
当需要使用 addr 的类型时, 可以直接 Person["addr"]
- 巧用ClassOf
abstract class Animal extends React.PureComponent {
/* Common methods here. */
}
class Cat extends Animal {}
class Dog extends Animal {}
// `AnimalComponent` must be a class of Animal.
const renderAnimal = (AnimalComponent: Animal) => {
return <AnimalComponent/>; // WRONG!
}
上面的代码是错的,因为 Animal 是实例类型,不是类本身。应该
interface ClassOf<T> {
new (...args: any[]): T;
}
const renderAnimal = (AnimalComponent: ClassOf<Animal>) => {
return <AnimalComponent/>; // Good!
}
renderAnimal(Cat); // Good!
renderAnimal(Dog); // Good!
- 为第三方库写类型文件
首先我们需要明确包使用的导出规范,global/umd/commonjs/module 等
对于 global 导出的包我们使用:
declare namesapce MyLib {
class A {}
// 我们可以直接在代码中使用
// const a = new MyLib.A()
}
对于 umd/commonjs 导出的包我们使用:
declare module 'my-lib' {
namespace MyLib {
class A {}
class B {}
// 使用时
// 我们可以使用
// import * as MyLib from 'my-lib'
// const a = new MyLib.A();
// 如果开启了 ES Module 融合模式 (esModuleInterop=true)
// 我们可以使用
// import { A } from 'my-lib'
// const a = new A()
}
export = MyLib
}
对于 ES Module 导出的包我们使用:
declare module 'my-lib' {
class MyLib {}
export default MyLib
// or other exorts
export class A {}
// 我们可以使用
// import MyLib, {A} from 'my-lib'
// const lib = new MyLib()
// const a = new A()
}
- 常量枚举和普通枚举区别
常量枚举在编译阶段会被删除
export const enum T{
a=1
}
T.a // 会被编译为 1 /*a*/
enum T{
a=1
}
T.a 会被编译为
var ITest;
(function (ITest) {
ITest[ITest["a"] = 1] = "a";
})(ITest || (ITest = {}));
ITest.a
- 一份较全的 tsconfig.json 配置(编译上下文配置)
{
"compilerOptions": {
/* 基本选项 */
"target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在编译中的库文件
"allowJs": true, // 允许编译 javascript 文件
"checkJs": true, // 报告 javascript 文件中的错误
"jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相应的 '.d.ts' 文件
"sourceMap": true, // 生成相应的 '.map' 文件
"outFile": "./", // 将输出文件合并为一个文件
"outDir": "./", // 指定输出目录
"rootDir": "./", // 用来控制输出目录结构 --outDir.
"removeComments": true, // 删除编译后的所有的注释
"noEmit": true, // 不生成输出文件
"importHelpers": true, // 从 tslib 导入辅助工具函数
"isolatedModules": true, // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).
/* 严格的类型检查选项 */
"strict": true, // 启用所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
"strictNullChecks": true, // 启用严格的 null 检查
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
"alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
/* 额外的检查 */
"noUnusedLocals": true, // 有未使用的变量时,抛出错误
"noUnusedParameters": true, // 有未使用的参数时,抛出错误
"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
"noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
/* 模块解析选项 */
"moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相对模块名称的基目录
"paths": {}, // 模块名到基于 baseUrl 的路径映射的列表
"rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
"typeRoots": [], // 包含类型声明的文件列表
"types": [], // 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。
/* Source Map Options */
"sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
/* 其他选项 */
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true // 为装饰器提供元数据的支持
/* 编译文件选择 */
"files":[],
"include":[],
"exclude":[],
}
}