🐟 Blog

TypeScript 类型编程指南

September 25, 2022

两个视角:

  • 类型系统是集合
  • 类型系统是编程语言

类型是集合

元素交集与并集

type S1 = 1 | 2 | 3
type S2 = 3 | 4 | 5
type S3 = 6 | 7 | 8
type S4 = 1 | 2

type R1 = S1 | S2 // 1 | 2 | 3 | 4 | 5
type R2 = S1 & S2 // 3
type R4 = Exclude<S1, S2> // 1 | 2
type R5 = S4 extends S1 ? "Yes" : "No"
type R6 = S3 extends S1 ? "Yes" : "No"

全集与空集

空集:never

(1 | 2 | 3) & (4 | 5 | 6) // never
keyof {}                  // never

全集:unknown

// In an intersection everything absorbs unknown
type T00 = unknown & null;              // null
type T01 = unknown & undefined;         // undefined
type T02 = unknown & null & undefined;  // null & undefined (which becomes never in union)
type T03 = unknown & string;            // string
type T04 = unknown & string[];          // string[]
type T05 = unknown & unknown;           // unknown
type T06 = unknown & any;               // any

// In a union an unknown absorbs everything
type T10 = unknown | null;              // unknown
type T11 = unknown | undefined;         // unknown
type T12 = unknown | null | undefined;  // unknown
type T13 = unknown | string;            // unknown
type T14 = unknown | string[];          // unknown
type T15 = unknown | unknown;           // unknown
type T16 = unknown | any;               // any

unknownany 的区别

any 相当于关闭了类型检查,可以对 any 做任何任何操作,改成 unknown 后都是编译错误:

function f1(a: any) {
  a.foo
  a["foo"]
  a()
}

function f2(a: unknown) {
  a.foo    // error
  a["foo"] // error
  a()      // error
}

必须对 unknown 的值做出相应的类型检查:

function f2(a: unknown) {
	if (typeof a == 'function') {
		a()
	}

	if (typeof a == 'object' && a != null && "foo" in a) {
		a["foo"]
	}
}

https://github.com/microsoft/TypeScript/pull/24439

类型是编程语言

TypeScript = JavaScript + 类型语言:

JavaScript类型语言
作用于集合
存在于运行时编译时

JavaScript 和类型语言之间的交互:

  • JavaScript → 类型语言:foo: Foo
  • 类型语言 → JavaScript:typeOf foo

绑定声明

const obj = {name: 'foo'}
type Obj = {name: string}

条件判断

https://www.typescriptlang.org/docs/handbook/2/conditional-types.html

SomeType extends OtherType ? TrueType : FalseType;

https://github.com/microsoft/TypeScript/pull/21316

遍历

type User = {
  name: string
  gender: "M" | "F"
}

type User1 = {
  [F in keyof User as Capitalize<F>]: User[F]
}

函数

function union(x, y) {
    return [...new Set([...x, ...y])]
}
type Union<X, Y> = X | Y

Implement the built-in Omit<T, K> generic without using it.

Constructs a type by picking all properties from T and then removing K

For example:

interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = MyOmit<Todo, 'description' | 'title'>

const todo: TodoPreview = {
  completed: false,
}

练习2:

Readonly

递归

练习:

DeepReadonly

模板字符串

type Events = 'Created' | 'Approved' | 'Terminated'
type EventHandler = `on${Events}`

https://github.com/microsoft/TypeScript/pull/40336

属性、元素访问

访问对象属性:

type Config = { key: string, value: string }
type Key = Config['key'] // string

访问数组内元素:ArrayType[number]

const MyArray = [
  { name: "Alice", age: 15 },
  { name: "Bob", age: 23 },
  { name: "Eve", age: 38 },
];

type Person = typeof MyArray[number];

练习:

实现一个类型安全的路由方法

假设有这样一个路由配置和方法:

const routes = {
    index: {
        path: "/index",
        payload: {} as { page: number }
    },
    search: {
        path: "/search",
        payload: {} as { query: string }
    }
}

declare function navigateTo(
    route: string,
    payload: Record<string, string>
)

navigateTo("index", { page: 1 })

请重构 navigateTo 的类型标注,使得以下调用都编译错误:

navigateTo("blah", { page: 1 }) // 不存在的路由
navigateTo("search", { page: 1 }) // 不匹配的路由参数配置

参考资料

An introduction to type programming in TypeScript

https://github.com/type-challenges/type-challenges


© Attribution Required | 转载请注明原作者与本站链接
© 2022 yujinyan.me