likes
comments
collection
share

《TypeScript 不完全指南》 - 变量声明

作者站长头像
站长
· 阅读数 20

前言

一个讲 TypeScript 的小册子,按揭开源中

这里不会去介绍任何关于 TypeScript 过于基础的信息,主要讲解一些在使用过程中需要注意的问题以及一些奇淫巧技。

如果你觉得阅读本文章出现了吃力以及头晕目眩,建议阅读《The TypeScript Handbook》

正文

你以为我要教你 type 和 interface 是什么?那就大错特错了!来吧,写点不一样的东西。

infer 的妙用

// 当一个类型无法通过简单的方式访问到时, 我们可以使用 infer 关键字来实现对其的快速访问
type Type0<T> = T extends (
    (...args: infer U) => any
) ? U : never

type P = Type0<(a: number, b: string) => void>
//   ^? type P = [a: number, b: string]

interface Foo<T> {
    a: { b: { c: T } }
}
type Type1<T> = T extends (
    Foo<infer U>
) ? U : never

type Q = Type1<{
//   ^? type Q = number
    a: { b: { c: string } }
}>

// 同时我们也可以将其运用于数组之中(虽然它可以通过简单的方式访问到)
type Type2<T> = T extends [infer T0, ...any[]]
    ? T0
    : never

type R = Type2<[1, 2, 3]>
//   ^? type R = 1

同时在较高版本 infer 提供了可以约束推断类型的行为(infer 默认推断的类型不可用)。

第一个增强没啥太大的用,主要用于简化代码,以及更好的提供对 infer 的类型支持,重点是第二个可以用来整点点小活。

type T0 = "1" extends `${infer T extends number}` ? T : never
//   ^? type T0 = 1
type T1 = "true" extends `${infer T extends boolean}`
//   ^? type T1 = "This is true"
    ? ([T] extends [true] ? 'This is true' : 'This is false')
    : never

其实它很像一个概念「Pattern Matching(模式匹配)」,或者说就是。

当然在 ECMAScript 中也有一个相关的提案

拓展阅读:

类型中的泛型

除了 infer 的形式声明一个“变量”,还有常见于在「Generic(泛型)」中的“变量”声明。

type T0<
    A,
    // 直接定义间接类型变量(这里并没有描述为「Constraint(约束)」,因为主要想阐述的是在这里的作用,你能这么用)
    B extends A,
    // 同时支持定义 type 一样的运算
    C extends A extends [infer A0] ? A0 : never,
    // 下面俩种不会保证类型一定正确,所以在后续的类型运算过程中可能会有问题
    D = A,
    E = A extends [infer A0] ? A0 : never,
> = {}

我们可以利用这个做一些常用类型的初始化,但是请注意,尽量减少在这里的类型定义。

在这里过多过大的类型可能会导致类型不方便被观测检察。

其次便是一个相对来说十分常用的类型定义位置了,在 Property 的位置中我们能够使用 in 运算符定义一个 string | number | symbol 类型的变量,并在 ValueType 的位置中使用它。

type T0 = {
    [K in string | number | symbol]: K
}

通过这个我们可以实现一些小需求

  • 修改前面的 Property 的类型,但是不去修改 Value 引用位置 K 的类型
  • 将某些 Property 通过一定的规则删除掉 详情可以参考 ts@4.1 - Key Remapping via as