TypeScript上手指南
wen 2022/9/1 TypeScript
源链接:https://juejin.cn/post/6981728323051192357 (opens new window)
# 一、前言
- 官方文档:https://www.typescriptlang.org/ (opens new window)。
- 中文文档:https://www.tslang.cn/index.html (opens new window)。
# 二、TypeScript 的优缺点
# 1. 优点
- 代码的可读性和可维护性,友好的提示。
- 在编译阶段就发现大部分错误,避免了很多线上 bug。
- 增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等。
# 2. 缺点
- 有一定的学习成本,需要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的概念。
- 会增加一些开发成本,当然这是前期的,后期维护更简单了。
- 一些 JavaScript 库需要兼容,提供声明文件。
- TypeScript 编译是需要时间的,这就意味着项目大了以后,开发环境启动和生产环境打包的速度就成了考验。
# 三、anyScript
可能因为业务场景或者业务紧张,or 某个跑路的大哥省了点功夫,用了 TypeScript 的项目也可能会变成 anyScript
。以下是几种救急的方式:
- // @ts-nocheck 禁用整个文件的 ts 校验;
- // @ts-ignore 禁用单行 ts 校验;
any
和unknown
不建议多用,但也不是不能用,有些场景确实不好写 ts 定义。这个时候就不要硬憋自己了,写个备注 any 下。
抛个面试题:你知道 any 和 unknown 的区别吗?
# 四、TypeScript 类型
# 1. 基础类型
- 常用:boolean、number、string、array、enum、any、void。
- 不常用:tuple、null、undefined、never。
const count: number = 20220901;
1
# 2. 对象类型
- 简单理解 interface 和 type 的区别:type 更强大,interface 可以进行声明合并,type 不行。
interface Hero {
name: string;
age: number;
skill: string;
skinNum?: number;
say(): string; // say 函数返回值为 string
[propname: string]: any; // 当前 Hero 可定义任意字符串类型的 key
}
type Hero = {
name: string,
age: number,
skill: string,
skinNum?: number,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3. 数组类型
- 项目中常见的写法,需要声明列表数据类型:
interface Item {
id: number;
name: string;
}
const objectArr: Item[] = [{ id: 1, name: 'wen' }];
const objectArr: Array<Item> = [{ id: 1, name: 'wen' }];
const numberArr: number[] = [1, 2, 3];
const arr: (number | string)[] = [1, "string", 2];
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 4. 元组 tuple
- 元组和数组类似,但是类型注解时会不一样。
- 赋值的类型、位置、个数需要和定义(声明)的类型、位置、个数一致。
// 数组 某个位置的值可以是注解中的任何一个
const LOL: (string | number)[] = ["zed", 25, "darts"];
// 元祖 每一项数据类型必须一致
const LOL: [string, string, number] = ["zed", "darts", 25];
1
2
3
4
5
2
3
4
5
# 5. 联合|类型 or 交叉&类型
- 联合类型:某个变量可能是多个 interface 中的其中一个,用
|
分割。 - 交叉类型:由多个类型组成,用
&
连接。
type UnionA = 'px' | 'em' | 'rem' | '%';
type UnionB = 'vh' | 'em' | 'rem' | 'pt';
type IntersectionUnion = UnionA & UnionB;
const intersectionA: IntersectionUnion = 'em'; // ok
const intersectionB: IntersectionUnion = 'rem'; // ok
const intersectionC: IntersectionUnion = 'px'; // ts(2322)
const intersectionD: IntersectionUnion = 'pt'; // ts(2322)
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 6. enum 枚举
- 提高代码可维护性,统一维护某些枚举值。
enum TestEnum {
RED,
BLUE,
GREEN
}
enum TestEnum2 {
RED = 6,
BLUE,
GREEN
}
const test: TestEnum = TestEnum.BLUE // 1
const test2: TestEnum2 = TestEnum2.BLUE // 7
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 7. 泛型 T(Type)
- 简单说就是,泛指的类型,不确定的类型,可以理解为一个占位符(使用T只是习惯,使用任何字母都行)。
- K(Key):表示对象中的键类型;V(Value):表示对象中的值类型;E(Element):表示元素类型。
// T 自定义名称
function myFun<T>(params: T[]) {
return params;
}
myFun<string> (["123", "456"]);
// 定义多个泛型
function join<T, P>(first: T, second: P) {
return `${first}${second}`;
}
join<number, string> (1, "2");
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 8. 断言
- 断言用来手动指定一个值的类型。
值 as 类型
or<类型>值
。
注意在 tsx 语法中必须使用前者,即 值 as 类型
# 9. in
- 类似于数组和字符串的
includes
方法。 - 也有遍历的作用,拿到 ts 类型定义的 key;获取 key 还有个方法:keyof 是取类型的 key 的联合类型 , in 是遍历类型的 key。
interface test {
one: string,
two?: string,
three?: string
}
const t: test = {
one: 'one'
}
console.log('one' in t) // true
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 10. 类型注解
- 显式的告诉代码,count 变量就是一个数字类型,这就叫做类型注解。
let count: number; // 类型注解
count = 123;
1
2
2
# 11. 类型推断
- 如果 TS 能够自动分析变量类型, 我们就什么也不需要做了。
- 如果 TS 无法分析变量类型的话, 我们就需要使用类型注解。
// ts 可以推断出 count 为 number 类型
let count = 123;
1
2
2
# 12. void 和 never
- 返回值类型,也算是基础类型。
// 没有返回值的函数 void
function sayHello(): void {
console.log("hello");
}
// 如果一个函数是永远也执行不完的,就可以定义返回值为 never
function errorFuntion(): never {
throw new Error();
console.log("error");
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 13. 类型检测
# 13.1 typeof
- typeof 操作符可以用来获取一个变量或对象的类型。
interface Hero {
name: string;
skill: string;
}
const zed: Hero = { name: "影流之主", skill: "影子" };
type LOL = typeof zed; // type LOL = Hero
const ahri: LOL = { name: "阿狸", skill: "魅惑" };
1
2
3
4
5
6
7
2
3
4
5
6
7
# 13.2 instanceof
# 13.3 keyof
- keyof 与 Object.keys 略有相似,只不过 keyof 取 interface 的键。
interface Point {
x: number;
y: number;
}
type keys = keyof Point; // type keys = "x" | "y"
// 用 keyof 可以更好的定义数据类型
function get<T extends object, K extends keyof T>(o: T, name: K): T[K] {
return o[name]
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 14. TypeScript 类里的关键字
了解 ts 关键字的作用,在写 base 类的时候可能会用到,个人用的不多。
- public
- private 类的外部不可用,继承也不行
- protected 类的外部不可用,继承可以
- public readOnly xxx 只读属性
- static funcXXX 静态方法,不需要 new 就可以调用
- abstract funcXXX 抽象类,所有子类都必须要实现 funcXXX
# 五、tsconfig
# 1. 作用
- 用于标识 TypeScript 项目的根路径;
- 用于配置 TypeScript 编译器;
- 用于指定编译的文件。
# 2. 注意事项
- tsc -init 生成 tsconfig.json,项目目录下直接 tsc,编译的时候就会走配置文件。
- compilerOptions 内部字段含义 阿宝哥 这篇文章有详细说明 (opens new window)。
- 项目别名配置:遇到过的一个坑,仅在项目config中配置别名不生效,需要在 tsconfig.json 中再配置一遍。
# 六、Utility Types
- Utility Types:可以理解为基于 ts 封装的工具类型。
- 具体源码解析可以参考:源码解读utility-types (opens new window),TypeScript Utility Types 学习笔记及源码解析 (opens new window)。
# 1. Partial<T>
- 将 T 中所有属性转换为可选属性。返回的类型可以是 T 的任意子集。
export interface UserModel {
name: string;
age?: number;
sex: number;
}
type JUserModel = Partial<UserModel>
// =
type JUserModel = {
name?: string | undefined;
age?: number | undefined;
sex?: number | undefined;
}
// 源码解析
type Partial<T> = { [P in keyof T]?: T[P]; };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2. Required<T>
- 通过将 T 的所有属性设置为必选属性来构造一个新的类型。与 Partial 相反。
type JUserModel2 = Required<UserModel>
// =
type JUserModel2 = {
name: string;
age: number;
sex: number;
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 3. Readonly<T>
- 将 T 中所有属性设置为只读。
type JUserModel3 = Readonly<UserModel>
// =
type JUserModel3 = {
readonly name: string;
readonly age?: number | undefined;
readonly sex: number;
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 4. Record<K,T>
- 构造一个类型,该类型具有一组属性 K,每个属性的类型为 T。可用于将一个类型的属性映射为另一个类型。Record 后面的泛型就是对象键和值的类型。
- 简单理解:K 对应对应的 key,T 对应对象的 value,返回的就是一个声明好的对象。
type TodoProperty = 'title' | 'description';
type Todo = Record<TodoProperty, string>;
// =
type Todo = {
title: string;
description: string;
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 5. Pick<T,K>
- 在一个声明好的对象中,挑选一部分出来组成一个新的声明对象。
interface Todo {
title: string;
description: string;
done: boolean;
}
type TodoBase = Pick<Todo, "title" | "done">;
// =
type TodoBase = {
title: string;
done: boolean;
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 6. Omit<T,K>
- 从 T 中取出除去 K 的其他所有属性。与 Pick 相对。
# 7. Exclude<T,U>
- 从 T 中排除可分配给 U 的属性,剩余的属性构成新的类型。
type T0 = Exclude<'a' | 'b' | 'c', 'a'>;
// =
type T0 = "b" | "c"
1
2
3
2
3
# 8. Extract<T,U>
- 从 T 中抽出可分配给 U 的属性构成新的类型。与 Exclude 相反。
type T0 = Extract<'a' | 'b' | 'c', 'a'>;
// =
type T0 = 'a'
1
2
3
2
3
# 9. NonNullable<T>
- 去除 T 中的 null 和 undefined 类型。
# 10. Parameters<T>
- 返回类型为 T 的函数的参数类型所组成的数组。
type T0 = Parameters<() => string>; // []
type T1 = Parameters<(s: string) => void>; // [string]
1
2
2
# 11. ReturnType<T>
- function T 的返回类型。
type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<(s: string) => void>; // void
1
2
2
# 12. InstanceType<T>
- 返回构造函数类型 T 的实例类型。
class C {
x = 0;
y = 0;
}
type T0 = InstanceType<typeof C>; // C
1
2
3
4
5
6
2
3
4
5
6
# 七、TypeScript 学习资源
- B站 技术胖ts入门视频 (opens new window):胖哥新版ts教程。
- 尚硅谷2021版TypeScript教程(李立超老师TS新课) (opens new window):还算比较新,喜欢视频学习的同学了解下。
- TypeScript 中文手册 (opens new window):比官网那个易读一些。
- TypeScript与React结合 (opens new window):快速上手指南。
- 一份不可多得的 TS 学习指南(1.8W字) (opens new window):阿宝哥,ts大佬 主页有很多ts教程 (opens new window)。
- 深入理解 TypeScript (opens new window):讲的就比较深入了。
- TypeScript 代码整洁之道 (opens new window):翻译国外大佬写的,国内大佬翻译的。
- TypeScript Playground (opens new window):TypeScript 官方提供的在线 TypeScript 运行环境。
- json2ts (opens new window):将JSON转换成ts声明,应该好用,不过我们后端的接口文档自带了这个功能,我是用不上了。了解到有些类库可以直接根据数据表结构生成ts定义。