《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 语句,在那些分支层层嵌套的情况下尤其好用。

项目迁移

  • 首先,基于项目规模,设计迁移顺序。 「全量迁移」与「渐进迁移」两个方案
  • 谨记,不要发生逻辑重构,只做类型的补充。不要发生技术栈的替换,只做类型包的补充。
  • 编码工作的第一步,一定是尽可能补充类型定义。
  • 先迁移通用工具方法,再迁移前端组件。
  • 通用类型定义的沉淀
相关推荐
还是大剑师兰特27 分钟前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解28 分钟前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~34 分钟前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding39 分钟前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT43 分钟前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓43 分钟前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶213643 分钟前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了43 分钟前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
Mr.咕咕1 小时前
Django 搭建数据管理web——商品管理
前端·python·django
张张打怪兽1 小时前
css-50 Projects in 50 Days(3)
前端·css