TS常用关键字和工具类梳理

339 阅读7分钟

符号

& 交叉类型

交叉类型是一种将多种类型组合为一种类型的方法。 这意味着你可以将给定的类型 A 与类型 B 或更多类型合并,并获得具有所有属性的单个类型

type LeftType = {
    id: number;
    left: string;
};

type RightType = {
    id: number;
    right: string;
};

type IntersectionType = LeftType & RightType;

function showType(args: IntersectionType) {
    console.log(args);
}

showType({ id: 1, left: 'test', right: 'test' });
// Output: {id: 1, left: "test", right: "test"}

语法提示会提示联合后的所有的属性

同时,类型校验的时候也会校验交叉后的所有属性

| 联合类型

联合类型使你可得到多个类型中公共属性组成的类型,最后得到的类型只包含所有联合项的公用属性

type UnionType = string | number[];

function showType(arg: UnionType) {
    console.log(arg.length)
    arg.forEach() 
  	// 类型“UnionType”上不存在属性“forEach”。
  	// 
  类型“string”上不存在属性“forEach”。ts(2339)
}

showType([1])
showType('1')

联合类型只能拿到组合前的共有属性

! 非空断言操作符

在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined 。

那么非空断言操作符到底有什么用呢?下面我们先来看一下非空断言操作符的一些使用场景。

忽略 undefined 和 null 类型

function myFunc(maybeString: string | undefined | null) {
  // Type 'string | null | undefined' is not assignable to type 'string'.
  // Type 'undefined' is not assignable to type 'string'. 
  const onlyString: string = maybeString; // Error
  const ignoreUndefinedAndNull: string = maybeString!; // Ok
}

调用函数时忽略 undefined 类型

type NumGenerator = () => number;

function myFunc(numGenerator: NumGenerator | undefined) {
  // Object is possibly 'undefined'.(2532)
  // Cannot invoke an object which is possibly 'undefined'.(2722)
  const num1 = numGenerator(); // Error
  const num2 = numGenerator!(); //OK
}

注意⚠️: 谨慎使用非空断言,尤其是函数类型的。这很可能事变量被错误的使用

?. 可选链条

可选链条并不是类型操作语法,而是ESnext规范的一部分

有了可选链后,我们编写代码时如果遇到 null 或 undefined 就可以立即停止某些表达式的运行。可选链的核心是新的 ?. 运算符,它支持以下语法:

obj?.prop
obj?.[expr]
arr?.[index]
func?.(args)

上述的代码会自动检查对象 a 是否为 null 或 undefined,如果是的话就立即返回 undefined,这样就可以立即停止某些表达式的运行。你可能已经想到可以使用 ?. 来替代很多使用 && 执行空检查的代码

注意⚠️ :

1. ?. && 运算符行为略有不同, && 专门用于检测 falsy 值,比如空字符串、0、NaN、null 和 false 等。而 ?. 只会验证对象是否为 null undefined ,对于 0 或空字符串来说,并不会出现 “短路”。

2.可选链条与函数调用

let result = obj.customMethod?.();

由于可以选链条只判断 undefined或null,所以customMethod为非函数数据时,也会被调用且报错

??空值合并运算

类似 || ,但只检验左侧是否为undefined或null

const foo = null ?? 'default string';
console.log(foo); // 输出:"default string"

const baz = 0 ?? 42;
console.log(baz); // 输出:0

短路属性

当空值合并运算符的左表达式不为 null 或 undefined 时,不会对右表达式进行求值

function A() { console.log('A was called'); return undefined;}
function B() { console.log('B was called'); return false;}
function C() { console.log('C was called'); return "foo";}

console.log(A() ?? C());
console.log(B() ?? C());

