关键字和方法就是 ts 内置的东西
关键字如 js 中的typeof
、delete
等
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));
关键字对于我们来说很重要,它可以说是从语言层面上提供的方法,极大的提高了使用的灵活和多变性。
而一些方法则就显得不那么重要了,我们可以使用语言的基础功能和关键字去写一个一毛一样方法
所以,我们接下来先说说关键字
as
typeof
extends
infer
keyof
is
如果有其他关键字没有在这里,读者可以联系我交流补充
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 是不是有点惊讶,记住,它和 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 在 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,用于推算一个类型
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 内置的方法Parameters
、ConstructorParameters
、ReturnType
是的,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
的类型,达到枚举所有类型的功能
明确类型,用于类型保护(我只知道这个,如果有其他用法,可与我交流一下)
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
的用法
isPerson
接受一个参数p
如果为 true,那么最后 p,会被认定为 Person
,如果为 false,那么就不是 Person
看完上句话,想必大家已经知道is
的作用了
作用: 如果函数返回 true,那么在此判断下作用域中,这个传入的参数将一直是Person
(也就是 is 后面的类型)
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;
};
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;
可以看见大量使用了三元运算符和循环,毕竟分支和循环就是语言的基础
谢谢大家的观看,如果有错误,请使用邮箱联系我