前言
上一篇, 我們提到, 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
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; };
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[];
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; };
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