TypeScript 类型体操-语法入门篇
- TypeScript 类型体操-语法入门篇
- TypeScript 类型体操-常见套路篇
- TypeScript 类型体操-常见套路总结
TypeScript
给 JavaScript
添加了一套静态类型系统,通过 TS Compiler
可以将 TS
编译为 JS
,在编译的过程做类型检查。
这样就使得 JavaScript
从动态类型语言变成了静态类型语言,可以在编译期间做类型检查,提前发现一些类型安全问题。
为什么有类型体操
- 简单的类型系统
比如一个 add
函数既可以做整数加法、又可以做浮点数加法,却需要声明两个函数:
c++ 代码解读复制代码int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
这样做会感觉有些死板,如果类型能作为参数传递是不是会更好。
- 支持泛型的类型系统
泛型表示一种通用的类型,它可以代表任何一种类型,也叫做类型参数
。
c++ 代码解读复制代码T add(T a, T b) {
return a + b;
}
这样的泛型虽然会灵活一点,但是对于 JavaScript
来说可能还有点不太够。
比如我们有一个自定义的对象类型。
强类型中,比如Java
,对象都是由一个类new
出来的,有了类型,就可以获取到类的信息。
但是 JavaScript
不一样,JavaScript
中有对象字面量,可以凭空创建一个对象。
如果有一个函数是一个返回对象某个属性值的函数,类型该怎么写呢?
c++ 代码解读复制代码function getPropValue(obj: T, key): key对应的属性值类型 {
return obj[key];
}
好像拿到了 T,也不能拿到它的属性和属性值,如果能对类型参数 T 做一些逻辑处理就好了。
- 支持类型编程的类型系统
在 Java
里面,拿到了对象的类型就能找到它的类,进一步拿到各种信息,所以类型系统支持泛型就足够了。
但是在 JavaScript
里面,对象可以字面量的方式创建,还可以灵活的增删属性,拿到对象并不能确定什么,所以要支持对传入的类型参数做进一步的处理。
对传入的类型参数(泛型)做各种逻辑运算,产生新的类型,这就是类型编程。
比如上面那个 getProps
的函数,类型可以这样写:
ts 代码解读复制代码function getPropValueextends object, Key extends keyof T>(obj: T, key: Key): T[Key] {
return obj[key];
}
这里的 keyof T
、T[Key]
就是对类型参数 T
的类型运算。
TypeScript
的类型系统就是第三种,支持对类型参数做各种逻辑处理,可以写很复杂的类型逻辑。
TypeScript 类型系统支持哪些类型和类型运算?
TypeScript 支持的类型
JavaScript 中支持的类型,TypeScript 也都支持。
- 简单类型: number、boolean、string、object、bigint、symbol、undefined、null
- 包装类型: Number、Boolean、String、Object、Symbol。
- 复杂类型:Class、Array 等。
TypeScript 特有的类型
Tuple 元祖
元组(Tuple)
就是元素个数和类型固定的数组类型:
ts 代码解读复制代码type Tuple = [number, string];
Interface 接口
接口(Interface)
可以描述函数、对象、构造器等复合类型
ts 代码解读复制代码interface IPerson {
name: string;
age: number;
}
class Person implements IPerson {
name: string;
age: number;
}
const obj: IPerson = {
name: 'guang',
age: 18,
};
Enum 枚举
枚举(Enum)
是一系列值的复合:
ts 代码解读复制代码enum Transpiler {
Babel = 'babel',
Postcss = 'postcss',
Terser = 'terser',
Prettier = 'prettier',
TypeScriptCompiler = 'tsc',
}
const transpiler = Transpiler.TypeScriptCompiler;
字面量类型
TypeScript 还支持字面量类型
,也就是类似 123
、'aaaa'
、{ a: 1}
这种值也可以做为类型。
其中,字符串的字面量类型有两种,
- 普通的字符串字面量,比如
'aaa'
- 模版字面量,比如
`aaa${string}`
,它的意思是以aaa
开头,后面是任意string
的字符串字面量类型。
所以想要约束以某个字符串开头的字符串字面量类型时可以这样写:
特殊类型
never
: 代表不可达,比如函数抛异常的时候,返回值就是 never。void
: 代表空,可以是 undefined 或 never。any
: 是任意类型,任何类型都可以赋值给它,它也可以赋值给任何类型(除了 never)。unknown
: 是未知类型,任何类型都可以赋值给它,但是它不可以赋值给别的类型。
类型装饰
TypeScript 的类型系统还支持描述类型的属性,比如是否可选,是否只读等:
ts 代码解读复制代码interface IPerson {
readonly name: string;
age?: number;
}
type tuple = [string, number?];
TypeScript 支持的类型运算
获取类型:typeof
typeof 操作符可以用来获取一个变量声明或对象的类型。
ts 代码解读复制代码interface Person {
name: string;
age: number;
}
const sem: Person = { name: 'semlinker', age: 33 };
type Sem = typeof sem; // -> Person
function toArray(x: number): Array<number> {
return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]
获取类型的键:keyof
keyof 可以用于获取某种类型的所有键,其返回类型是联合类型。
ts 代码解读复制代码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
遍历:in
in 用来遍历联合类型:
ts 代码解读复制代码type Keys = 'a' | 'b' | 'c';
type Obj = {
[p in Keys]: any;
};
// -> { a: any, b: any, c: any }
泛型约束:extends
有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。
ts 代码解读复制代码interface Lengthwise {
length: number;
}
function loggingIdentityextends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
这时我们需要传入符合约束类型的值,必须包含必须的属性:
ts 代码解读复制代码loggingIdentity(3); // Error, number doesn't have a .length property
loggingIdentity({ length: 10, value: 3 }); // 正确用法
条件运算:extends ? :
TypeScript
里的条件判断是 extends ? :
,可以理解成 if else
。
ts 代码解读复制代码type isString = T extends string ? true : false;
type res1 = isString<'123'>;
type res2 = isString<123>;
推导:infer
如何提取类型的一部分呢?答案是 infer。
比如提取元组类型的第一个元素:
ts 代码解读复制代码type First<Tuple extends unknown[]> = Tuple extends [infer T, ...infer R] ? T : never;
type res = First<[1, 2, 3]>;
联合:|
联合类型(Union) 代表类型可以是几个类型之一
ts 代码解读复制代码type Union = 1 | 2 | 3;
交叉:&
交叉类型(Intersection)
- 相同的类型做合并
ts 代码解读复制代码type ObjType = { a: number } & { c: boolean };
let obj: ObjType = {
a: 2,
c: false,
};
- 不同的类型做舍弃
ts 代码解读复制代码type ObjType = (number | string | symbol) & string;
// 此时 ObjType 只能是 string
case
- 修改对象类型的值类型
ts 代码解读复制代码interface Person {
name: string;
age: number;
}
type MyPerson = {
[P in keyof Person]: { value: Person[P] };
};
- 修改对象类型的 key 类型
ts 代码解读复制代码interface Person {
name: string;
age: number;
}
type MyPerson = {
[P in keyof Person as `get${P & string}`]: Person[P];
};
评论记录:
回复评论: