Skip to content

Latest commit

 

History

History
432 lines (291 loc) · 9.39 KB

5_各种关键字以及方法.md

File metadata and controls

432 lines (291 loc) · 9.39 KB

各种关键字以及方法

关键字和方法就是 ts 内置的东西

关键字如 js 中的typeofdelete

const a = 10;
typeof a;
const b = {
    a: 10,
};

delete b.a;

方法就如同全局的 parseInt,数组上自带的工具方法

const b = '3.14156';
const a = parseInt(b);

[1, 2, 3, 4, 5].map(item => console.log(item));

关键字对于我们来说很重要,它可以说是从语言层面上提供的方法,极大的提高了使用的灵活和多变性。

而一些方法则就显得不那么重要了,我们可以使用语言的基础功能和关键字去写一个一毛一样方法

所以,我们接下来先说说关键字



TS 中的关键字

as typeof extends infer keyof is

如果有其他关键字没有在这里,读者可以联系我交流补充

as

as 的作用就比较大了,我们可以叫做它强转类型(字面意思),但是要少用

let str: string = '123123';
let num: number = 123;

接下来,我们把num 赋值给str

str = num;

经过我们前几篇的文章,我们肯定知道这是不能赋值的,会直接报错

我们可以这样

str = num as string;

但是,如果读者试的话,会发现这样写还是会报错,我们需要这样写

str = (num as unknown) as string;

这里解释一下,在 num 是已经确定类型的话,我们是不能直接强转的,TS 会觉得你是错的

[TS]: 你都确定类型了,还转类型干啥,肯定是想干啥不规范的事,先给你判断成错误写法(第一种写法)

[我]: 我知道这不规范,但是我就是要转(第二种写法)

// 毕竟 ts 只是一个工具,提示也给你了,也拗不过你,就只能妥协你了


ps: 这里解释下为啥要转成 unknown(其实转成 any 也行,但转成 unknown 是官方推荐的)

不论是 any 还是 unknown 都有一层你可以赋值给任意类型的意思(如果不懂,可以看文章 1)


typeof

看到 typeof 是不是有点惊讶,记住,它和 js 中是一样的,确定(ts 中则是推断)一个值的类型

通过变量推断变量的类型

const person = {
    name: 'shang',
    age: 18,
    say: () => {
        console.log(123);
    },
};

/**
 * [ts]
 * type Person = {
 *      name: string;
 *      age: number;
 *      say: () => void;
 * }
 */
type Person = typeof person;

let person1: Person;

person1 = person;

extends

extends 在 ts 中不仅只能在 class 的继承interface 的继承类型这些地方使用,还能在其他地方使用。


三元运算

它在 ts 中还起到三元运算符的作用

interface Person<T> {
    type: T extends number ? 1 : 0;
}

const person: Person<number> = {
    type: 0, // error
};

const person1: Person<string> = {
    type: 0, // success
};

其实很多方法的基础实现就是靠这个 extends 的三元运算作用


约束

重要!

它配合泛型使用还具有约束作用,约束传入的参数。

function forEach<T>(arr: T, fn: (arg: T[number]) => void) {
    return arr.forEach(item => fn(item));
}

读者可以把上面这段代码放到文件中查看对错


示例中,会有两个错误,我可以解读一下原因

  • [第一个错误]

arg: T[number]

因为使用泛形的原因,T 是如果没有默认值的话是无类型状态,也就是 any

如果 T 为一个数组,那么我们直接使用 T[number]来索引内部元素就是没有问题的(数组自带数字索引哦)

如果 T 为一个 any 或数组之外的值,那么 T[number]这个类型参数的用法就是错误,因为它们没有索引器

  • [第二个错误]

arr.forEach

如上,如果 T 为一个数组,那么这里是绝对没有问题的,但是问题就是,我们不能确定这个泛形参数一定是数组


function forEach<T extends unknown[]>(arr: T, fn: (arg: T[number]) => void) {
    return arr.forEach(item => fn(item));
}

现在在看第二个示例,第二个示例在 ts 环境中是没有错误的。

我们可以重点看一下多出来的 extends 的使用方法

T extends unknown[]

这段代码的意思就是,把 T 约束成为一个unknown[]类型,unknown[]的意思则是,一个任意元素类型的数组

现在,ts 就会知道,这个 T 一定会是一个数组,第二个参数的数字索引器以及arr.forEach此刻就是确定的

这个就可以说是约束的第一个好处,被约束的泛形在某个作用域中可以直接确定类型


约束外部传参

function forEach<T extends unknown[]>(arr: T, fn: (arg: T[number]) => void) {
    return arr.forEach(item => fn(item));
}

和上面示例 2 一样的代码