/*
* A was called 
* C was called 
* foo 
* B was called 
* false 
/

不能与 || && 共用

\

关键字

extends

类型继承

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

interface Player extends Person {
    item: 'ball' | 'swing';
}

// Player会有Person的属性

做断言

// 如果 T 可以满足类型 Person 则返回 Person 类型,否则为 T 类型
type IsPerson<T> = T extends Person ? Person : T;

typeof

在 TS 中用于类型表达时,typeof 可以用于从一个变量上获取它的类型

let a = '1';
let obj = {
    'a': 1,
    'b': 2
}
let obj2 = {
    'a': 1,
    'b': 2
} as const;
type IA = typeof a;
type IObj = typeof obj
type IObj2 = typeof obj2;

// type IA = string
// type IObj = {
    a: number;
    b: number;
}
// type IObj2 = {
    readonly a: 1;
    readonly b: 2;
}

keyof

keyof 是TS中的索引类型查询操作符。keyof T 会得到由 T 上已知的公共属性名组成的联合类型

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

type PersonProperty = keyof Person;

// type PersonProperty = "name" | "age" | "phoneNum"

简单的应用场景

// 配合范型使用
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

来点高阶的 获取类型中属性的key 和 值(的类型)

type IValueByKey = {
    [key: string| number| symbol]: any
}
type GetKeys<T extends IValueByKey>= keyof T
type GetValue<T extends IValueByKey>= T[keyof T]

const obj = {
    name: 'liu',
    age: '27'
}

const obj1 = {
    name: 'liu',
    age: '27'
} as const

type ObjKey = GetKeys<typeof obj> // "name" | "age"
type ObjValue = GetValue<typeof obj> // string
type ObjKey1 = GetKeys<typeof obj1> // "name" | "age"
type ObjValue1 = GetValue<typeof obj1> // "liu" | "27"

in

用来遍历可枚举类型通常是枚举类型或联合类型,遍历出的key可以做类型的属性名

enum Letter {
    A,
    B,
    C,
}

type LetterMap = {
    [key in Letter]: string;
}

// type LetterMap = {
//     0: string;
//     1: string;
//     2: string;
// }
type Property = 'name' | 'age' | 'phoneNum';

type PropertyObject = {
    [key in Property]: string;
}

// type PropertyObject = {
//     name: string;
//     age: string;
//     phoneNum: string;
// }
type IB = {
    [key in PropertyKey]: PropertyKey
}
let key = Symbol();
let b:IB = {
    name: '1',
    1: '2',
    key:'ss'
}

通常配合keyof使用,因为keyof返回的是联合类型

infer

我理解的infer是配合extends做类型推断

比如 我们预期范型T是个数组类型,我们想知道T具体是什么类型的书组

比如 我们预期范型T是函数,我们想得到T参数和返回值的类型

// 获取函数的参数类型
type ParamType<T> = T extends (...args: infer R) => any ? R : any;
type func = (a: string, number: number) => number;
type funcParamType = ParamType<func>; 
// type funcParamType = [a: string, number: number]
// 获取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

type func = () => number;
type funcReturnType = ReturnType<func>; // funcReturnType 类型为 number
// 对象的联合类型推断
type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;

type T10 = Foo<{ a: string; b: string }>; // T10类型为 string
type T11 = Foo<{ a: string; b: number }>; // T11类型为 string | number
// 数组的基本类型推断
type Nums = [string];
type BasicType<T> = T extends (infer R)[] ? R : any
type Basic = BasicType<Nums>  // numbr

工具范性

Partial 全部属性可选

type Partial<T> = {
    [P in keyof T]?: T[P];
};
interface PartialType {
    id: number;
    firstName: string;
    lastName: string;
}

/*
等效于
interface PartialType {
  id?: number
  firstName?: string
  lastName?: string
}
*/

function showType(args: Partial<PartialType>) {
    console.log(args);
}

showType({ id: 1 });
// Output: {id: 1}

showType({ firstName: 'John', lastName: 'Doe' });
// Output: {firstName: "John", lastName: "Doe"}

Required 全部属性必选

type Required<T> = {
    [P in keyof T]-?: T[P];
};
interface RequiredType {
    id: number;
    firstName?: string;
    lastName?: string;
}

function showType(args: Required<RequiredType>) {
    console.log(args);
}

