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