TypeScript 复习与进阶三部曲 (2) – 把 TypeScript 当编程语言使用

前言

上一篇, 我们提到, TypeScript 进阶有 3 个阶段. 

第一阶段是 "把 TypeScript 当强类型语言使用", 我们已经介绍完了. 

第二阶段是 "把 TypeScript 当编程语言使用"

这篇主要就是介绍这个.

 

逻辑类型

C# 是没有办法表达出类型间的逻辑关系的. 你不能表达 "这个变量的类型是那个函数的第一个参数类型".

但 TS 可以!

function doSomething(str: string): void {}

// const str : string = ''; // C# 只能 hardcode 声明类型是 string
const str: Parameters<typeof doSomething>[0] = ''; // TS 可以表达出 "这个变量的类型是那个函数的第一个参数类型"

Parameters<typeof doSomething>[0] 的意思是, 这个类型是 doSomething 函数的第一个参数类型.

我们先不去理解这个语法是怎么回事. 只要知道 TS 可以表达出这个意思就好了.

 

类型 transform

类型  A > transformation> 类型 B

transform 指的是从一个类型, 经过一个 transform process, 变成另一个类型.

这有点像 RxJS stream 中间一堆 pipe operator 那样.

看例子

// 有个 Person Object Literal Type
type Person = {
  str: string;
  num: number;
};

// 我想搞一个 PromisePerson, 它拥有所有 Person 的属性, 只是类型变成 Promise<原本类型>

// hardcode 的方式
type PromisePerson = {
  str: Promise<string>;
  num: Promise<number>;
};

// transform 的方式
type PromisePerson1 = { [key in keyof Person]: Promise<Person[key]> };

我们先不关心它的语法, 只要知道 TS 有这个表达力就好.

 

由浅入深 (TS 基础编程语法)

我们先学几招简单的 TS 编程语法 >  然后再学几招 TS build-in 封装好的 Utility > 最后再把所有语法学全 > 下一篇我们才学如何把语法融会贯通写出自己的 Utility.

define variable

编程语言最基本的功能之一就是定义变量, 定义变量有两个主要目的, 一是为了 code study, 二是为了复用.

上一篇我们介绍过 Type Aliases, 它其实就是 TS 语言中的 define variable (声明变量)

type WhatEverName = string;
type NumberOrStringArray = number[] | string[];

TS 整个语言的目的就是去声明/管理 JS 的类型. 所以 TS variable 的 value alwasy is Type, 这个概念我们要记清楚.

上面例子中, 我声明了 2 个 variables, 分别存放了不同的类型, (注: TS 的 variable case style 是 CamelCase 哦).

typeof

typeof 是 JS 的语法, 但同时它也是 TS 的语法.

首先我们要先学会区分 TS 语句 和 JS 语句.

const value = typeof ''; // 这句是 JS
type MyType = typeof value; // 这句是 TS

很简单, 哪一句是 JS 你一定会知道, 其余的就是 TS 咯 (TS 语句基本上都是 starts with 'type' 或者 'declare ' 这类的)

那 typeof 有啥用呢? 

它可以从 JS 变量中提取出 TS 类型.

上面例子中 value 的类型是 string, 在 TS 语句中通过 "typeof value" 引入 JS 语句的 value, 它就能把 value 的类型放入到 MyType 变量中.

为什么要这样搞呢? 让 TS 语句和 JS 语句这样传来传去不是挺乱的吗? 

之所以需要 typeof 是因为类型推断. 为了少写代码, 许多时候我们是不声明类型的. 而这时碰巧我们需要利用这些类型, 那么就只能从 JS 语句中提取了.

在看几个例子感受以下

function doSomething(str: string) {
  return 0;
}
class Person {}
const values = [1, 2, 3];

type MyFunction = typeof doSomething; // (str: string) => number
type MyArray = typeof values; // number[]
type MyClass = Person; // 注意: class 不需要 typeof

注意 class 是不需要 typeof 的哦.

collection / array / set

集合也是一门语言重要的概念, 用来表达集合的 TS 语法是 Union Types.

type MyArrayType = string | number | boolean;

// 用 JS 来描述大概是这样
const myArrayType = ['string', 'number', 'boolean'];

有了集合就少不了迭代语法. 但这里我们先不展开迭代语法. 下一 part 才详细讲.

function & call

编程语言除了有变量, 集合概念, 还有一个重要的概念就是函数. TS 的变量用来封装类型, 函数则用来封装 transform 的过程.

它长这样

type MyFunction<T> = T | string;

type MyType = MyFunction<number>; // MyType is number | string

有点像是 Type Aliases + 泛型的味道. 但我不建议你这样去理解它. 把它当函数看就好了.

MyFunction 是函数的名字.

<T> 是参数

= T | string 是 return

MyFunction<number> 就是 call 这个函数并传入参数 number (记住, TS 都是在处理类型, 所以参数值都是类型)

type MyFunction<T> = T | string;
type MyType = MyFunction<number>; // MyType is number | string

// 用 JS 来描述大概是这样
const myFunction = (value) => [value, 'other value'];
const myType = myFunction('value'); // myType = ['value', 'other value']

