Typescript 回調函數、事件偵聽的類型定義與註釋--拾人牙慧

来源:https://www.cnblogs.com/willian/archive/2023/02/22/17145034.html
-Advertisement-
Play Games

實際項目中會運到的 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 也不同
這時就應該想是不是能用泛型來解決,泛型就是在傳入的時候才確寫具體的類型約束

  1. 先建一個用於映射的類型對象 MyEventMap, key 是 eventType 類型, value 是對應的 listener 類型
  2. 添加泛型 T
  3. 用 extends keyof MyEventMap 約束 T 在 MyEventMap 的 key 範圍內,而key 範圍又是通過 keyof 來提取的
  4. 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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • SQL的分類 DDL:數據定義語言 CREATE\ALTER\RENAME(重命名)\DROP\TRUNCATE(清空表) DML:數據操作語言 INSERT\DELETE\UPDATE\SELECT(增刪改查) DCL:數據控制語言 COMMIT(提交)\ROLLBACK(回滾)\SAVEPOIN ...
  • 如今,各行各業都已經意識到了數據的價值,開始沉澱數據資產,挖掘數據價值,但是數據本身其實是很難直觀地看到其價值的。數據就是存儲在電腦系統的“01”代碼,如果你不去用它,能有什麼價值? 正如美國哈佛大學教授格林先生所說:數據本身並不等於知識,更不是智慧,只有經過正確分析之後,數據才能凸顯它的意義。 ...
  • 小編提醒:拿MariaDB的so去MySQL里install,這種方式很容易導致 audit plugin工作異常,不推薦這麼做。強烈建議使用GreatSQL,自帶 audit plugin。 前言 資料庫審計功能主要將用戶對資料庫的各類操作行為記錄審計日誌,以便日後進行跟蹤、查詢、分析,以實現對用 ...
  • Android 啟動優化(一) - 有向無環圖 Android 啟動優化(二) - 拓撲排序的原理以及解題思路 Android 啟動優化(三) - AnchorTask 使用說明 Android 啟動優化(四)- 手把手教你實現 AnchorTask Android 啟動優化(五)- AnchorT ...
  • 配置請求地址:config->index.js 一個項目里通常有一個config->index.js,該文件包含了當前項目的請求地址,以及項目的版本信息。 // 請求地址 const API_URL_DEV = 'http://xxx.xxx.xxx.net:81/xxx' // 測試介面 cons ...
  • 首先我們知道JavaScript引擎包括一個調用棧和堆,調用棧是代碼實際執行的地方,使用執行上下文(執行環境)來完成;堆是非結構化的記憶體池,存儲了應用程式所需要的所有對象。 執行上下文是什麼? 執行上下文包括全局執行上下文和執行上下文。 全局執行上下文:代碼編譯完成後進入調用棧執行首先創建全局執行上 ...
  • Promise基本使用 Promise是非同步編程的一種解決方案,用於一個非同步操作的最終完成(或失敗)及其結果值的表示,比傳統的回調函數方案更加合理。 var promise = new Promise((resolve, reject) => {/* executor函數 */ // ... som ...
  • 需求 提供一張2d編輯完成的戶型圖片,圖片中有多個房間、尺寸比例不固定、還有2d戶型圖數據(牆體頂點坐標、漫游點坐標等),需要做到將2d編輯時提供的漫游點坐標映射到圖片上,以做到用戶點擊圖片某個位置時跳轉至最近的漫游點VR視圖中。 解決思路 首先提供的2d坐標數據中的中心點(0,0)並不是戶型的正中 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...