實際項目中會運到的 Typescript 回調函數、事件偵聽的類型定義,如果剛碰到會一臉蒙真的,我就是 這是第一次我自己對 Typescript 記錄學習,所以得先說一下我與 Typescript 的孽緣 記得最早是在2014年遇上 Typescript 當時是完全看不上這東西的,甚至帶著鄙視的心態 ...
實際項目中會運到的 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/
https://github.com/willian12345