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

 

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