From ad5762063ae6ae225a510eac5bfe7bf98122b2d3 Mon Sep 17 00:00:00 2001 From: Curtis Dulmage Date: Sun, 5 Feb 2023 15:59:40 -0500 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20[Emitten]=20Change=20`libr?= =?UTF-8?q?ary`=20from=20an=20`object`=20to=20a=20`Map`=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/orange-bottles-know.md | 5 +++ .eslintrc | 4 +- README.md | 7 ++-- docs/examples.md | 36 ++++++++--------- src/EmittenProtected.ts | 67 +++++++++++++++---------------- src/index.ts | 1 - src/tests/Emitten.test.ts | 8 ++-- src/types.ts | 4 +- tsconfig.json | 1 - 9 files changed, 62 insertions(+), 71 deletions(-) create mode 100644 .changeset/orange-bottles-know.md diff --git a/.changeset/orange-bottles-know.md b/.changeset/orange-bottles-know.md new file mode 100644 index 0000000..ee61d8e --- /dev/null +++ b/.changeset/orange-bottles-know.md @@ -0,0 +1,5 @@ +--- +'emitten': patch +--- + +Internally, switch the library values from an object to a Map. diff --git a/.eslintrc b/.eslintrc index 7883d7e..32326cb 100644 --- a/.eslintrc +++ b/.eslintrc @@ -12,8 +12,6 @@ }, "rules": { "prettier/prettier": ["error"], - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/method-signature-style": "off", - "@typescript-eslint/no-dynamic-delete": "off" + "@typescript-eslint/explicit-function-return-type": "off" } } diff --git a/README.md b/README.md index 38575fd..f06e2d2 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,9 @@ import {Emitten} from 'emitten'; // Both the `eventName` and the `args` from the `listener` are // captured by TypeScript to assert type-safety! type EventMap = { - change(value: string): void; - count(value?: number): void; - collect(...values: boolean[]): void; - // Method signature style could also look like: + change: (value: string) => void; + count: (value?: number) => void; + collect: (...values: boolean[]) => void; other: (required: string, ...optional: string[]) => void; }; diff --git a/docs/examples.md b/docs/examples.md index 614f176..7e76e4c 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -17,25 +17,23 @@ For this use case, you most likely want to use `Emitten` instead of `EmittenProt // There is an important distinction... using `type`, you will: // 1. Not need to `extend` from `EmittenMap`. // 2. Automatically receive type-safety for `event` names. - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions type EventMap = { - change(value: string): void; - count(value?: number): void; - collect(values: boolean[]): void; - rest(required: string, ...optional: string[]): void; - nothing(): void; + change: (value: string) => void; + count: (value?: number) => void; + collect: (values: boolean[]) => void; + rest: (required: string, ...optional: string[]) => void; + nothing: () => void; }; // If you do prefer using `interface`, just know that: // 1. You MUST `extend` from `EmittenMap`. // 2. You will NOT receive type-safety for `event` names. export interface AltMap extends EmittenMap { - change(value: string): void; - count(value?: number): void; - collect(values: boolean[]): void; - rest(required: string, ...optional: string[]): void; - nothing(): void; + change: (value: string) => void; + count: (value?: number) => void; + collect: (values: boolean[]) => void; + rest: (required: string, ...optional: string[]) => void; + nothing: () => void; } // Instantiate a new instance, passing the `EventMap` @@ -133,10 +131,9 @@ myEvents.empty(); Since “derived classes” have access to the `protected` members of their “base class”, you can utilize `EmittenProtected` to both utilize `protected` members while also keeping them `protected` when instantiating your new `class`. ```ts -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions type ExtendedEventMap = { - custom(value: string): void; - other(value: number): void; + custom: (value: string) => void; + other: (value: number) => void; }; class ExtendedEmitten extends EmittenProtected { @@ -193,12 +190,11 @@ extended.empty(); We can of course create classes that do not extend `Emitten`, and instead create a `private` instance of `Emitten` to perform event actions on. ```ts -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions type AnotherMap = { - change(value: string): void; - count(value?: number): void; - names(...values: string[]): void; - diverse(first: string, second: number, ...last: boolean[]): void; + change: (value: string) => void; + count: (value?: number) => void; + names: (...values: string[]) => void; + diverse: (first: string, second: number, ...last: boolean[]) => void; }; class AnotherExample { diff --git a/src/EmittenProtected.ts b/src/EmittenProtected.ts index 77588c3..e754275 100644 --- a/src/EmittenProtected.ts +++ b/src/EmittenProtected.ts @@ -1,8 +1,8 @@ -import type {EmittenMap, EmittenLibraryPartial} from './types'; +import type {EmittenMap, EmittenLibrary} from './types'; export class EmittenProtected { - #multiLibrary: EmittenLibraryPartial = {}; - #singleLibrary: EmittenLibraryPartial = {}; + #multiLibrary: EmittenLibrary = new Map(); + #singleLibrary: EmittenLibrary = new Map(); protected get activeEvents() { // This redundant getter + method are required @@ -11,36 +11,36 @@ export class EmittenProtected { } protected getActiveEvents() { - const multiKeys = Object.keys(this.#multiLibrary); - const singleKeys = Object.keys(this.#singleLibrary); + const multiKeys = this.#multiLibrary.keys(); + const singleKeys = this.#singleLibrary.keys(); const dedupedKeys = new Set([...multiKeys, ...singleKeys]); - const result: Array = [...dedupedKeys]; - return result; + return [...dedupedKeys]; } protected off(eventName: K, listener: T[K]) { - const multiSet = this.#multiLibrary[eventName]; - const singleSet = this.#singleLibrary[eventName]; + this.#multiLibrary.get(eventName)?.delete(listener); - if (multiSet != null) { - multiSet.delete(listener); - if (multiSet.size === 0) delete this.#multiLibrary[eventName]; + if (this.#multiLibrary.get(eventName)?.size === 0) { + this.#multiLibrary.delete(eventName); } - if (singleSet != null) { - singleSet.delete(listener); - if (singleSet.size === 0) delete this.#singleLibrary[eventName]; + this.#singleLibrary.get(eventName)?.delete(listener); + + if (this.#singleLibrary.get(eventName)?.size === 0) { + this.#singleLibrary.delete(eventName); } } protected on(eventName: K, listener: T[K]) { - if (this.#multiLibrary[eventName] == null) { - this.#multiLibrary[eventName] = new Set(); + if (!this.#multiLibrary.has(eventName)) { + this.#multiLibrary.set(eventName, new Set()); } - this.#multiLibrary[eventName]?.add(listener); + // TypeScript doesn't understand that the above + // condition results in this `.get()` being defined. + this.#multiLibrary.get(eventName)?.add(listener); return () => { this.off(eventName, listener); @@ -48,26 +48,25 @@ export class EmittenProtected { } protected once(eventName: K, listener: T[K]) { - if (this.#singleLibrary[eventName] == null) { - this.#singleLibrary[eventName] = new Set(); + if (!this.#singleLibrary.has(eventName)) { + this.#singleLibrary.set(eventName, new Set()); } - this.#singleLibrary[eventName]?.add(listener); + // TypeScript doesn't understand that the above + // condition results in this `.get()` being defined. + this.#singleLibrary.get(eventName)?.add(listener); } protected emit(eventName: K, ...values: Parameters) { - const multiSet = this.#multiLibrary[eventName]; - const singleSet = this.#singleLibrary[eventName]; - - multiSet?.forEach((listener) => { + this.#multiLibrary.get(eventName)?.forEach((listener) => { listener(...values); }); - singleSet?.forEach((listener) => { + this.#singleLibrary.get(eventName)?.forEach((listener) => { listener(...values); }); - delete this.#singleLibrary[eventName]; + this.#singleLibrary.delete(eventName); } protected empty() { @@ -75,13 +74,11 @@ export class EmittenProtected { this.#every(this.#singleLibrary); } - #every = (library: EmittenLibraryPartial) => { - for (const eventName in library) { - if (Object.hasOwn(library, eventName)) { - library[eventName]?.forEach((listener) => { - this.off(eventName, listener); - }); - } - } + #every = (library: EmittenLibrary) => { + library.forEach((collection, eventName) => { + collection.forEach((listener) => { + this.off(eventName, listener); + }); + }); }; } diff --git a/src/index.ts b/src/index.ts index 45ae2b9..1758a8f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,5 +7,4 @@ export type { EmittenListener, EmittenMap, EmittenLibrary, - EmittenLibraryPartial, } from './types'; diff --git a/src/tests/Emitten.test.ts b/src/tests/Emitten.test.ts index b581d0d..cb84823 100644 --- a/src/tests/Emitten.test.ts +++ b/src/tests/Emitten.test.ts @@ -3,10 +3,10 @@ import {Emitten} from '../Emitten'; // eslint-disable-next-line @typescript-eslint/consistent-type-definitions type MockEventMap = { - foo(value: string): void; - bar(value?: number): void; - baz(...values: boolean[]): void; - qux(required: string, ...optional: string[]): void; + foo: (value: string) => void; + bar: (value?: number) => void; + baz: (...values: boolean[]) => void; + qux: (required: string, ...optional: string[]) => void; }; describe('Emitten full public members', () => { diff --git a/src/types.ts b/src/types.ts index e71344e..628281e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,6 +4,4 @@ export type EmittenListener = ( ) => void; export type EmittenMap = Record; - -export type EmittenLibrary = Record>; -export type EmittenLibraryPartial = Partial>; +export type EmittenLibrary = Map>; diff --git a/tsconfig.json b/tsconfig.json index a50cc56..cdf824a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,6 @@ "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noImplicitAny": true, "noImplicitReturns": true, "skipLibCheck": true },