小结

到这里, 我们介绍了 TS 作为编程语言的 3 大特性, variable 变量, set 集合, function 函数.

利用这些编程手法, 我们就可以表达出逻辑类型和 transform 类型了. 下一 part 我们学一些 TS build-in 的 Utility

 

TypeScript Build-in Utility

参考: Docs – Utility Types

Utility 是啥? 用过 JS Lodash Library 的就知道, 它就是许许多多小方法的库, 其目的就是让代码复用和干净. 、

TS 的 Utility 也是同样的, 它有许许多多小方法 (TS function), 让我们 transform 类型.

这 part, 我们不去探索这些 utility function 底层语法是怎样的, 我们只学它的功效, 调用就好.

Partial<Type>

Partial 函数的功能是 transform Object Literal / Class / Interface (之后统称 Object) 的所有属性变成 optional property.

type Person = {
  str: string;
  num: number;
};

type PartialPerson = Partial<Person>;

// 等价于
type PartialPerson1 = {
  str?: string; // optional property
  num?: number; // optional property
};

题外话, 学习这类 Utility 库 (e.g. RxJS operators), 具体使用场景不重要, 我们把所有方法过一遍, 有个印象就好. 等项目遇到问题的时候, 回来翻一翻就可以了.

Required<Type>

和 Partial 相反, 它是把 optional property 变成 not optional

type PartialPerson = {
  str?: string;
  num?: number;
};

type Person = Required<PartialPerson>;

// 等价于
type Person1 = {
  str: string;
  num: number;
};
View Code

Readonly<Type>

顾名思义, 就是把 Object / Array 变成 Readonly

type Obj = { str: string; num: number };
type Arr = string[];

type ReadonlyObj = Readonly<Obj>;
type ReadonlyArr = Readonly<Arr>;

// 等价于
type ReadonlyObj1 = {
  readonly str: string;
  readonly num: number;
};

type ReadonlyArr1 = readonly string[];
View Code

Record<Keys, Type>

Record 用来创建 Object Literal, 特色是所有属性拥有相同的类型.

type Obj = Record<'key1' | 'key2' | 'key3', string>;

// 相等于
type Obj1 = {
  key1: string;
  key2: string;
  key3: string;
};

第一个参数是放入所有的属性 keys, 它用一个集合来表示, 也就是 Union Types + String Literal

第二个参数是属性的 value, 也就是类型 (记住, TS 都是在处理类型, 我重复很多次了)

Record 函数最终会返回 Object Literal 类型, 它拥有所有的 keys 属性, 每一个属性的类型都相同, 就是第二参数传入的类型.

Pick<Type, Keys>

Pick 是从一个 Object 里选出指定的 keys 保留, 去掉其余的属性.

type Obj = {
  str: string;
  num: number;
  bool: boolean;
};

type PickedObj = Pick<Obj, 'str' | 'bool'>; // 只保留 str 和 bool

// 相等于
type PickedObj1 = {
  str: string;
  bool: boolean;
  // 没有 num 了
};

Omit<Type, Keys>

Omit 和 Pick 相反, 它是选择要删除的, 没有选中的则保留下来.

type Obj = {
  str: string;
  num: number;
  bool: boolean;
};

type OmitedObj = Omit<Obj, 'str' | 'bool'>; // 删除 str bool 只留下 num

// 相等于
type OmitedObj1 = {
  num: number;
};
View Code

Exclude<UnionType, ExcludedMembers>

参数 1, 2 都是集合. 什么类型的集合都可以.

参数 1 是所有类型, 参数 2 是声明不要保留的类型 (和上面 Omit 的概念差不多, 其实 Omit 底层就是用了 Exclude 函数来完成的哦)

type Keys = 'key1' | 'key2' | 'key3' | 'key4';
type ExcludedKeys = Exclude<Keys, 'key1' | 'key3'>; // left 'key2' | 'key4'

type Arr = boolean | string | number | null;
type ExcludedArr = Exclude<Arr, boolean | number>; // left string | null

// 相等于
type ExcludedKeys1 = 'key2' | 'key4';
type ExcludedArr1 = string | null;

来一个复杂点的

type Types = 'key1' | 'key2' | 'key3' | number | boolean;
type ExludedTypes = Exclude<Types, string>; // left number | boolean

string 把 'key1' | 'key2' | 'key3' 都给 exclude 掉了, 其实不难理解, 因为 'key1' 是 String Literal, 它是"一种" string, 所以当声明要把 string exclude 掉时, 它自然也需要被 exclude 掉.

Extract<Type, Union>

它时 Exclude 的相反. 参数 2 声明的类型都是要保留的类型.

type Types = 'key1' | 'key2' | number | boolean;
type ExtractedTypes = Extract<Types, string | boolean>; // left boolean | "key1" | "key2"

NonNullable<Type>

参数是一个类型集合, 它会把集合内的 null 和 undefined 类型过滤掉, 留下其它的

type Types = string | undefined | number | null | boolean;
type NonNullableTypes = NonNullable<Types>; // left: string | number | boolean

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章