# 系统化学习 TS 类型系统

目的:快速、系统性的入门 TS 类型系统

# 前言

TS 是什么?
TS 是 JS 的超集,
TS = JS + 类型系统

为了描述如此复杂(由于 JS 语言的灵活性/复杂性)的类型信息,类型系统表现出非常明显的编程语言特性。
以学习编程语言的方式,来学习 TS 类型系统

# 关键字/符号

  • 类型: boolean, number, string, null, undefined, unknow, any, never
  • 运算符: &, |, ...
  • 声明: type, interface, declare
  • 其他: readonly, keyof, extends, infer, ?, -?

# 语法

声明、条件分支、函数、循环/递归 是绝大部分语言的基础语法。

# 声明类型

type N = 1 | 2 | 3

# 声明类型结构

interface Point {
  x: number
  y: number
}

type Point3 = Point & { z: number } // 计算确切的结果

type Intersect<P, Q> = Pick<P, keyof P & keyof Q> // 描述计算过程,类似“函数”

一般我用 interface 描述静态类型(数据)结构,用 type 描述类型之间的计算关系

# 条件分支

declare function distance<P extends Point>(p1: P, p2: P): number
distance(0, 1) // Error

declare function promisify<T>(p: T): T extends Promise<any> ? T : Promise<T>

# 递归

循环不是编程语言的必要语法,循环的应用场景可被递归替代

type GenArr<
  N extends number, 
  Arr extends any[] = []
> = Arr["length"] extends N ? Arr : GenArr<N, [...Arr, 1]>;

type ThreeItemArr = GenArr<3>

N, Arr
3, []
3, [1]
3, [1, 1]
3, [1, 1, 1] // Arr["length"] => 3

# 标准库

标准库是以语言自身基础能力实现的通用工具函数

深入了解一门语言的最佳方式之一是阅读标准库源码 可以学习如何灵活应用该语言基础特性、推荐风格、常用工具函数等等

以下是高频使用的标准库工具函数,其实现涵盖大部分 TS 类型系统的特性。

type Partial<T> = {
    [P in keyof T]?: T[P];
};
// Partial<{ x: string }> 
// { x?: string }

type Required<T> = {
    [P in keyof T]-?: T[P];
};
// Required<{ x?: string }> 
//  { x: string }

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
// Readonly<{ x: string }> 
// { readonly x: string }

type Exclude<T, U> = T extends U ? never : T;
// Exclude<"a" | "b" | "c", "a">
// "b" | "c"

type Extract<T, U> = T extends U ? T : never;
// Extract<"a" | "b" | "c", "a">
// "a"

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
// Pick<{ x: string, y: number }, 'x'> 
// { x: string }

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// Omit<{ x: string, y: number }, 'x'> 
// { y: number }

type Record<K extends keyof any, T> = {
    [P in K]: T;
};
// Record<string, string>
// { [k: string]: string }

type NonNullable<T> = T extends null | undefined ? never : T;
// NonNullable<null | 1 | 2> 
// 1 | 2

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
// Parameters<(a: string, b: number) => Promise<string>>
// [string, number]

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
// ReturnType<(a: string, b: number) => Promise<string>>
// Promise<string>

# 练习

# 柯里化函数类型描述

type Curr<Args, R> = Args extends [infer First, ... infer Rest]
  ? (arg: First) => Curr<Rest, R>
  : R

declare function curry<Fn extends (...args: any[]) => any> (fn: Fn):
Fn extends (...args: infer Args) => infer R ? Curr<Args, R> : never

function add (a: number, b: number): number {
  return a + b
}

const currAdd = curry(add)

currAdd(1)(2) // 3

柯里化的 js 实现、用法参考 lodash.curry (opens new window)

# 获取 readonly 字段

type Equal<First, Second> = (<T>() => T extends First ? true : false) extends 
  (<T>() => T extends Second ? true : false) 
    ? true : false 

