TypeScript 类型经验本

1,001 阅读6分钟

这篇文章讲解类型。莫要让 TypeScript 成为 AnyScript,学习类型也是从简单复杂

范型参数变量常用理解

  • T 表示 type 类型
  • P 表示 props 属性
  • K/V key/value
  • U 常用的类型变量

类型中的关键字和操作符

复杂类型中常用的关键字和操作符

  • keyof 相当于 Object.keys
  • in 相当于 for-in
  • ? 可选操作符
  • -? 可选取反
  • extends
    • interface/class 表示继承
    • type 中表示约束
    • ...
  • []
    • 类型属性访问
    • 类型计算

签名

定义函数的输入和输出

严格的类型检查

为了更好的使用 TypeScript, 需要开启严格的 TypeScript 代码检查:

  • noImplicitAny 没有隐式的 any 类型推断
    • 错误提示:Parameter 'a' implicitly has an 'any' type.ts(7006)
  • strictNullChecks 严格的 null 检查
    • 默认情况下,值 null 和 undefined 可分配给任何其他类型。
{
    "compilerOptions": {
        "noImplicitAny": true,
        "strictNullChecks": true,
    }
}

严格的 null 检查 最佳实践1 , 在处理空指针的情况下,非常实用。

type User = {
  name: string;
  age?: number; // 可选的属性,不存在时,不能使用其上面的属性和方法
}

const getUser = (user: User) => {
  console.log(user.name, user.age.toString()) // user.age 🐛 可能不存在但是调用方法
}

age 是可选的,但是在 console.log 中调用了 toString 方法。

  • ts: Object is possibly 'undefined'.ts(2532) 我们可以使用 ?. 来解决这个🐛,严格检查
console.log(user.name, user.age?.toString())

基础类型

  • boolean 布尔类型
  • string 字符串类型
  • void/null/undefined 三种空类型
  • any/any[] 类型
  • tuple 元祖类型
  • 枚举类型
  • never/unknown
  • 函数类型
    • 函数/构造函数字面量类型
    • 函数调用类型
    • 构造函数签名

按级别划分类型

顶级类型 top type

  • any/unknown. 包含所有类型的集合。

底部类型 bottom type

  • never 空集合

从类型 type 引出接口 interface

在 TypeScript 中,使用 type 关键字,定义类型别名

type User = number | string; // 定义 number | string 的联合类型为 User

⚠️:别名 type 一旦定义定义就不能扩展自己,只能用 type 定一个其他的类型。基于别名 type 自生不可扩展,引出 接口 interface 的概念就很合适了。

interface Global {
    api1: number
}

我们想要扩展 Global 接口,就可以直接重新定义(重新定义不是简单的覆盖行为)

 interface Global {
     api2: number
 }

api2接口属性是补充在 Global 属性上面的,而不是覆盖之前定义的 api1。

TypeScript 对象类型 Object Types

对象类型应该是我们最常用的复合类型在之一了。

定义对象类型的方式:

  • interface
  • type

他们的最主要的区别之一就是:能不能不产生新的类型的基础上扩展自己。

可选/只读的类型属性

  • 可选: ?.<your option>
  • 只读: readonly <you option>

索引类型

有时候,我们不确定属性的名称,此时我们就可以定义索引类型

interface User {
    [index: string]: any
}

index 表示索引,索引的类型是 string,属性对应的类型是 any

interface 的类型扩展

  1. 重新定义接口,然后补充新的属性,不产生一个新的接口
  2. 使用 extend 关键字补充,产生一个新的接口

type 的类型补充

  1. 使用 | 联合类型,进行扩展,产生一个新的类型。

交叉类型

两个类型交叉,将两个不同类型组合成一个新的类型没有重复的新的类型

type A = {
    name: string
    age: number
}

type B = {
    age: number
    sex: string
}

const C: A & B = {
    name: 'sdfd',
    age: 123,
    sex: 'male'
}

const D: A | B = {
    name: 'sdf',
    age: 123
}

重要关键字

  • extends 赋值(也可以叫分配) 。 广泛应用于条件类型,是高级类型的基石。
  • keyof 获取索引值,并组合成新的索引值联合类型。