大家可以看见第一个参数,arr 的类型为 T,T 又被约束为 unknown[]类型了,那么 arr 的类型就是 unknown[]

那么这时候使用 forEach 的时候,第一参数就会被约束为必须传一个数组

这个也就是数组的第二个好处,约束外部传参


infer

infer,用于推算一个类型

type Params<T> = T extends (...arg: infer P) => void ? P : never;

function fun(a: string, b: number) {}

// [ts] type Args = [a: string, b: number]
type Args = Params<typeof fun>;

可以看见 Args 已经获取到 fun 这个函数的所有参数,这就是 infer 的作用

但是 infer 只能在 extends 中使用,可用于获取函数参数返回值class的constructor参数

如果有兴趣,可以看一下 ts 内置的方法ParametersConstructorParametersReturnType


keyof

是的,keyof 的作用就是取出对象的 key,然后进行枚举

interface Obj {
    [key: string]: unknown;
    [key: number]: unknown;
}

function each<T extends Obj>(o: T, fn: (item: T[keyof T], key: keyof T, index: number) => void) {
    let c = 0;
    for (let i in o) {
        fn(o[i], i, c++);
    }
}
/**
 * [ts] (parameter) item: string | number
 * [ts] (parameter) key: "a" | "b"
 */
each({ a: 1, b: '2' }, (item, key) => {
    console.log(item, key);
});

keyof 可直对类型对象进行取 key,在由T[keyof T]取出所有value的类型,达到枚举所有类型的功能


is

明确类型,用于类型保护(我只知道这个,如果有其他用法,可与我交流一下)

class Person {
    isPerson = true;
    constructor(public name: string, public age: number) {}
    static isPerson(p: any): p is Person {
        return Boolean(p && p.isPerson === true);
    }
}

const person = new Person('shang', 18);

const person2 = {};

if (Person.isPerson(person)) {
    // [ts] const person: Person
    person.name;
}

if (Person.isPerson(person2)) {
    // [ts] const person2: Person
    person2.age;
}

解读一下 isPerson 函数

Person.isPerson 最后的 p is Person就是is的用法

  1. isPerson 接受一个参数p
  2. p 存在,且 p 身上的 isPerson 为 true
  3. 返回结果然后用 Boolean 转换为布尔值

如果为 true,那么最后 p,会被认定为 Person,如果为 false,那么就不是 Person

看完上句话,想必大家已经知道is的作用了

作用: 如果函数返回 true,那么在此判断下作用域中,这个传入的参数将一直是Person(也就是 is 后面的类型)


in

in 的使用访问较少(目前所了解的),但是能实现的功能却很多

in 的作用就是循环,直接看代码吧

type MPartial<T> = {
    [P in keyof T]?: T[P];
};

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

const person: MPartial<Person> = {};

大家可以把这段代码放到 ts 环境下看一看

它不会报错,是不是很神奇

应为 Person 类型中的每个参数我们都是要求要必须有的,但是经过MPartial<Person>后,我们这样写就不会报错

其实,MPartial的作用就是,将传入的对象复制一遍,并且将每位参数都设置为可选的,所以,在下面的 person 才能被赋值一个空对象

下面我们看一下 in 的各种用法


  • 枚举的值
/**
 * type T1 = {
 *     1: 1;
 *     2: 2;
 *     3: 3;
 * }
 */
type T1 = {
    [P in 1 | 2 | 3]: P;
};
  • 对象
interface Keys {
    a: 1;
    b: 2;
    c: 3;
}

/**
 * type T2 = {
 *     a: 1;
 *     b: 2;
 *     c: 3;
 * }
 */
type T2 = {
    [P in keyof Keys]: Keys[P];
};
  • 数组
type Arr = [1, 2, 3, 4];

/**
 * type T3 = {
 *     1: 1;
 *     2: 2;
 *     3: 3;
 *     4: 4;
 * }
 */
type T3 = {
    [P in Arr[number]]: P;
};


TS 中的方法

Partial Required Readonly Pick Record Exclude Extract Omit NonNullable

这些方法我就不一一解释,

有兴趣大家可以在自行搜索一下 ts 方法,这里咱就只说几个实现

// 将对象所有属性转换为可选
type Partial<T> = {
    [P in keyof T]?: T[P];
};

// 如果T中有U,那么排除掉此类型,否则则返回这个类型
type Exclude<T, U> = T extends U ? never : T;

// 如果T中有U,那么返回这个类型,否则排除
type Extract<T, U> = T extends U ? T : never;

可以看见大量使用了三元运算符和循环,毕竟分支和循环就是语言的基础



结尾

大家有兴趣可以去代码这里看看其他内容

谢谢大家的观看,如果有错误,请使用邮箱联系我