Bootstrap

重温Typescript

对TS有些生疏了,想着来总结一波,可作为日后工作中的快速参考手册。

TypeScript具有类型系统,且是JavaScript的超集。

它可以编译成普通的JavaScript代码。 TypeScript支持任意浏览器,任意环境,任意系统并且是开源的。

TS是JS的超集,我的理解是:JS + 类型系统 = TS,对于熟悉JS的同学来说,TS的学习成本很低!

项目引入TS好处也很明显:

  • 让程序更加健壮,更易维护。

  • 在编译器中的类型提示让一些低级错误在编译阶段就被发现。

  • 避免一些无效的沟通。

👂🏻听说你很会Ts? 来,让我看看👁👁!!

初学者建议移步 【TS中文网】 进行学习

若行文有误,欢迎指出👏🏻👏🏻👏🏻

一些准备工作

这些工作更多的是为了测试ts特性。
当然这不是必须的,也可以到 Typescript练习场 去练习和测试。

安装 typescriptts-node 。前者是安装一个ts环境,将编译ts文件为js,方便查看其编译情况,后者支持终端运行ts文件。

npm install -g  typescript # 全局安装 ts

tsc -v # 查看安装的版本
# Version 5.0.3

npm install -g ts-node # 全局安装 ts-node

ts-node -v
# v10.9.1

新建一个名 summary 的目录,进入目录新建 helloTS.ts 文件。

mkdir summary
cd summary
touch helloTs.ts

上面步骤进行顺利的话,此时可以初始化目录、编译文件、运行文件了。

helloTs.ts里写下一点想法

const hello: string = '你好,ts, 我顶你个肺啊,哈哈哈'

终端

tsc --init # 初始化,同目录下会生成 tsconfig.json 文件

tsc helloTs.ts # 编译,初次运行会在同目录下生成 helloTs.js 文件,这里面就是编译的结果

ts-node helloTs.ts # 执行 helloTs.ts 文件,有点像 node xxx.js

tsconfig.json 文件定义着 会对哪些文件进行编译,如何编译(细则,约束),见最后。

基础类型

变量类型声明 是 冒号+ 类型 对一个变量进行类型声明的(废话。

js的八大类型

let str: string = "ethan";
let num: number = 24;
let bool: boolean = false;
let und: undefined = undefined;
let nul: null = null;
let obj: object = {x: 1};
let big: bigint = 100n;
let sym: symbol = Symbol("me"); 

null && undefined

这两个值稍微特殊些,null,undefined 是所有类型的子类型,即可以赋值给任何类型

let obj:object ={};
obj = null;
obj= undefined;

const und: undefined = undefined;
const nul: null = null;
let str: string = nul;
let num: number = und;

如果开启严格模式:在 tsconfig.json 指定了 "strict": true,"strictNullChecks":true,运行会抛错,在编译器里就会有提示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xLxjyYGH-1680958995419)(/Users/ethanyin/Library/Application%20Support/marktext/images/2023-04-07-17-32-04-image.png)]

let aa: string = nul;// ❌不能将类型“null”分配给类型“string”
let b: number = und;// ❌不能将类型“undefined”分配给类型“number”。

空值(void

void 意思就是无效的, 一般作用在函数上,告诉别人这个函数没有返回值

function sayHello(): void {
  console.log("hello ts");
}

需要注意 null 赋值给 void 值在严格模式下会抛错

let c:void = undefined // 编译正确
let d:void = null // 严格模式下: ❌不能将类型“null”分配给类型“void”

never

never类型表示的是那些永不存在的值的类型。 例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}

枚举(enum

使用枚举我们可以定义一些有名字的数字常量。 枚举通过 enum 关键字来定义。

// 不赋值,默认从0开始~~
// 赋值后即从该值往后 依次累加
enum Anmimal {
  Cat,// 0
  Dog = 2,
  Num = Dog * 2, // 4
  Bird // 5,
  G = '123'.length // 计算成员
}
const cat: Anmimal = Anmimal.Cat;
const dog: Anmimal = Anmimal.Dog;
const num: Anmimal = Anmimal.Num;
const bird: Anmimal = Anmimal.Bird;
console.log(cat, dog, num, bird) // 0 2 4 5

异构枚举(Heterogeneous enums),指枚举值同时出现字符串和数字,尽量避免。

enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES",
}

