學(xué)習(xí) TypeScript 類型
- 項(xiàng)目地址 Github
- 項(xiàng)目描述: 高質(zhì)量的類型可以幫助提高項(xiàng)目的可維護(hù)性,同時(shí)避免潛在的錯(cuò)誤张咳。
優(yōu)勢(shì)
- 提高 TypeScript 技術(shù)能更好的在編譯階段處理一些問(wèn)題, 這些問(wèn)題可能在運(yùn)行時(shí)侵占更多的資源. 例如一個(gè)只讀的對(duì)象屬性, 在 JavaScript 中的一種實(shí)現(xiàn)方式就是在該屬性的 setter 中拋出異常. 在TS中使用關(guān)鍵字即可在編譯階段就阻止這樣的代碼寫(xiě)入程序.
// 運(yùn)行時(shí)
class Husky {
set a () {
throw new Error('a is a readonly propertype')
}
}
class Husky {
readonly a
}
- 提升程序穩(wěn)定性, 可預(yù)期的代碼行為, 更好的代碼說(shuō)明性.
劣勢(shì)
- 引入TypeScript 就是引入復(fù)雜度, 項(xiàng)目必須編寫(xiě)更多運(yùn)行時(shí)以外的說(shuō)明代碼.
- 對(duì)程序人員技術(shù)要求更高, 一個(gè)復(fù)雜的類型約束可能非常不好讀. 項(xiàng)目最后很有可能編程 AnyScript
- 并完全不能替代運(yùn)行時(shí)的類型校驗(yàn), 因?yàn)閿?shù)據(jù)很有可能都是后端傳遞過(guò)來(lái)的無(wú)法控制. Superstruct 運(yùn)行時(shí)類型檢查
Easy 版需要注意的事情
- 需要了解基本的語(yǔ)法含義比如
extends
keyof
typeof
infer
...
他們有些和 JavaScript 語(yǔ)法形式一致. 但含義并不相同. 類型操作是在類型命名空間. 值操作(即JavaScript)是在值命名空間進(jìn)行. 所以在編譯后抹去所有的類型操作, 并不會(huì)影響程序運(yùn)行. - 基于上一條, 剛進(jìn)行類型挑戰(zhàn)的人. 比如剛才的我, 很難注意到類型和值邊界. 所有的類型的操作所返回的東西依然是類型, 盡管它看起來(lái)是一個(gè)值. 比如:
type Exclude<'a' | 'b', 'a'> => 'b'
你可以去源碼倉(cāng)庫(kù)去在線實(shí)操一下. 更容易掌握. 更多的類型技術(shù)需要更長(zhǎng)的時(shí)間進(jìn)行積累. 下面我們開(kāi)始吧.
Note:
- 有意思的是校驗(yàn)挑戰(zhàn)的正確性的程序本身也是 TypeScript 的類型語(yǔ)法. 所以 TypeScript 自稱是圖靈完備的. 但是項(xiàng)目中的這句話被橫線劃掉了, 這看起來(lái)就很有意思了, 我們隨著挑戰(zhàn)的進(jìn)行繼續(xù)看下去吧.
- 練習(xí)時(shí)也可以使用編輯器新建TS文件, 通過(guò)這種方式進(jìn)行校驗(yàn).
Pick
Easy
, #union
, #built-in
實(shí)現(xiàn) TS 內(nèi)置的 Pick<T, K>
帝洪,但不可以使用它。
從類型 T 中選擇出屬性 K脚猾,構(gòu)造成一個(gè)新的類型葱峡。
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
答案
type MyPick<T, U extends keyof T> = {
[K in U]: T[K]
}
// U = keyof T = ['title', 'description', 'completed']
// K = 循環(huán) U
Readonly
Easy
, #union
, #built-in
不要使用內(nèi)置的 Readonly<T>
,自己實(shí)現(xiàn)一個(gè)婚陪。
該 Readonly 會(huì)接收一個(gè) 泛型參數(shù)族沃,并返回一個(gè)完全一樣的類型,只是所有屬性都會(huì)被 readonly 所修飾。
也就是不可以再對(duì)該對(duì)象的屬性賦值脆淹。
interface Todo {
title: string
description: string
}
const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar"
}
todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property
答案
type MyReadonly<T> = {
readonly[K in keyof T]: T[K]
}
Tuple to Object
Easy
傳入一個(gè)元組類型常空,將這個(gè)元組類型轉(zhuǎn)換為對(duì)象類型,這個(gè)對(duì)象類型的鍵/值都是從元組中遍歷出來(lái)盖溺。
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type result = TupleToObject<typeof tuple>
// expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
答案
type TupleToObject<T extends readonly any[]> = {
[K in T[number]]: K
}
// T = ['string', 'string', 'string', 'string']
// T[number] = tuple
// K = 循環(huán) T[number] = 循環(huán) tuple
// readonly 對(duì)應(yīng) as const
First of Array
Easy
, #array
實(shí)現(xiàn)一個(gè)通用 First<T>
漓糙,它接受一個(gè)數(shù)組T
并返回它的第一個(gè)元素的類型。
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3
答案
type First<T extends any[]> = T extends [] ? never : T[0]
Length of Tuple
創(chuàng)建一個(gè)通用的 Length
烘嘱,接受一個(gè) readonly
的數(shù)組昆禽,返回這個(gè)數(shù)組的長(zhǎng)度。
type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']
type teslaLength = Length<tesla> // expected 4
type spaceXLength = Length<spaceX> // expected 5
答案
type Length<T extends readonly any[]> = T['length']
Exclude
Easy
, #built-in
實(shí)現(xiàn)內(nèi)置的 Exclude <T, U>
類型蝇庭,但不能直接使用它本身醉鳖。
type MyExclude<'a' | 'b' | 'c', 'a'> // Exclude<'a' | 'b' | 'c', 'a'>, 'b' | 'c'
type MyExclude<string | number | (() => void), Function> // Exclude<string | number | (() => void), Function> // string | number
答案
type MyExclude<T, U> = T extends U ? never : T
// Exclude = T - U
Awaited
Easy
, #promise
, #built-in
假如我們有一個(gè) Promise 對(duì)象,這個(gè) Promise 對(duì)象會(huì)返回一個(gè)類型哮内。在 TS 中盗棵,我們用 Promise 中的 T 來(lái)描述這個(gè) Promise 返回的類型。請(qǐng)你實(shí)現(xiàn)一個(gè)類型北发,可以獲取這個(gè)類型纹因。
type X = MyAwaited<Promise<string>> // string
type Y = MyAwaited<Promise<{ field: number }>> // { field: number }
type Z = MyAwaited<Promise<Promise<Promise<string | number>>>> // string | number
答案
type MyAwaited<T extends Promise<any>> = T extends Promise<infer U>
? U extends Promise<any>
? MyAwaited<U>
: U
: T
If
Easy
, #utils
實(shí)現(xiàn)一個(gè) IF
類型,它接收一個(gè)條件類型 C
琳拨,一個(gè)判斷為真時(shí)的返回類型 T
瞭恰,以及一個(gè)判斷為假時(shí)的返回類型 F
袱巨。 C
只能是 true 或者 false济赎, T
和 F
可以是任意類型。
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
答案
type If<C extends boolean, T, F> = C extends true ? T : F
Concat
Easy
, #array
在類型系統(tǒng)里實(shí)現(xiàn) JavaScript 內(nèi)置的 Array.concat 方法栓票,這個(gè)類型接受兩個(gè)參數(shù)密任,返回的新數(shù)組類型應(yīng)該按照輸入?yún)?shù)從左到右的順序合并為一個(gè)新的數(shù)組陕截。
type Result = Concat<[1], [2]> // expected to be [1, 2]
答案
type Concat<T extends any[], U extends any[]> = [...T, ...U]
Includes
Easy
, #array
在類型系統(tǒng)里實(shí)現(xiàn) JavaScript 的 Array.includes 方法,這個(gè)類型接受兩個(gè)參數(shù)批什,返回的類型要么是 true 要么是 false。
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
答案
type Equal<X, Y> = <T>() => T extends X ? 1 : 2 extends <T>() => T extends Y ? 1 : 2 ? true : false
type Includes<T extends readonly any[], U> =
T extends [infer First, ...infer Rest]
? Equal<U, First> extends true
? true
: Includes<Rest, U>
: false
Push
Easy
, #array
在類型系統(tǒng)里實(shí)現(xiàn)通用的 Array.push
type Result = Push<[1, 2], '3'> // [1, 2, '3']
type Push<T extends any[], U> = [...T, U]
Unshift
Easy
, #array
在類型系統(tǒng)里實(shí)現(xiàn)通用的 Array.unshift
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
type Unshift<T extends any[], U> = [U, ...T]
Parameters
Easy
, #infer
, #tuple
, #built-in
實(shí)現(xiàn)內(nèi)置的 Parameters 類型
const foo = (arg1: string, arg2: number): void => {}
const bar = (arg1: boolean, arg2: { a: 'A' }): void => {}
const baz = (): void => {}
type MyParameters<typeof foo> // [string, number]
type MyParameters<typeof bar> // [boolean, { a: 'A' }]
type MyParameters<typeof baz> // []
]
type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer U) => any ? U : never