實際專案中會運到的 Typescript 回撥函式、事件偵聽的型別定義,如果剛碰到會一臉蒙真的,我就是
這是第一次我自己對 Typescript 記錄學習,所以得先說一下我與 Typescript 的孽緣
記得最早是在2014年遇上 Typescript 當時是完全看不上這東西的,甚至帶著鄙視的心態,到不是因為它比原生 Js 要多寫很多程式碼而是
作為一名前端老兵遇上 Typescript 的語法與型別就會讓我想起剛工作時學習的 Flash Actionscript3.0 指令碼時代。不能說是完全相同,簡直是一模一樣。
大約2006年 Adobe 的 flash 9 就開發了自己的新指令碼語言 ActionScript 3 完全符合 ECMAScript 第四版規範, 也就是ES4
與當時代的 Javascript 還處於刀耕火種不同,在 Flash 編輯器中使用 ActionScript3 編寫程式碼就有了比較完善的型別檢測與型別提示。
曾經的 Actionscript3.0 輝煌的時代,那時動畫有Flash, 應用有 Flex, 跨平臺桌面應用有 Adobe air ,後面還支援移動端,這些用的都是 Actionscript3.0 指令碼
而 Actionscript3.0 在我熟練掌握後退出了歷史舞臺。。。多棒的指令碼語言啊,我又白學了。原因大致是 Adobe 的不上進和其它大公司的聯合圍剿
所以當我第一次接觸到 Typescript 的時候內心非常牴觸。
這幾年 Javascript 跟其它語言相比可能還差一大截,但已和當年刀耕火種不同,前端工具與框架層出不窮,快速更新迭代 web 應用越來越複雜,前端工具越來越成熟,Typescript 的應用
也就水到渠成了。當在團隊中使用 Typescript 雖然多寫了點兒型別程式碼,但是好處太多了,可以說是用了就回不去了
我們這樣的小角色怎能與時代洪流相抵呢,隨波逐流吧,學吧學到廢為止
如果你學過 Actionscript3 那麼對 Typescript 中普通的,類、介面、繼承、變數型別等概念與語法就會非常熟悉
唯一沒有且用的比較廣泛的概念當屬 Typescirpt 中的 "泛型" , 泛型的理解與運用自我感覺是比較難的,但又不能不面對,只能多看多學了
我所學到與理解的也是看的其它人分享的資料,拾人牙慧
最討厭別人寫的文章、書,上來就是一堆概念和名詞解釋。把你繞的雲裡霧裡
我希望的是從實際運用出發,從問題開始找解決方案。也就是學了幹啥用,得學以致用才能更好的理解
以下假設你已經對 Typescript 已經有了一定的基礎瞭解
如果你從未學過 Typescript 那麼請退出先去學基礎!
一、回撥函式的型別提示
註冊自定義事件,傳入的回撥函式,如果事件型別(事件名)對應的回撥函式內回撥引數不一樣
那麼回撥函式的型別註釋我們無能為力,只能用 any ,如下 addEvent 函式,用於註冊事件
eventType 定義為 string 型別
listener 這個是函式 Function, 但由於事件型別有多種,對應的回撥函式也有好多種
這就尬住了,暫時只能用 (...args: any[]) => any 來作為 listener 的型別
但這樣還是沒有辦法明確 listener 裡邊有多少個具體的引數以及型別
// 自定義註冊事件函式的型別註釋
const addEvent = (eventType: string, listener:(...args: any[]) => any) => {
console.log(eventType, listener);
}
addEvent('eventTypeName1', () => {
})
如果是這樣,那麼 呼叫 addEvent 時回撥函式是沒有任何有用的提示的
尬住了是不是
eventType 不同,對應的 listener 也不同
這時就應該想是不是能用泛型來解決,泛型就是在傳入的時候才確寫具體的型別約束
- 先建一個用於對映的型別物件 MyEventMap, key 是 eventType 型別, value 是對應的 listener 型別
- 新增泛型 T
- 用 extends keyof MyEventMap 約束 T 在 MyEventMap 的 key 範圍內,而key 範圍又是透過 keyof 來提取的
- listener 的型別透過 MyEventMap[T] 來獲取
type MyEventMap = {
'eventTypeName1': (a: string) => number
'eventTypeName2': (test: boolean) => string[]
}
const addEvent = <T extends keyof MyEventMap>(eventType: T, listener: MyEventMap[T]) => {
console.log(eventType, listener);
}
addEvent('eventTypeName1', (a) => {
return 1
})
這樣就有提示了,看效果
兩個關鍵點
- extends 來約束 eventType
- MyEventMap[T] 來獲取具體的 listener 型別
二、代理 DOM 事件的型別註釋
比如你自己在寫 Js 框架,其中需求是要實現 addEventListener 的代理函式,如何給這個代理函式寫ts註釋呢?
on('click', ()=> {})
這樣的方法,且能提示 Typescript 預設提供的型別,並約束 eventName 在dom事件
const on = (eventName: string, listener: (...args: any[]) => any) => {
console.log(eventName, listener);
}
這樣寫也透過了檢測...那肯定不行,因為需求是約束為 dom 事件,但現在約束了eventName為 string
on('click', () => {
})
又尬住了,我們得在 ts 提供的 lib.dom.d.ts 檔案內找答案
原始碼中找到 interface HTMLElement
的介面定義
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
顯然 HTMLElementEventMap 就是我們要找的透過 eventName 對映 具體回撥的 Map
那就和上面自定義註冊函式一樣處理就可以了即
const on = <T extends keyof HTMLElementEventMap>(eventName: T, listener: (this: HTMLElement, ev: HTMLElementEventMap[T]) => any, options?: boolean | AddEventListenerOptions) => {
console.log(eventName, listener, options);
}
這樣就都有正常的提示了
on('mousedown', (e) => {
console.log(e)
})
on('paste', (e) => {
console.log(e)
})
三、fetch 的 ts 註釋
很多情況下,我們會給 fetch 請求回來的函式用 data as User[]
來主動告訴編譯器返回資料的型別, 雖然能用,但不優雅
我們會順著思路,我們試試給 fetch 請求函式作 ts 註釋
一樣先建一個 ResponseMap ,key 是 三、fetch 請求地址 value 是 fetch 返回的資料型別
type ResponseMap = {
'hello/world': number
'test/getlist': string[]
}
const get = async <T extends keyof ResponseMap>(url: T):Promise<ResponseMap[T]> => {
const response = await fetch(url);
return response.json();
}
測試一下
get('hello/world')
get('test/getlist')
試了一下挺完美,但是,但是,肯定沒這麼簡單,請求地址很多情況下是帶有引數的
get('test/getlist?a=1&b=2')
發現提示錯誤,通不過校驗了
果然型別很麻煩。。。
!!!需要改進一下泛型匹配
const get = async <T extends keyof ResponseMap>(url: T | `${T}?${string}`):Promise<ResponseMap[T]> => {
const response = await fetch(url);
return response.json();
}
get('test/getlist?a=1&b=2')
這下可以透過校驗了,提示也正常工作
關鍵在於
url: T | ${T}?${string}
這一句的改動, 透過字串模板提取出 T 來
最後,人家的建議是泛型也需要更友好的命名,T、K、R、等等都太不友好了,可以更具名化如下, 把範圍名字變的更具體
const addEvent = <EventType extends keyof MyEventMap>(eventType: EventType, listener: MyEventMap[EventType]) => {
console.log(eventType, listener);
}
const get = async <FetchUrl extends keyof ResponseMap>(url: FetchUrl | `${FetchUrl}?${string}`):Promise<ResponseMap[FetchUrl]> => {
const response = await fetch(url);
return response.json();
}
說明:以上知識是看到國外某個講 typescript 的影片中學到的,沒找到原影片內容。當然很多英文內容也沒有翻譯,我只是把理解的知識轉化一下,所以才叫拾人牙慧麼...
cnblogs.com/willian/