成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

用 TypeScript 實(shí)現(xiàn)類型安全的 EventEmitter,這下不用怕寫錯(cuò)事件名了

開(kāi)發(fā) 前端
配合越來(lái)越流行的 TypeScript,我們可以通過(guò)安裝 @types/node,我們能夠進(jìn)一步獲得類型能力,減少低級(jí)錯(cuò)誤的出現(xiàn)。但 EventEmitter 的類型實(shí)現(xiàn)并不出色,稱不上是類型安全。

大家好,我是前端西瓜哥。最近個(gè)人項(xiàng)目用 EventEmitter 模塊越來(lái)越多了,因?yàn)轭愋筒粔虬踩瑢懫饋?lái)要很小心。所以打算改良一下,實(shí)現(xiàn) TypeScript 類型安全的 EventEmitter,解決事件名和函數(shù)類型不能做檢驗(yàn)的問(wèn)題。

Nodejs 的 EventEmitter 是一個(gè)發(fā)布訂閱模塊。

利用該類,我們可以實(shí)現(xiàn)事件的監(jiān)聽(tīng),被監(jiān)聽(tīng)對(duì)象會(huì)在合適的時(shí)機(jī)觸發(fā)事件,調(diào)用監(jiān)聽(tīng)對(duì)象提供的方法,是模塊間解耦的常用實(shí)現(xiàn)。

配合越來(lái)越流行的 TypeScript,我們可以通過(guò)安裝 @types/node,我們能夠進(jìn)一步獲得類型能力,減少低級(jí)錯(cuò)誤的出現(xiàn)。但 EventEmitter 的類型實(shí)現(xiàn)并不出色,稱不上是類型安全。

通常來(lái)說(shuō),不同事件對(duì)應(yīng)的響應(yīng)函數(shù)類型是不同的,但 @types/node 的 EventEmiiter 類型沒(méi)有提供高級(jí)類型,而是給一個(gè)異常寬松的類型。

class EventEmitter {
constructor(options?: EventEmitterOptions);
// 類型過(guò)于寬泛
on(eventName: string | symbol, listener: (...args: any[]) => void): this;
emit(eventName: string | symbol, ...args: any[]): boolean;
}

可以看到,on 方法傳入的事件名類型是 ??string | symbol???,listener 則是隨意任何類型的一個(gè)函數(shù)即可。emit 傳入的參數(shù)也是 ??any[]??。

因?yàn)檫^(guò)于寬松的類型,如果事件名拼錯(cuò)了,TypeScript 并不會(huì)報(bào)錯(cuò),當(dāng)一個(gè) eventEmitter 的事件類型變得非常多,我們就和裸寫 JavaScript 沒(méi)什么區(qū)別了。

自己動(dòng)手,豐衣足食,我們不妨 自己實(shí)現(xiàn)一個(gè)類型安全的 EventEmitter。

EventEmitter 實(shí)現(xiàn)

因?yàn)槲移鋵?shí)是在前端用的 EventEmitter,所以寫了一個(gè)  EventEmitter 簡(jiǎn)易 JavaScript 實(shí)現(xiàn)。

class EventEmitter {
eventMap = {};

// 添加對(duì)應(yīng)事件的監(jiān)聽(tīng)函數(shù)
on(eventName, listener) {
if (!this.eventMap[eventName]) {
this.eventMap[eventName] = [];
}
this.eventMap[eventName].push(listener);
return this;
}

// 觸發(fā)事件
emit(eventName, ...args) {
const listeners = this.eventMap[eventName];
if (!listeners || listeners.length === 0) return false;
listeners.forEach((listener) => {
listener(...args);
});
return true;
}

// 取消對(duì)應(yīng)事件的監(jiān)聽(tīng)
off(eventName, listener) {
const listeners = this.eventMap[eventName];
if (listeners && listeners.length > 0) {
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
}
return this;
}
}

如果你是 nodejs,繼承 EventEmitter 然后改它的類型或許是更好的做法,或者可以 “基于組合而不是繼承” 的方式實(shí)現(xiàn)一個(gè)。

類型安全的 EventEmitter

接著是將上面的代碼改為 TypeScript。

我們希望的效果是:

const ee = new EventEmitter<{
update(newVal: string, prevVal: string): void;
destroy(): void;
}>();
const handler = (newVal: string, prevVal: string) => {
console.log(newVal, prevVal)
}
ee.on("update", handler);
ee.emit('update', '前端西瓜哥上班前的精神狀態(tài)', '前端西瓜哥上班后的精神狀態(tài)')
ee.off("update", handler);

// 以下報(bào)錯(cuò)
// 'number' is not assignable to parameter of type 'string'
ee.emit('update', 1, 2)
// (val: number) => void' is not assignable to parameter of type '() => void
ee.on('destroy', (val: number) => {})

EventEmitter 支持接受一個(gè)對(duì)象結(jié)構(gòu)的 interface 作為類型參數(shù),指定不同的 key 對(duì)應(yīng)的函數(shù)類型。

然后我們?cè)僬{(diào)用 on、emit、off 時(shí),如果事件名、函數(shù)參數(shù)不匹配,編譯就不能通過(guò)。

代碼實(shí)現(xiàn):

class EventEmitter<T extends Record<string | symbol, any>> {
private eventMap: Record<keyof T, Array<(...args: any[]) => void>> =
{} as any;

// 添加對(duì)應(yīng)事件的監(jiān)聽(tīng)函數(shù)
on<K extends keyof T>(eventName: K, listener: T[K]) {
if (!this.eventMap[eventName]) {
this.eventMap[eventName] = [];
}
this.eventMap[eventName].push(listener);
return this;
}

// 觸發(fā)事件
emit<K extends keyof T>(eventName: K, ...args: Parameters<T[K]>) {
const listeners = this.eventMap[eventName];
if (!listeners || listeners.length === 0) return false;
listeners.forEach((listener) => {
listener(...args);
});
return true;
}

// 取消對(duì)應(yīng)事件的監(jiān)聽(tīng)
off<K extends keyof T>(eventName: K, listener: T[K]) {
const listeners = this.eventMap[eventName];
if (listeners && listeners.length > 0) {
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
}
return this;
}
}

讀者朋友可自行拷貝上面兩段代碼到 TypeScript Playground 測(cè)試一下。

簡(jiǎn)單講解一下。

首先是開(kāi)頭的類型參數(shù)。

class EventEmitter<T extends Record<string | symbol, any>> {
//
}

這里的 extends 作用是限定類型范圍,防止提供一個(gè)不符合規(guī)則的類型參數(shù)。

Record 是 TypeScript 自帶的高級(jí)類型,根據(jù)傳入的 key 和 value 創(chuàng)建一個(gè)對(duì)象結(jié)構(gòu)(后面說(shuō)到的 T 就是它)。

Record<string | symbol, any>
// 等價(jià)于
{
[key: string | symbol]: any
}

value 本來(lái)的類型應(yīng)該是 (...args: any[]) => void,好限制為函數(shù)。但在不是非字面量類型直傳的情況下無(wú)法通過(guò)類型檢測(cè),只好改成 any 了。(坑爹的 Index signature for type 'string' is missing 報(bào)錯(cuò))。

然后是 eventMap,它的實(shí)際內(nèi)容是這樣的:

eventMap = {
event1: [ handler1, handler2 ],
event2: [ handler3, handler4 ]
}

所以 key 需要為傳入對(duì)象類型參數(shù)的 key。

函數(shù)則不用指定特定類型,因?yàn)樗撬接械模瑹o(wú)法被類外部訪問(wèn),沒(méi)有做過(guò)多的類型推斷,就寬松一些,設(shè)置為任何函數(shù)類型。

private eventMap: Record<keyof T, Array<(...args: any[]) => void>> =
{} as any;

這里我用了對(duì)象字面量,讀者朋友也可以考慮用 Map 數(shù)據(jù)結(jié)構(gòu)。

然后是 on 方法,首先 eventName 必須為 T 的 key 的其中之一,因?yàn)橐茢?K 這么個(gè)內(nèi)部類型變量,所以我們要在 on 后面加上 <K extends keyof T>,listener 就是對(duì)應(yīng)的 T[K]。

on<K extends keyof T>(eventName: K, listener: T[K]): this

off 方法同理,不展開(kāi)講。

然后是 emit,第一個(gè) eventName 用 keyof T 沒(méi)問(wèn)題,后面需要取出 handler 的參數(shù),作為剩余參數(shù)。