常量枚举(constant enum),使用const关键字修饰的常量枚举在编译后会被删除

const enum Color {
  RED,
  PINK,
  BLUE,
}

const color: Color[] = [Color.RED, Color.PINK, Color.BLUE];
console.log(color); //[0, 1, 2]

//编译后
var color = [0 /* Color.RED */, 1 /* Color.PINK */, 2 /* Color.BLUE */];
console.log(color); //[0, 1, 2]

运行时枚举(Enums at runtime)

enum E { X, Y, Z }
function f(obj: { X: number }) {
  return obj.X;
}
// Works, since 'E' has a property named 'X' which is a number.
console.log(f(E)); // 0

编译时枚举(Enums at compile time)

enum LogLevel { ERROR, WARN, INFO, DEBUG, }
type LogLevelStrings = keyof typeof LogLevel; // 'ERROR' | 'WARN' | 'INFO' | 'DEBUG'
function printImportant(key: LogLevelStrings, message: string) {
  const num = LogLevel[key];
  if (num <= LogLevel.WARN) {
    console.log("Log level key is:", key); // Log level key is: ERROR
    console.log("Log level value is:", num); // Log level value is: 0
    console.log("Log level message is:", message); // Log level message is:This is a message
  }
}
printImportant("ERROR", "This is a message");

