[译]<<Effective TypeScript>> 高效TypeScript62个技巧 技巧6-7

477 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.

了解类型系统

本章主要的带大家理解和掌握ts的类型系统

技巧6: 用你的IDE(代码编辑平台)来查询,探索ts的类型系统

IDE可以选择webStorm或者vscode

我们可以用IDE学习类型系统:

  1. 查看变量的类型. 鼠标悬浮到变量名上, 就能显示该变量的类型

    image.png

  2. 查看函数的类型:

    image.png 这里需要注意的: 类型系统可以自动推断函数的返回值类型, 当然你也可以自己设置返回值类型

  3. 查看if分支内变量的类型 image.png 类型系统可以根据if条件,来修订变量类型

  4. 查看对象属性值的类型: image.png

  5. 在操作链中查看类型: image.png

除了上面的方法, 我们还可以通过报错来学习类型系统:

function getElement(elOrId: string|HTMLElement|null): HTMLElement {
  if (typeof elOrId === 'object') {
    return elOrId;
 // ~~~~~~~~~~~~~~ 'HTMLElement | null' is not assignable to 'HTMLElement'
  } else if (elOrId === null) {
    return document.body;
  } else {
    const el = document.getElementById(elOrId);
    return el;
 // ~~~~~~~~~~ 'HTMLElement | null' is not assignable to 'HTMLElement'
  }
}

第一个报错: 第一个判断条件本应该筛选elOrIdHTMLElement, 但是奇怪的是:在js中, typeof null 也是 'object', 所以 elOrId可能为 HTMLElement, 也可能为null.

第二个报错: document.getElementById 可能返回null 也可能返回HTMLElement

除了上面的好处, ts的类型系统还可以帮你查询库里面的变量, 函数类型等.

例如: 代码里面有fetch函数, 你想多了解了解:对准fetch函数右键,选择Go to Definition

image.png

你就可以查阅fetch函数的参数和返回值:

declare function fetch(
  input: RequestInfo, init?: RequestInit
): Promise<Response>;

同样的方法, 你可以查阅RequestInfo的类型申明.

type RequestInfo = Request | string;

同样的方法, 你可以查阅Request的类型申明.

declare var Request: {
    prototype: Request;
    new(input: RequestInfo, init?: RequestInit): Request;
};

同样的方法, 你可以查阅RequestInit的类型申明.

interface RequestInit {
    body?: BodyInit | null;
    cache?: RequestCache;
    credentials?: RequestCredentials;
    headers?: HeadersInit;
    // ...
}

技巧7: 把类型看成值的集合

最好把类型看做值组成的集合

什么意思? 举个例子:

number 可以考虑成由number值组成的集合, 包括有42, -37.25 但是不包括 'canada'.
当你设置ts的配置strictNullChecks为true时, null 和 undefined 也不属于number集合.

那什么是最小的集合?

空集合, 对应的type是never. 不能将值分配给, 类型为never的变量, 例如:

const x: never = 12;
   // ~ Type '12' is not assignable to type 'never'

下一个最小的集合是什么?

只包含单个值, 例如:

type A = 'A';
type B = 'B';
type Twelve = 12;

你可以联合单个值的类型:

type AB = 'A' | 'B';
type AB12 = 'A' | 'B' | 12;

在ts错误中常出现一个单词: assignable, 用值的集合理解, 这个单词的意思: 属于xxx的成员, 或者说属于xxx的子集:

const a: AB = 'A';  // OK, value 'A' is a member of the set {'A', 'B'}
const c: AB = 'C';
   // ~ Type '"C"' is not assignable to type 'AB'

'A' 就是 type AB 的成员, 或者说子集

'C' 就不是

上述类型作为集合很好理解, 因为他们集合中的元素个数是有限的, 但是实际工作中, 遇到大部分的类型集合的元素个数都是无限的 例如:

interface Identified {
  id: string;
}

同时把类型考虑成集合, 那么类型也有集合中常见的操作: 交、并。 用符号 &, | 来表示. 看下面的集合:

interface Person {
  name: string;
}
interface Lifespan {
  birth: Date;
  death?: Date;
}
type PersonSpan = Person & Lifespan;

用&来计算Person , lifespan的交集. 第一眼看,发现Person, lifespan 没有相同属性, 那么他们的交集PersonSpan应该是空集.

但是要记住: 类型是值的集合, 而不是属性的集合.

其实:

const ps: PersonSpan = {
  name: 'Alan Turing',
  birth: new Date('1912/06/23'),
  death: new Date('1954/06/07'),
};  // OK

为什么呢?

根据类型的兼容原则(见技巧1-技巧5), 属于PersonSpan的变量, 一定属于Person和属于Lifespan.

所以根据交集的定义PersonSpan = Person & Lifespan;

keyof可以得到类型的所有属性. 实际上:

keyof (A&B) = (keyof A) | (keyof B)
keyof (A|B) = (keyof A) & (keyof B)

所以:

keyof (Person & Lifespan) = keyof(Person) | keyof(Lifespan) = (name) | (birth, death) = (name,birth, death)

keyof (Person | Lifespan) = keyof(Person) & keyof(Lifespan) = (name) & (birth, death) = 空

但其实我们不常用 | 符号, 我们更到用extends.

interface Person {
  name: string;
}
interface PersonSpan extends Person {
  birth: Date;
  death?: Date;
}

extends 什么意思? 也就是'属于xxx','xxx的子集'.

再来看一个例子:

interface Vector1D { x: number; }
interface Vector2D extends Vector1D { y: number; }
interface Vector3D extends Vector2D { z: number; }

可以说Vector3D 是 Vector2D的子集, Vector2D 是Vector1D的子集. 也可以理解为: Vector3D 继承了Vector2D所有的属性.

image.png

当然extends在泛型中显示为约束.如:

function getKey<K extends string>(val: any, key: K) {
  // ...
}

K extends string意思是K被约束为string. 所以有:

getKey({}, 'x');  // OK, 'x' extends string
getKey({}, Math.random() < 0.5 ? 'a' : 'b');  // OK, 'a'|'b' extends string
getKey({}, document.title);  // OK, string extends string
getKey({}, 12);
        // ~~ Type '12' is not assignable to parameter of type 'string'

最后: 并不是所有值的集合都能对应到ts的类型,例如没有一个ts的类型表示所有整数. 不过有时候你可以用Exclude排除部分类型:

type T = Exclude<string|Date, string|number>;  // Type is Date
type NonZeroNums = Exclude<number, 0>;  // Type is still just number