emit<K extends keyof T>(eventName: K, ...args: Parameters<T[K]>): boolean

這里用了 TS 自帶的 Parameters 高級(jí)類型,作用是取出函數(shù)的參數(shù)返回一個(gè)數(shù)組類型。

臨時(shí)擴(kuò)展自定義事件

如果要給一個(gè)已經(jīng)固定了類型的實(shí)例,臨時(shí)加一個(gè)事件,可以用 & 交叉類型擴(kuò)展一下。

interface Events {
update(newVal: string, prevVal: string): void;
destroy(): void;
}
const ee = new EventEmitter<Events>();

// 用 & 擴(kuò)展
const ee2 = ee as EventEmitter<
Events & {
customA(a: boolean): void;
}
>;
// 不報(bào)錯(cuò)
ee2.emit('customA', true)

// 或者
(ee as EventEmitter<
Events & {
customA(a: boolean): void;
}
>).emit('customA', true)

結(jié)尾

一番改造,我們充分利用 TypeScript 的強(qiáng)大類型體操能力,構(gòu)建了一個(gè)類型安全的 EventEmitter。寫錯(cuò)事件名,函數(shù)類型沒(méi)對(duì)上什么的,根本不在怕的。

這次的類型體操還算是比較簡(jiǎn)單的。如果再?gòu)?fù)雜一點(diǎn),可讀性就很差了。

TypeScript 的類型編程的語(yǔ)法真的很不美觀,可讀性差。如果你不是庫(kù)作者,個(gè)人不建議過(guò)度使用類型體操,它像正則一樣,很強(qiáng)大,但也很復(fù)雜。

責(zé)任編輯:姜華 來(lái)源: 前端西瓜哥
相關(guān)推薦

2022-09-14 15:24:57

typescript快排

2022-04-12 08:09:22

Nodejs前端面試題

2024-07-04 07:44:19

2022-04-11 08:42:09

TypeScript子類型定義

2021-06-09 07:55:19

NodeEventEmitte驅(qū)動(dòng)

2010-09-13 13:56:03

2010-08-23 09:20:57

2010-06-07 17:45:06

MySQL數(shù)據(jù)庫(kù)密碼

2022-05-04 09:02:41

TypeScript類型工具

2021-01-06 10:26:17

電腦WindowsWindows 10

2021-06-09 07:55:19

Typescript類型檢查

2010-08-17 14:47:52

DB2備份歷史文件損壞

2021-05-16 18:02:52

系統(tǒng)編程JavaScript

2022-06-08 10:01:06

Go語(yǔ)法PHP

2012-03-13 15:49:43

win7

2021-12-22 10:29:23

Prometheus elasticsear運(yùn)維

2020-03-13 13:30:18

5G通信智能家居

2021-01-23 23:16:07

運(yùn)營(yíng)商5G數(shù)據(jù)

2022-02-25 09:06:02

TypeScripnever工具

2021-07-27 06:06:34

TypeScript語(yǔ)言運(yùn)算符
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 久久精品视频播放 | 涩涩鲁亚洲精品一区二区 | 久久精品日产第一区二区三区 | 日韩欧美亚洲一区 | 日韩av成人在线 | 中国91av | 精品久久一区 | 成人免费观看男女羞羞视频 | 国产精品久久久久久久久久 | 91精品国产综合久久精品图片 | 91视频88av| 成人av在线网站 | 黄色香蕉视频在线观看 | 日韩小视频在线 | 免费一级黄色电影 | jav成人av免费播放 | 在线观看a视频 | 成人综合在线视频 | 中文字幕在线视频免费观看 | 国产日韩一区二区 | 成人免费视频7777777 | 久久精品免费看 | 成人性视频免费网站 | 日韩网站在线 | 久草精品视频 | 日韩国产高清在线观看 | 97超级碰碰 | 国产精品久久久久久久久久久新郎 | 免费一级网站 | 国产高清精品在线 | 精品久久久久久久 | 欧美激情精品久久久久久 | 99re6在线视频精品免费 | 午夜视频在线免费观看 | 日本午夜一区 | 久久精品福利 | 国产男女视频 | 男女污污动态图 | 国产在线视频一区二区董小宛性色 | 人人色视频 | 蜜月aⅴ国产精品 |