任意值(any

众所周知,TS的any大法无所不能,哈哈哈~~~~(玩笑

当在编程阶段给还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库,那就需要使用any了。

any 类型可赋值给任何类型,并接受任何类型赋值。

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // 可接受二次类型赋值
let str:string = notSure;// any可给任意类型赋值

let obj: any = 4;
obj.ifItExists(); // ifItExists 方法也许在运行时存在
obj.toFixed(); // toFixed 存在 (但是编译时不检查)

let list: any[] = [1, true, "free"];

unknown

unknownany 一样,所有类型都可以分配给 unknown

let value: unknown = 1;
value = "hello"; 
value = false; 

any 不同的是,unknown 的值只能赋值给 unknown any

let unv: unknown = 1;
let anyv: any = unv;
let str: string = unv; // ❌不能将类型“unknown”分配给类型“string”。

对象类型

在JavaScript中,我们分组和传递数据的基本方式是通过对象。在TypeScript中,我们通过对象类型(泛指)来表示这些类型。

这里的对象很抽象,记得刚学java时,老师说过:“万物皆可为对象~~”。你懂我意思吧😂。

数组

数组两种申明方式

const arr: number[] = [1,2];
const arr1:Array<number> = [1,2]; 

对象类型

这个对象就是实实在在的,具体的,对象分为 objectObject{}

object 仅仅指一个对象,不接受其他基础值的赋值,包括(null, undefined)。

let object: object;
// object = 1; // 报错:不能将类型“number”分配给类型“object”
// object = "a"; // 报错:不能将类型“string”分配给类型“object”。
object = {}; // 编译正确

Object 代表所有拥有 toStringhasOwnProperty 方法的类型,所以所有原始类型、非原始类型都可以赋给 Object(严格模式下 null 和 undefined 不可以)。{}同样,可以认为是Object的简写

let bigObj: Object;
bigObj = 1;
bigObj = "a";

let normalObj: {};
normalObj = 1;
normalObj = "a";

let object: object;
normalObj = {};

{} 和大 Object可以互相代替,用来表示原始类型(null、undefined 除外)和非原始类型;而小 object 则表示非原始类型。object 更像是 Object的子集。

另外注意,除了 Oject大小写可以相互赋值,而其他基础类型(number、string、boolean、symbol)大写赋值给小写会抛错,且没有意义。

❌ Don’t ever use the types Number, String, Boolean, Symbol, or Object These types refer to non-primitive boxed objects that are almost never used appropriately in JavaScript code.


https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#general-types

元祖(Tuple

元组类型允许表示一个 已知元素数量和类型 的数组,各元素的类型不必相同。可以理解为数组的扩充

const tuple: [string, number] = ['哈哈', 1];

type Either2dOr3d = [number, number, number?];// number可选

// ... 表示0或多个 boolean 值 的元素
type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];

// 只读元祖
const roTuple: readonly [string, number] = ['哈哈', 1]

扩展类型(Extending Types

对某一个类型 使用 extends 进行扩展

interface BasicAddress {
  name?: string;
  street: string;
  city: string;
  country: string;
  postalCode: string;
}

interface AddressWithUnit extends BasicAddress {
  unit: string;
}

const liveAdress: AddressWithUnit = {
  unit: "ts",
  name: "ethan",
  street: "secret",
  city: "secret",
  country: "china",
  postalCode: "dont know",
}

交叉类型(Intersection Types

交叉类型 就是跟 联合类型(Union Types,后面提到)相反,用 & 操作符表示,交叉类型就是两个类型必须存在

interface PersonA{ // interface 为接口
  name: string,
  age: number,
  birth?: Date; // 生日:YYYY-MM-DD
  hobby?: "football" | "tennis"; // 爱好 只能为 "football" 或 "tennis"
}

interface PersonB {
  name: string,
  gender: string
}

type PersonAWithBirthDate = PersonA & { birth: Date };

let person: PersonA & PersonB = { 
    name: "ethan",
    age: 18,
    gender: "男"
};

只读数组(ReadonlyArray Type

此类型的数组不能被改变

const roArray: ReadonlyArray<string> = ["red", "green", "blue"];
roArray[0] = 'change' // ❌类型“readonly string[]”中的索引签名仅允许读取
roArray.push('grey'); // ❌类型“readonly string[]”上不存在属性“push”。

以上情况 与只读修饰符 的效果一样

const roArray: readonly string[] = ["red", "green", "blue"];

最后需要注意的一点是,与只读属性修饰符不同,可赋值性在常规数组和只读数组之间不是双向(不可分配/赋值)的。

let xx: readonly string[] = [];
let yy: string[] = [];
 
xx = yy; // ok
yy = xx; // ❌类型 "readonly string[]" 为 "readonly",不能分配给可变类型 "string[]"。

roArray = yy; // 因为const定义的。❌类型 "readonly string[]" 为 "readonly",不能分配给可变类型 "string[]"
yy = roArray; // ❌类型 "readonly string[]" 为 "readonly",不能分配给可变类型 "string[]"。

函数

js 的一等公民,使用场景太多了

函数:支持给参数、返回值定义类型;默认参数依然适用 ;?表示y是可选参数

function func(x: number, def: boolean = false, y?: number): number {
  return y ? x + y : x; // 对于可选参数需要做判断
}

// 接口定义函数,后面讲
interface Add {
  (x: number, y: number): number;
}

// 声明一个箭头函数,=> 后面number 约定的是返回值类型
let Add: (baseValue: number, increment: number) => number =
    function(x: number, y: number): number { return x + y; };

支持剩余参数定义类型

function restFunc(...numbers: number[]): number {
  let sum = 0;
  for (let i = 0; i < numbers.length; i++) {
    sum += numbers[i];
  }
  return sum;
}

函数重载,函数重载真正执行的是同名函数最后定义的函数体,在最后一个函数体定义之前全都属于函数类型定义 ,不能写具体的函数实现方法,只能定义类型

function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
  return x + y;
}

约定了add 接收的参数为 一旦有数字或字符串,两个参数必须同为数字或字符串,且返回值也应该为数字或字符串。相当于约定了参数的一致性(入参,出参),当然这里只是演示,并不是说他的作用只有这样,毕竟入参和出参自己定义,但应该是一个强绑定的。

类型推断

对某个变量直接赋值,那么改变量的类型就会被推断为该值的类型

let number = 1;// 被推断为number类型

如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查.

let value;
value = 'ethan';
value = 7;

类型断言

对于某些数据,我比TS更加了解它的类型,那么这时候类型断言就适用。

类型断言: 手动指定一个值的类型

基本语法

支持两种方式

// 方式1
let str: any = "to be or not to be";
let strLength: number = (<string>str).length;
// 方式2
let str1: any = "to be or not to be";
let strLength1: number = (str1 as string).length;

非空断言

对于多类型的数据,某个场景下无法根据上下文确定其类型,那么非空断言可以支持排除 nullundefined 值,避免使用时不必要的判断。语法是 一个后缀表达符号 !(英文感叹号)

// 场景1
let value: null | undefined | string;
value!.toString();
value.toString(); // ❌value可能为 “null” 或“未定义”。

// 场景2
type funcType = () => number
function myFunc(func: funcType | undefined) {
  const num1 = func(); // “func”可能为“未定义”
  const num2 = func!();
}

确定赋值断言

某个变量被定义后,未赋值前使用就会报错,这时候可以利用确定赋值断言解决。相当于告诉TS,我后面会赋值的,你别瞎操心了,哈哈。

let value:number;
// 以下语句未赋值使用就会抛错
console.log(value); // ❌在赋值前使用了变量“value”。

//使用赋值断言
let value!:number;
console.log(value); // undefined

联合类型(Union Types

将二种及以上多种类型用 | 隔开的类型称为联合类型

let union: string | number; // union 支持赋值 string 或 number

type U = string[] | string; // 支持字符串数组或字符串

条件类型(Conditional Types

简单理解就像 js 的 三目运算符.结合代码很好理解。

SomeType extends OtherType ? TrueType : FalseType;

基本使用

interface Animal {
  live(): void;
}
interface Dog extends Animal {
  woof(): void;
}
 
type Example1 = Dog extends Animal ? number : string; // type Example1 = number
 
type Example2 = RegExp extends Animal ? number : string; // type Example2 = string

在某些情况下,可以避免写复杂的重载函数。下面是一个重载函数示例

interface IdLabel {
  id: number;
}
interface NameLabel {
  name: string;
}
 
function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
  throw "unimplemented";
}

利用条件类型改一下

type NameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;
function createLabel<T extends number | number>(nameOrId: T): NameOrId<T> {
    throw "unimplemented";
}

可以看到 nameOrId 做到了 入参类型不同,出参类型自动判断。很明显在只有两种条件的情况下很便捷。

泛型限制

与泛型结合使用时能更好的约束泛型。

type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;

type Flatten<T> = T extends any[] ? T[number] : T;

分配条件类型

当我们向条件类型传入联合类型,如果没有使用 [] 会出现映射联合类型的每个成员类型。

type ToArray<T> = T extends any ? T[] : never;
type ToArray2<T> = [T] extends [any] ? T[] : never;

type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]
type StrArrOrNumArr2 = ToArray2<string | number>; // (string | number)[]

类型别名

类型别名用 type 来给一个类型起个新名字。它只是起了一个新名字,并没有创建新类型。类型别名常用于联合类型。

type personType = {
   name: string,
   age: number  
}
let Ethan: personType;

type count = number | number[];
function hello(value: count) {}

类型保护(Type Guards

类型保护是运行时检查某些值,确保某些值在所要类型的范围内。(结合代码示例更方便理解

类型保护有四种方式

1,is(自定义类型保护)

function getLength(arg: any): pet is string {
    return arg.length;
}

2,typeof, 利用判断去做

function getType(val: any) {
  if (typeof val === "number") return 'number'
  if (typeof val === "string") return 'string'
  return '啥也不是'
}

3,instanceof, instanceof类型保护*是通过构造函数来细化类型的一种方式

function creatDate(date: Date | string){
  console.log(date)
  if(date instanceof Date){
      date.getDate()
  }else {
      return new Date(date)
  }
}

4,in

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(pet: Fish | Bird) {
  if ("swim" in pet) { // 如果pet里存在 swim方法则为Fish,就游起来
    return pet.swim();
  }
  return pet.fly(); // 否则一定为Bird,就飞起来
}

接口(interface

这,是个大玩意儿。

在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

接口命名一般首字母大写。

基本用法

// 此接口囊括了接口的大部分特性
interface InterA{
  prop1: string,
  prop2?: number, // 可选参数
  readonly prop3: boolean //只读参数
  readArr: ReadonlyArray<number> //只读数组,一经定义不能被修改
  birth?: Date; // 生日(可选):YYYY-MM-DD
  hobby?: "football" | "tennis"; // 爱好(可选), 只能为 "football" 或 "tennis"
  // 以下使用来扩展未知字段的,也就是说此接口除了以上字段还有许多不确定的字段。
  [prop: string | number]: any; // 索引签名, prop字段必须是 string类型 or number类型。
}

扩展

接口支持 extends 扩展更多的属性

interface InterA {
  // props
}
// InterB 在 InterA 的基础上扩展了 propb 属性
interface InterB extends InterA {
  propb: string
}

扩展多类型

interface Colorful {
  color: string;
}
interface Circle {
  radius: number;
}

interface ColorfulCircle extends Colorful, Circle {}

const cc: ColorfulCircle = {
  color: "red",
  radius: 42,
};

接口(interface)和类型(type)的区别

概念上,interface是一种具体的类型,而type仅是类型的一种别名。

大多数情况下,此两者使用起来差不多。都可以用来描述对象/函数的类型,都可以扩展

这里有一些区别

1,语法

// 定义
type MyTYpe = {
  name: string;
  say(): void;
}

interface MyInterface {
  name: string;
  say(): void;
}

// 扩展
type MyType2 = MyType & {
  sex:string;
}
let person:MyType2 = {
  name:'ethan',
  sex:'男',
  say(): void { console.log("hello ts") }
}

interface MyInterface2 extends MyInterface {
  sex: string;
}
let person1:MyInterface2 = {
  name:'ethan',
  sex:'男',
  say(): void { console.log("hello ts") }
}

当然,也支持互相扩展

interface MyInterface3 {
  sex: string;
}

type mixType = MyType & MyInterface3
interface mixInterface extends MyType { sex: string }

上面可能是小不同,下面的不同可能让它两在各自场景能各尽其用:

  • type 可以声明基本数据类型别名/联合类型/元组等,而 interface 不行
// 基本类型别名
type UserName = string;
type UserName = string | number;
// 联合类型
type Animal = Pig | Dog | Cat;
type List = [string, boolean, number];
  • interface 能够合并声明,而type 不行
interface Person {
  name: string
}
interface Person {
  age: number
}
// 此时Person同时具有name和age属性

泛型(Generics

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

假设一个场景:一个函数需要入参出参都是 string。可能会这样写

function func(arg:string):string { return arg }

现在需求改了,除了支持 string,还得支持 number,你可能会这样写

function func(arg:string | number):string | number { return arg }

某日,需求又至矣,修修改改无穷尽也??吾心有一马奔腾不止。。。
泛型就是解决这样的问题

基本使用

function func<T>(arg:T):T  {
  return arg;
}
func<string | number>('哈哈哈');
func('哈哈哈');// 不指定类型,ts会自动推断

let myIdentity: <U>(arg: U) => U = identity;

泛型中的 T 就像一个占位符、或者说一个变量,在使用的时候可以把定义的类型像参数一样传入,它可以原封不动地输出

Array 自身也是个泛型。

interface Array<Type> {
  length: number;
  pop(): Type | undefined;
  push(...items: Type[]): number;
  // ...
}

多个参数

function func<T, U>(arg:[T,U]):[T,U] {
  return arg;
}
func("ethan", 18);// ["ethan", 18]

泛型约束

即预先指定该泛型满足某些条件

示例1 基本用法

// 使用extends约束泛型
interface Lengthwise {
  length: number;
}
function getLength1<T extends Lengthwise>(arg:T):T  {
  console.log(arg.length); 
  return arg;
}

const str = getLength('ethan')
const arr = getLength([1,2,3])
const obj = getLength({ length: 1 })
const num = getLength1(1) // ❌类型“number”的参数不能赋给类型“Lengthwise”的参数。

示例2 泛型约束中使用类型参数

function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a");
getProperty(x, "m"); // ❌类型“"m"”的参数不能赋给类型“"a" | "b" | "c" | "d"”的参数泛型接口

泛型接口

演示 接口与泛型 如何搭配

interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

泛型类

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

泛型别名

type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];

泛型工具类型

1,typeof,typeof 的主要用途是在类型上下文中获取变量或者属性的类型

let p1 = { name: "ethan", age: 18, isAdult: true };
type People = typeof p1; // 返回的类型一般用type承接取个别名
function getName(p: People): string {
  return p.name;
}
getName(p1);

// typeof p1 编译后
type People = {
  name: string,
  age: number,
  isAdult: boolean,
};

如果p1为嵌套对象也适用。

2, keyof,此操作符是获取某种类型的所有键,返回的是联合类型。

interface Person {
  name: string;
  age: number;
}

type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join" 
type K3 = keyof { [x: string]: Person };  // string | number

// 也支持基础类型
let K1: keyof boolean; // let K1: "valueOf"
let K2: keyof number; // let K2: "toString" | "toFixed" | "toExponential" | ...
let K3: keyof symbol; // let K1: "valueOf"

3, in,用以遍历枚举类型

type Keys = "a" | "b" | "c"

type Obj =  {
  [p in Keys]: any
} // -> { a: any, b: any, c: any }

4, infer,在条件类型语句中,声明类型变量以使用它。

type ReturnType<T> = T extends (
  ...args: any[]
) => infer R ? R : any; // R承载了返回值的类型

5,extends,为泛型增加约束, 参考之前泛型。

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

6,[Type][key], 索引访问操作符

interface Person {
  name: string;
  age: number;
}

type Name = Person["name"]; // string

实用程序类型(Utility Types

1,Required,将类型的属性变成必选

Required<Type>

interface Person {
    name?: string,
    age?: number
}
const person: Required<Person> = {
    name: "ethan",
    age: 18
}

2,Partial,与 Required 相反,将所有属性转换为可选属性

Partial<Type>

interface Person {
    name: string,
    age: number
}
type User = Partial<Person>
const shuge: User = {// 输入Person的某些属性
  name:'ethan'
}

3,Extract,相当于取交集(参1∩参2)

Extract<Type, Union>

type T01 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T11 = Extract<string | number | (() => void), Function>; // () =>void

4,Exclude,相当于取差集(参1 - 参1∩参2)

Exclude<UnionType, ExcludedMembers>

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number

5,Readonly,将数组或对象的所有属性值转换为只读。

Readonly<Type>

interface Todo {
  title: string;
}
const todo: Readonly<Todo> = {
  title: "Delete inactive users",
}
todo.title = "Hello"; // ❌无法为“title”赋值,因为它是只读属性。

6,Record, 将参2的类型一一赋给参1的key

Record<Keys, Type>,等同于

type newType = {
  [key keyof keys]: Type
}
interface CatInfo {
  age: number;
  breed: string;
}
type CatName = "miffy" | "boris" | "mordred";
const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: "Persian" },
  boris: { age: 5, breed: "Maine Coon" },
  mordred: { age: 16, breed: "British Shorthair" },
};

7,Pick,从某些类型里面挑一些属性出来,方便复用

Pick<Type, Keys>

type Person = {
  name: string;
  age:number;
  gender:string
}

type P1 = Pick<Person, "name" | "age">; // { name: string; age: number; }

8,Omit,与Pick相反,从某些类型里面剔除一些属性出来,方便复用

Omit<Type, Keys>

interface Person {
  name: string,
  age: number,
  gender: string
}
type P1 = Omit<Person, "age" | "gender"> // { name: string }

9,ReturnType,获取函数返回值类型

ReturnType

type T0 = ReturnType<() => string>; // string

function f1(): { a: number; b: string };
type T1 = ReturnType<typeof f1>; // { a: number; b: string }

type T2 = ReturnType<<T extends U, U extends number[]>() => T>;// number[]

10,NonNullable,去除 null 和 undefined 类型

NonNullable<Type>

type P1 = NonNullable<string | number | undefined>; // string | number
type P2 = NonNullable<string[] | null | undefined>; // string[]

11,Paramenters, 获取函数类型的参数类型,结果是一个元祖(Tuple

Parameters<Type>

type T0 = Parameters<() => string>; // []

function f1(arg: { a: number; b: string }): void;
type T1 = Parameters<typeof f1>; // [arg: { a: number; b: string }]

12, InstanceType, 获取 构造函数的实例类型

InstanceType<Type>

class C {
  x = 0;
  y = 0;
}
type T0 = InstanceType<typeof C>; // C
type T1 = InstanceType<any>; // any

13, Awaited,比较新,获取异步函数中的await或Promises上的.then()方法 返回的类型.

Awaited<Type> | Released: 4.5

type A = Awaited<Promise<string>>; // string

type B = Awaited<Promise<Promise<number>>>; // number

type C = Awaited<boolean | Promise<number>>; // number | boolean

我的理解是为了更好的处理 async 函数内 await 的结果类型。

async function foo<T>(x: T) {
    const y: Awaited<T> = await x;
    // const y: Awaited<T>
    // const y: T before TS4.5
}

参考
What is the Awaited Type in TypeScript - Stack Overflow

https://www.typescriptlang.org/play

14, 大小写转换

Uppercase<StringType> | 全大写

Lowercase<StringType> | 全小写

Capitalize<StringType> | 首字母写

Uncapitalize<StringType> | 非首字母大写

很好理解,为了更加精确的规定字符串类型的值,值也是一种类型嘛。

type Greeting = "Hello, world";
type ShoutyGreeting = Uppercase<Greeting>;
// type ShoutyGreeting = "HELLO, WORLD"
const helloStr: ShoutyGreeting = "HELLO, WORLD";
const helloStr: ShoutyGreeting = "hello";// ❌不能将类型“"hello"”分配给类型“"HELLO, WORLD"”。

type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`;
type MainID = ASCIICacheKey<"my_app">;
// type MainID = "ID-MY_APP"

type Greeting = "Hello, world";
type QuietGreeting = Lowercase<Greeting>;
// type QuietGreeting = "hello, world"

type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>;
// type Greeting = "Hello, world"

type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;
// type UncomfortableGreeting = "hELLO WORLD"

还有更多的实用工具类,不一一例举了 ,可见 官网

类型兼容性

这两天在 思否 上看一些ts相关的问题,看到一个有趣的问题
javascript - 虚心请教关于TypeScript的问题? - SegmentFault 思否

有个回答讲到了类型兼容性,很有意思推荐大家去看看。我总结为三点(仅从回答)

  1. 当传入一个对象时,如果该对象包含了目标类型中不存在的属性,TypeScript 会忽略这些属性而不会报错

  2. 当传入一个对象时,必须该对象包含目标类型属性,这是类型兼容性的前提

  3. 在约定了函数参数类型时,在调用时传入具体的值而非变量(情况1)。那就会触发ts类型初始化,在类型不匹配的情况下从而报错!

示例

interface Pet {
  name: string; // name是否可选不影响结果
}
function greet(pet: Pet) {
  console.log("Hello, " + pet.name);
}

// 情况1
let dog = { name: "Lassie", owner: "Rudd Weatherwax" };
greet(dog); // OK

// 情况2
let anotherDog = { owner: "Rudd Weatherwax" };
greet(anotherDog); // ❌ 如下:
// 类型“{ owner: string; }”的参数不能赋给类型“Pet”的参数。
// 类型 "{ owner: string; }" 中缺少属性 "name",但类型 "Pet" 中需要该属性。

// 情况3
greet({ name: "Lassie", owner: "Rudd Weatherwax" }); // ❌ 如下:
// 类型“{ name: string; owner: string; }”的参数不能赋给类型“Pet”的参数。
// 对象字面量只能指定已知属性,并且“owner”不在类型“Pet”中。

详见 https://www.typescriptlang.org/docs/handbook/type-compatibility.html

配置文件(tsconfig.json

主要字段

  • extends - 继承配置, 例 “extends”: “./configs/base”,

  • files - 设置要编译的文件的名称;

  • include - 设置需要进行编译的文件,支持路径模式匹配;

  • exclude - 设置无需进行编译的文件,支持路径模式匹配;即使include包含了,但不能排除files的文件

  • compilerOptions - 设置与编译流程相关的选项,初始化的时候只会有此字段。

compilerOptions

{
  "compilerOptions": {

    /* 基本选项 */
    "target": "es5",           // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'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          // 为装饰器提供元数据的支持
  }
}

结尾

github 上面有个 类型体操 的项目,适合练习,进阶。

参考了一些网上的博客,甚至有些例子是直接copy, 非常感谢👏👏👏。

5分钟了解 TypeScript - TypeScript 中文手册

https://www.typescriptlang.org/

2022年了,我才开始学 typescript ,晚吗?(7.5k字总结) - 掘金

2022 typescript史上最强学习入门文章(2w字) - 掘金

「1.9W字总结」一份通俗易懂的 TS 教程,入门 + 实战! - 掘金

TypeScript 类型操作 - 掘金

;