爲什麼你非常不適應 TypeScript
前言
在羣裏看到一些問題和言論:爲什麼你們這麼喜歡 “類型體操”?爲什麼我根本學不下去 TypeScript?我最討厭那些做類型體操的了;爲什麼我學了沒過多久馬上又忘了?
有感於這些問題,我想從最簡單的一個角度來切入介紹一下 TypeScript,並向大家介紹並不是只要是個類型運算就是體操。並在文中介紹一種基本思想作爲你使用類型系統的基本指引。
引子
我將從一個相對簡單的 API 的設計過程中闡述關於類型的故事。在這裏我們可以假設我們現在是一個工具的開發者,然後我們需要設計一個 API 用於從對象中拿取指定的一些 key 作爲一個新的對象返回給外面使用。
垃圾 TypeScript
一個人說:我纔不用什麼破類型,我寫代碼就是要沒有類型,我就是要隨心所欲的寫。然後寫下了這段代碼。
declare function pick(target: any, ...keys: any): any
他的用戶默默的寫下了這段代碼:
pick(undefined, 'a', 1).b
寫完運行,發現問題大條了,控制檯一堆報錯,接口數據也提交不上去了,怎麼辦呢?
剛學 TypeScript
一個人說:稍微檢查一下傳入類型就好了,別讓人給我亂傳參數就行。
declare function pick(target: Record<string, unknown>, ...keys: string[]): unknown
很好,上面的問題便不復存在了,API 也是基本可用的了。但是!當對象複雜的時候,以及字段並不是短單詞長度的時候就會發現了一個沒解決的問題。
pick({ abcdefghijkl: '123' }, 'abcdefghikjl')
從肉眼角度上,我們很難發現這前後的不一致,所以我們爲什麼要讓調用方的用戶自己去 check 自己的字段有沒有寫對呢?
不就 TypeScript
一個人說:這還不簡單,用個泛型加 keyof 不就行了。
declare function pick<
T extends Record<string, unknown>
>(target: T, ...keys: (keyof T)[]): unknown
我們又進一步解決的上面的問題,但是!還是有着相似的問題,雖然我們不用檢查 keys 是不是傳入的是一個正確的值了,但是我們實際上對返回的值也存在一個類似的問題。
pick({ abcdefghijkl: '123' }, 'abcdefghijkl').abcdefghikjl
-
一點小小的拓展
在這裏我們看起來似乎是一個很簡單的功能,但實際上蘊含着一個比較重要的信息。
爲什麼我們之前的方式都拿不到用戶傳入進來的類型信息呢?是有原因的,當我們設計的 API 的時候,前面的角度是從,如何校驗類型方向進行的思考。
而這裏是嘗試去通過約定好的一種規則,通過 TypeScript 的隱式類型推斷獲得到傳入的類型,再通過約定的規則轉化出一種新的類型約束來對用戶的輸入進行限制。
算算 TypeScript
一個人說:好辦,算出來一個新的類型就好了。
declare function pick<
T extends Record<string, unknown>,
Keys extends keyof T
>(target: T, ...keys: Keys[]): {
[K in Keys]: T[K]
}
到這裏已經是對類型的作用有了基礎的瞭解了,能寫出來符合開發者所能接受的類型相對友好的代碼了。我們可以再來思考一些更特殊的情況:
// 輸入了重複的 key
pick({ a: '' }, 'a', 'a')
完美 TypeScript
到這裏,我們便是初步開始了類型 “體操”。但是在本篇裏,我們不去分析它。
export type L2T<L, LAlias = L, LAlias2 = L> = [L] extends [never]
? []
: L extends infer LItem
? [LItem?, ...L2T<Exclude<LAlias2, LItem>, LAlias>]
: never
declare function pick<
T extends Record<string, unknown>,
Keys extends L2T<keyof T>
>(target: T, ...keys: Keys): Pick<T, Keys[number] & keyof T>
const x0 = pick({ a: '1', b: '2' }, 'a')
console.log(x0.a)
// @ts-expect-error
console.log(x0.b)
const x1 = pick({ a: '1', b: '2' }, 'a', 'a')
// ^^^^^^^^
// TS2345: Argument of type '["a", "a"]' is not assignable to parameter of type '["a"?, "b"?] | ["b"?, "a"?]'.
// Type '["a", "a"]' is not assignable to type '["a"?, "b"?]'.
// Type at position 1 in source is not compatible with type at position 1 in target.
// Type '"a"' is not assignable to type '"b"'.
一個相對來說比較完美的 pick 函數便完成了。
總結
我們再來回到我們的標題吧,從我對大多數人的觀察來說,很多的人開始來使用 TypeScript 有幾種原因:
-
看到大佬們都在玩,所以自己也想來 “玩”,然後爲了過類型校驗而去寫
-
看到一些成熟的項目在使用 TypeScript ,想參與貢獻,參與過程中爲了讓類型通過而想辦法去解決類型報錯
-
公司整體技術棧採用的是 TypeScript ,要用 TypeScript 進行業務編寫,從而爲了過類型檢查和 review 而去解決類型問題
諸如此類的問題還有很多,我將這種都劃分爲「爲了解決類型檢查的問題」而進行的類型編程,這也是大多數人爲什麼非常不適應 TypeScript,甚至不喜歡他的一個原因。這其實對學習 TypeScript 並不是一個很好的思路,在這裏我覺得我們需要站在設計者的角度去對類型系統進行思考。我覺得有以下幾個角度:
-
類型檢查到位
-
類型提示友好
-
類型檢查嚴格
-
擴展性十足
我們如果站在這幾個角度對我們的 API 進行設計,我們可以發現,開發者能夠很輕鬆的將他們需要的代碼編寫出來,而儘量不用去翻閱文檔,查找 example。
希望通過我的這篇分享,大家能對 TypeScript 多一些理解,並參與到生態中來,守護我們的 JavaScript。
關於本文
作者:一介 4188
https://juejin.cn/post/7248599585751515173
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/GiP53truHLickXtehNxTLA