type GetReadonlyKeys<T> = {
  [P in keyof T]-?: Equal<
    { [O in P]: T[P] },
    { -readonly [O in P]: T[P] }
  > extends true
    ? never
    : P
}[keyof T]

GetReadonlyKeys<{ readonly x?: string, y: string, readonly z: number }>
// { readonly x: "x", y: never, readonly z: "z" }["x" | "y" | "z"]
// "x" | "z"

# 资源

utility-types (opens new window)
type-challenges (opens new window)

💗 博主正处于裸辞待业状态,欢迎 商务合作 💗

相关文章

从 React 看前端 UI 代码范式革命

alt text 前言 本来打算写的主题是“我为什么讨厌 React Hooks API”,展开聊聊“小甜甜”是如何变成“牛夫人”的,没想到越写越严肃:) React 是两次前端范式革命的引领者,至今仍有繁荣的社区和旺盛的创造力; React 多次天才又激进的创新,一些想法被借鉴改良、一些引发广泛质疑,大部分是被认同和接受的; ...

一句话总结:TS 何时选择 interface 或 type

用 interface 描述类型的结构,用 type 描述类型关系。 有点编程基础中数据结构与算法的味道。 结构即是类型的属性集合 // 如 Point3D 的属性集合: x, y, z。 interface Poi ...

JS 定时器时长控制细节

背景 JS 最常使用 setTimeout、setInterval 来延迟或定时循环执行函数,通常会传递第二个参数来控制延迟或间隔执行的时间。 但开发者必须意识到函数执行时间并非精确地符合预期,在以下场景中它会超出你的预期 CPU 繁忙(主线程被长时间占用),JS 无法按开发者设定的预期时间延迟函数 定时器过于频繁地执行(第二个参数 < 4),达到一定 ...

JS 多线程并发

[[toc]] 为什么需要并发 我们常听说 JS 是单线程模型,即所有代码都在主线程中执行的。 如果某些任务计算量较大,将阻塞主线程,UI 界面轻则掉帧、重则卡死。 // 提示:本文所有代理均可复制到浏览器控制台中执行,验证效果 // ...

跨域(Options)请求介绍及解决方法

介绍 OPTIONS请求指method为OPTIONS的http请求。 通俗来说:它的作用是用于WEB服务器是否支持某些 header,也可以叫做预检请求(顾名思义:预先检测)。 程序员:跨域发送 http get { headers { xxx: abc } } 浏览器:等等,你这个请求有点奇怪,我去跟服务器确认下 浏览器:发送 http options ...

单测(Unit Test)技巧

前言 本文目的是提高编写单测的效率,适合于有一定单测编写经验,但被单测困扰的同学。 后文的示例都在 unit-test-examples 仓库中。 单测的意义与价值 单测本质:将测试行为及结果固化下来,自动检查被测试代码的运行结果是否符合期望。 单测是一 ...

compilerOptions字段详解

{ "compilerOptions": { /* 基本选项 */ "target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT' "module": ...

JS 正则表达式基础

前言 个人经验,正则是一个前期少量投入,回报超高的技能点。 其适用范围非常广泛,如批量文本处理、源码替换、程序中逻辑判断等等。 本文只介绍常用的基础知识、技巧,让初学者快速掌握大部分日常所需的正则知识。目标是 5 分钟内可逐字读完,10 分钟内可把例子都动手实践一 ...

JS优缺点

回顾上两期 优点(简单) 对象 链(原型链 & 作用域链) 一切都是对象(包括函数),构建世界的原料,越少越简单、灵活。 jimu 观察者模式,例: class Observer { constructor() { this.subscribe ...

JS 数据处理

[[toc]] 分享目标 JS 数据处理技巧速成 让你感叹:JS 还可以这样写 基础 实践原则 数据处理时,尽量避免创建临时变量(特别是 let)、修改参数、改变外部引用、for、if 等 分离 数据处理 与 副作用(DOM 操作、存储、网络请求等)代码 数据处理指:数值计算和数据结构变换。 第一点:如何避免? **1. 熟 ...