类型进阶

  • 泛型
  • 联合类型
  • 交叉类型
  • 索引类型
  • 映射类型
  • 条件类型

范型

范型,有一个广义上的类型,但是这个类型具体还不确定,由于不确定所有可以是多种类型。是一对多的关系。常使用 T, U 表示范型,常用于函数参数的约束。

function fn<T>(a: T) {};

T 就范型类型,范型类型确定后,fn 函数参数的类型就确定了。 T 可以对应多个类型。

联合类型

开始可以粗糙的理解为: 数学中的并集。联合类型使用 |操作符来联合类型

type A = string | number; // A 就是 string 和 number 的联合类型,A 可以是 string 也可以是 number;

注意,黏合类型在切换类型的时候,整个值切换

type B = boolean | {z: string};

let binst: B = {z: 123};
binst = false;
// 整个切换之后就能在访问 z 属性了。

交叉类型

开始可以粗糙的理解为: 数学中的交集。联合类型使用 &操作符来交叉类型

type X = string & number; // never 我们知道 string 和 number 类型是没有相交的可能性的

所以交叉类型常用于一下相对复杂点的类型.

type C = string | number;
type D = string | Function

type CxD = C & D; // string

// 示例
const h: CxD = 'this is cxd type'; // 类型检查成功
const hF: CxD = 12312; // 类型检查失败
const gF: CxD = () => {}; // 类型检查失败

索引类型

类型中也有索引操作,获取索引,设置索引

  • keyof 获取类型索引
  • 使用 [] 来定义索引类型和索引值类型
// 访问索引
type INN = {
  a: string;
  b: string;
};

const kea: keyof INN = "a";
const keb: keyof INN = "b";
const kec: keyof INN = "c"; // 类型检查出错 没有 c 索引
type INN = {
  a: string;
  b: string;
  [index: string]: any; // 添加字符串的索引
};

const kea: keyof INN = "a";
const keb: keyof INN = "b";
const kec: keyof INN = "c"; // 类型检查不会出错,因为又添加字符串的索引

条件类型

与 js 的三元表达式相似。常与 extends 关键字以及范型配合使用形成高级类型。

T extends U ? X : Y;

理解: T 赋值给 U 之后正确吗?正确取 X 类型,否则取 Y 类型。

因为 extends 关键字是 TypeScript 高级类型的基石。所以下面会啰嗦一下,用 js 类型打好基础:

// string
type StrT = "abc" extends string ? "StrTrue" : "StrFalse"; // StrTrue
type StrF = "abc" extends unknown ? "StrTrue" : "StrFalse"; // StrFalse

// boolean
type BoolT = true extends boolean ? "BoolTrue" : "BoolFalse"; // BoolTrue
type BoolF = true extends unknown ? "BoolTrue" : "BoolFalse"; // BoolFalse

// number
type NumT = 123 extends number ? "NumTrue" : "NumFalse"; // NumTrue
type NumF = 123 extends unknown ? "NumTrue" : "NumFalse"; // NumFalse

// null
type NullT = null extends null ? "NullTrue" : "NullFalse"; // NullTrue
type NullT1 = null extends unknown ? "NullTrue" : "NullFalse"; // NullTrue
type NullT2 = null extends any ? "NullTrue" : "NullFalse"; // NullTrue
type NullT3 = null extends void ? "NullTrue" : "NullFalse"; // NullFalse
type NullT4 = null extends boolean ? "NullTrue" : "NullFalse"; // NullFalse
// ...

// object
type ObjT = {} extends Object ? "ObjTrue" : "ObjFalse"; // ObjTrue
type ObjT1 = {} extends unknown ? "ObjTrue" : "ObjFalse"; // ObjTrue
type ObjT2 = {} extends any ? "ObjTrue" : "ObjFalse"; // ObjTrue
type ObjT3 = {} extends void ? "ObjTrue" : "ObjFalse"; // ObjFalse
type ObjT4 = {} extends boolean ? "ObjTrue" : "ObjFalse"; // ObjFalse
type ObjT5 = {} extends null ? "ObjTrue" : "ObjFalse"; // ObjFalse

参考

Footnotes

  1. 最佳的 TypeScript Null 类型检查实践