《TypeScript 入门教程》学习笔记

大家好,这是掘金小册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配置

targetmodule 这两个配置项,它们分别控制产物语法的 ES 版本以及使用的模块(CommonJs / ES Module)

如果我们的 target 指定了一个版本,比如 es5,但你又希望使用 es6 中才有的 Promise 语法,此时就需要在 lib 配置项中新增 es2015.promise,来告诉 TypeScript 你的目标环境中需要启用这个能力,否则就会得到一个错误:

使用 includeexclude 这两个配置项来确定要包括哪些代码文件,再通过 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 语句,在那些分支层层嵌套的情况下尤其好用。

项目迁移

  • 首先,基于项目规模,设计迁移顺序。 「全量迁移」与「渐进迁移」两个方案
  • 谨记,不要发生逻辑重构,只做类型的补充。不要发生技术栈的替换,只做类型包的补充。
  • 编码工作的第一步,一定是尽可能补充类型定义。
  • 先迁移通用工具方法,再迁移前端组件。
  • 通用类型定义的沉淀
相关推荐
To_OC9 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic9 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy9 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志10 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble11 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯12 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜12 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司13 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
江华森13 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http
IT_陈寒14 小时前
Redis的KEYS命令把我搞崩溃了,改用SCAN才活过来
前端·人工智能·后端