showType({ id: 1, firstName: 'John', lastName: 'Doe' });
// Output: { id: 1, firstName: "John", lastName: "Doe" }

showType({ id: 1 });
// Error: Type '{ id: number: }' is missing the following properties from type 'Required<RequiredType>': firstName, lastName

Readonly

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
interface ReadonlyType {
    id: number;
    name: string;
}

function showType(args: Readonly<ReadonlyType>) {
    args.id = 4;
    console.log(args);
}

showType({ id: 1, name: 'Doe' });
// Error: Cannot assign to 'id' because it is a read-only property.


// 也可单独指定某个属性
interface ReadonlyType {
    readonly id: number;
    name: string;
}

as const 会把对象Readonly化

// const obj = {
//   a: '1',
//   b: '2'
// } as const 

// 等价于

type IReadOnlyObj = {
  readonly a: '1',
  readonly b: '2'
}

const obj:IReadOnlyObj = {
  a: '1',
  b: '2'
}

Pick 属性选择

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

此方法允许你从一个已存在的类型 T中选择一些属性作为K, 从而创建一个新类型

即 抽取一个类型/接口中的一些子集作为一个新的类型

T代表要抽取的对象
K有一个约束: 一定是来自T所有属性字面量的联合类型
新的类型/属性一定要从K中选取

interface PickType {
    id: number;
    firstName: string;
    lastName: string;
}

function showType(args: Pick<PickType, 'firstName' | 'lastName'>) {
    console.log(args);
}

showType({ firstName: 'John', lastName: 'Doe' });
// Output: {firstName: "John"}

showType({ id: 3 });
// Error: Object literal may only specify known properties, and 'id' does not exist in type 'Pick<PickType, "firstName" | "lastName">'

Omit 属性删除

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
interface PickType {
    id: number;
    firstName: string;
    lastName: string;
}

function showType(args: Omit<PickType, 'firstName' | 'lastName'>) {
    console.log(args);
}

showType({ id: 7 });
// Output: {id: 7}

showType({ firstName: 'John' });
// Error: Object literal may only specify known properties, and 'firstName' does not exist in type 'Pick<PickType, "id">'

Extract<T, U> 提取交集属性

type Extract<T, U> = T extends U ? T : never;

Extract允许你通过选择两种不同类型中的共有属性来构造新的类型。 也就是从T中提取所有可分配给U的属性。

interface FirstType {
    id: number;
    firstName: string;
    lastName: string;
}

interface SecondType {
    id: number;
    address: string;
    city: string;
}

type ExtractType = Extract<keyof FirstType, keyof SecondType>;
// Output: "id"

在上面的代码中,FirstType接口和SecondType接口,都存在 id:number属性。 因此,通过使用Extract,即提取出了新的类型 {id:number}。

Exclude<T, U> 剔除交集属性

type Exclude<T, U> = T extends U ? never : T;
interface FirstType {
    id: number;
    firstName: string;
    lastName: string;
}

interface SecondType {
    id: number;
    address: string;
    city: string;
}

type ExcludeType = Exclude<keyof FirstType, keyof SecondType>;

// Output; "firstName" | "lastName"

注意⚠️: 剔除和提取都是以T为基准的,最后的类型中不会含有T中没有的属性

Record<T,U> 属性映射

type Record<K extends keyof any, T> = {
    [P in K]: T;
};

此工具可帮助你构造具有给定类型T的一组属性K的类型。将一个类型的属性映射到另一个类型的属性时,Record非常方便

比如一个经典场景,后端数据某一个层的key是可枚举的,下一层级的数据接口又很复杂

type Pro = {
    a: string;
    b: string;
    c?: number;
    d: {
        e: string
    }
}
const hashlist = ['hash1', 'hash2' , 'hash3'] as const
type Getprokey<T> =  T extends readonly (infer R)[] ? R : any;
type PorKey = Getprokey<typeof hashlist> // 'hash1'|'hash2'|'hash3'
                        
function ctrResult(res: Record<PorKey, Pro>){
    // 
}

export {}