大家好,这是掘金小册TypeScript 入门教程的学习笔记。
面向过程和面向对象指的分别是什么?
你可以认为它们是实现同一种效果的不同手段而已。比如类比到做一锅黄焖鸡,面向对象要求你分别建立鸡肉对象、土豆对象、青椒对象、锅对象等等,这些对象携带着自己的信息,只要将它们组合在一起就是一道菜。而面向过程的范式则是,按照顺序逐步完成这道菜,依次备菜、起锅烧油、煎炒、焖等等。
无论使用哪种方式,最后你都能得到这道黄焖鸡,唯一的区别在于你的执行流程不同。面向对象强调对象的封装、组合与交互,而面向过程强调程序的执行流程。
any 类型 = 万能类型 + 放弃类型检查 unknown 类型 = 万能类型 + 类型检查
类型断言
它能够修改一个变量的类型------无论是TS自己推导的,还是你手动标注的。
内置工具方法
Record<K,T>,构造一个对象类型,
Keys
表示对象的属性键 、Type
表示对象的属性值,用于将一种类型属性映射到另一种类型。
源码
ts
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
ts
type UserProps = 'name' | 'job' | 'email';
// 等价于你一个个实现这些属性了
type User = Record<UserProps, string>;
const user: User = {
name: 'John Doe',
job: 'fe-developer',
email: 'john.doe@example.com'
};
ts
// 声明属性名还未确定的接口类型
type User = Record<string, string>;
const user: User = {
name: 'John Doe',
job: 'fe-developer',
email: 'john.doe@example.com',
bio: 'Make more interesting things!',
type: 'vip',
// ...
};
Partial,它接收一个对象类型,并将这个对象类型的所有属性都标记为可选 Required,它接收一个对象类型,并将这个对象类型的所有属性都标记为必选
ts
type User = {
name: string;
age: number;
email: string;
};
type PartialUser = Partial<User>;
const user: User = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com'
};
// 可以不实现全部的属性了!
const partialUser: PartialUser = {
name: 'John Doe',
age: 30
};
ts
type User = {
name?: string;
age?: number;
email?: string;
};
type RequiredUser = Required<User>;
const user: User = {
name: 'John Doe'
};
// 现在你必须全部实现这些属性了
const requiredUser: RequiredUser = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com'
};
Readonly,只读,没有与 Readonly 结对出现的版本,只读通常是一个不可逆的行为
ts
type User = {
name: string;
age: number;
email: string;
};
type ReadonlyUser = Readonly<User>;
const user: User = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com'
};
const readonlyUser: ReadonlyUser = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com'
};
// 修改 user 对象的属性
user.name = 'Jane Doe';
user.age = 25;
user.email = 'jane.doe@example.com';
// 修改 readonlyUser 对象的属性
// readonlyUser.name = 'Jane Doe'; // 报错
// readonlyUser.age = 25; // 报错
// readonlyUser.email = 'jane.doe@example.com'; // 报错
Pick,类型接收一个对象类型,以及一个字面量类型组成的联合类型,这个联合类型只能是由对象类型的属性名组成的。它会对这个对象类型进行
裁剪
,只保留
你传入的属性名组成的部分: Omit,类型接收一个对象类型,以及一个字面量类型组成的联合类型,这个联合类型只能是由对象类型的属性名组成的。它会对这个对象类型进行裁剪
,只移除
你传入的属性名组成的部分:
ts
type User = {
name: string;
age: number;
email: string;
phone: string;
};
// 只提取其中的 name 与 age 信息
type UserBasicInfo = Pick<User, 'name' | 'age'>;
const user: User = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com',
phone: '1234567890'
};
const userBasicInfo: UserBasicInfo = {
name: 'John Doe',
age: 30
};
ts
type User = {
name: string;
age: number;
email: string;
phone: string;
};
// 只移除 phone 属性
type UserWithoutPhone = Omit<User, 'phone'>;
const user: User = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com',
phone: '1234567890'
};
const userWithoutPhone: UserWithoutPhone = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com'
};
Exclude,它能够从一个类型中移除另一个类型中也存在的部分 Extract,它能够从一个类型中只保留另一个类型中也存在的部分
ts
type UserProps = 'name' | 'age' | 'email' | 'phone' | 'address';
type RequiredUserProps = 'name' | 'email';
// OptionalUserProps = UserProps - RequiredUserProps
type OptionalUserProps = Exclude<UserProps, RequiredUserProps>;
const optionalUserProps: OptionalUserProps = 'age' | 'phone' | 'address';
ts
type UserProps = 'name' | 'age' | 'email' | 'phone' | 'address';
type RequiredUserProps = 'name' | 'email';
type RequiredUserPropsOnly = Extract<UserProps, RequiredUserProps>;
const requiredUserPropsOnly: RequiredUserPropsOnly = 'name' | 'email';
Parameters,提取函数的参数类型(参数是函数类型) ReturnType,提取函数的返回值类型(参数是函数类型)
ts
type Add = (x: number, y: number) => number;
type AddParams = Parameters<Add>; // [number, number] 类型
type AddResult = ReturnType<Add>; // number 类型
const addParams: AddParams = [1, 2];
const addResult: AddResult = 3;
typeof,可以提取变量和函数的类型(可以传给Parameters和ReturnType)
ts
const addHandler = (x: number, y: number) => x + y;
type Add = typeof addHandler; // (x: number, y: number) => number;
type AddParams = Parameters<Add>; // [number, number] 类型
type AddResult = ReturnType<Add>; // number 类型
const addParams: AddParams = [1, 2];
const addResult: AddResult = 3;
Awaited,处理异步的方法,提取出的返回值类型
ts
const promise = new Promise<string>((resolve) => {
setTimeout(() => {
resolve("Hello, World!");
}, 1000);
});
type PromiseInput = Promise<string>;
type AwaitedPromiseInput = Awaited<PromiseInput>; // string
// 定义一个函数,该函数返回一个 Promise 对象
async function getPromise() {
return new Promise<string>((resolve) => {
setTimeout(() => {
resolve("Hello, World!");
}, 1000);
});
}
type Result = Awaited<ReturnType<typeof getPromise>>; // string 类型
模板字符串类型:精确的字符串类型结构描述
ts
// 1
type Version = `${number}.${number}.${number}`;
const v1: Version = '1.1.0';
const v2: Version = '1.0'; // 报错:类型 "1.0" 不能赋值给类型 `${number}.${number}.${number}`
const v3: Version = 'a.0.0'; // 报错:类型 "a.0" 不能赋值给类型 `${number}.${number}.${number}`
// 2
type SayHello<T extends string | number> = `Hello ${T}`;
type Greet1 = SayHello<"linbudu">; // "Hello linbudu"
type Greet2 = SayHello<599>; // "Hello 599"
//3
type Brand = 'iphone' | 'xiaomi' | 'honor';
type SKU = `${Brand}`; // "iphone" | "xiaomi" | "honor"
// 4
type SKU = `${Brand}-latest`; // "iphone-latest" | "xiaomi-latest" | "honor-latest"
// 6
type Brand = 'iphone' | 'xiaomi' | 'honor';
type Memory = '16G' | '64G';
type ItemType = 'official' | 'second-hand';
type SKU = `${Brand}-${Memory}-${ItemType}`;
ts配置
target
与 module
这两个配置项,它们分别控制产物语法的 ES
版本以及使用的模块(CommonJs / ES Module)
如果我们的 target
指定了一个版本,比如 es5
,但你又希望使用 es6
中才有的 Promise
语法,此时就需要在 lib
配置项中新增 es2015.promise
,来告诉 TypeScript
你的目标环境中需要启用这个能力,否则就会得到一个错误:
使用 include
和 exclude
这两个配置项来确定要包括哪些代码文件,再通过 outDir
选项配置你要存放输出文件的文件夹
json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"outDir": "dist",
"strict": true
},
"include": [
"src/**/*"
],
"exclude": [
"src/generated",
"**/*.spec.ts"
]
}
首先通过 include
,我们指定了要包括 src
目录下所有的文件,再通过 exclude
选项,剔除掉已经被 include
进去的文件,包括 src/generated
文件夹,以及所有 .spec.ts
后缀的测试用例文件。
假设我们的项目中被三方依赖安装了大量的 @types 文件,导致类型加载缓慢或者冲突,此时就可以使用 types 配置项来显式指定你需要加载的类型定义:
json
{
"compilerOptions": {
"types": ["node", "jest", "react"],
}
}
以上配置会加载 @types/node
,@types/jest
,@types/react
这几个类型定义包。
declaration
,它的作用就一个------控制是否生成 .d.ts 文件,如果禁用的话你的编译产物将只包含 JS 文件,与之相对的是 emitDeclarationOnly
,如果启用,则只会生成 .d.ts 文件,而不会生成 JS 文件,如果你两个都不想要呢?------请使用 noEmit
!启用后将不会输出 JS 文件与声明文件,但类型检查能力还是能保留的。
-
noImplicitAny,当 TypeScript 无法推断出你这个变量或者参数到底是什么类型时,它只能默默给一个 any 类型。如果你的项目维护地还比较认真,可以启用这个配置,来检查看看代码里有没有什么地方是遗漏了类型标注的。
-
noUnusedLocals 与 noUnusedParameters,类似于 ESLint 中的
no-unused-var
,它会检查你的代码中是否有声明了但没有被使用的变量/函数。是否开启同样取决于你对项目质量的要求,毕竟正常情况下项目中其实不应该出现定义了但没有消费的变量,这可能就意味着哪里的逻辑出错了。 -
noImplicitReturns,启用这个配置项会要求你的函数中所有分支代码块都必须有显示的 return 语句,我们知道 JavaScript 中不写 return (即这里的 Implicit Returns)和只写一个简单的 return 的效果是完全一致的,但从类型层面来说却不一致,它标志着你到底是没有返回值还是返回了一个 undefined 类型的值。因此,启用这个配置项可以让你确保函数中所有的分支都有一个有效的 return 语句,在那些分支层层嵌套的情况下尤其好用。
项目迁移
- 首先,基于项目规模,设计迁移顺序。 「全量迁移」与「渐进迁移」两个方案
- 谨记,不要发生逻辑重构,只做类型的补充。不要发生技术栈的替换,只做类型包的补充。
- 编码工作的第一步,一定是尽可能补充类型定义。
- 先迁移通用工具方法,再迁移前端组件。
- 通用类型定义的沉淀