From 2a565e078090f7ac77821edd78bf18723a7aa7b0 Mon Sep 17 00:00:00 2001 From: Curtis Dulmage Date: Sat, 11 Feb 2023 17:09:28 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9A=20[Docs]=20Improve=20some=20exampl?= =?UTF-8?q?es=20(#25)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/stale-rules-dance.md | 5 + docs/examples.md | 192 ++++++++++++++++++++------------ 2 files changed, 125 insertions(+), 72 deletions(-) create mode 100644 .changeset/stale-rules-dance.md diff --git a/.changeset/stale-rules-dance.md b/.changeset/stale-rules-dance.md new file mode 100644 index 0000000..644c9a9 --- /dev/null +++ b/.changeset/stale-rules-dance.md @@ -0,0 +1,5 @@ +--- +'emitten': patch +--- + +Revise examples doc. diff --git a/docs/examples.md b/docs/examples.md index 7e76e4c..d657773 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -1,18 +1,18 @@ # Examples -Below are some common use-cases for `Emitten` and `EmittenProtected`. +Below are some common use-cases for `Emitten`, `EmittenCommon`, and `EmittenProtected`. ## Basic usage -The easiest way to use `Emitten` is to simply instantiate it and begin wiring up your logic. +The easiest way to use `Emitten` is to simply instantiate it with your custom “Event Map”. -For this use case, you most likely want to use `Emitten` instead of `EmittenProtected`. If you instantiate using `EmittenProtected`, you will not be able to call any `protected` members. +For this use case, you will most likely want to use the `Emitten` variant _(instead of `EmittenCommon` or `EmittenProtected`)_. For example, if you instantiate using `EmittenProtected`, you will not be able to call any `protected` members. -```ts -// Start by defining your “event map”. -// This is a `Record` type comprised of -// `eventName -> listener function`. +### Start by defining your “event map” + +This is a `Record` type comprised of `eventName -> listener function`. +```ts // It is recommended to use the `type` keyword instead of `interface`! // There is an important distinction... using `type`, you will: // 1. Not need to `extend` from `EmittenMap`. @@ -28,28 +28,42 @@ type EventMap = { // 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; +interface AltMap extends EmittenMap { + 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` -// as the generic to `Emitten`. +### Instantiate + +Instantiate a new instance, passing the `EventMap` as the generic to `Emitten`. + +```ts const myEvents = new Emitten(); +``` -// Define your callback functions. +### Define your callback functions -// If needed, you can grab the `listener` argument -// types by using the TypeScript `Parameters` -// utility and manually selecting the value -// by `key + index`. Example: -type ChangeValue = Parameters[0]; +You can always pass annonymous functions to `Emitten` methods. If you prefer named functions, then its best to leverage the `EventMap` you created to make the correct type associations. -function handleChange(value: ChangeValue) { +```ts +// By selecting 'change' from the `EventMap`, +// you can connect this function to it’s correct type. +const handleChange: EventMap['change'] = (value) => { console.log('change', value); +}; + +// Alternatively, you can grab the `listener` +// argument types by using the TypeScript +// `Parameters` utility and manually selecting +// the value by `key + index`: +type ChangeValue = Parameters[0]; + +function handleAltChange(value: ChangeValue) { + console.log('alt change', value); } function handleCount(value = 0) { @@ -59,8 +73,13 @@ function handleCount(value = 0) { function handleCollect(values: boolean[]) { console.log('collect', values); } +``` + +### Subscribe -// Subscribe to the events you are interested in. +Start listening to the events you are interested in. + +```ts myEvents.on('change', handleChange); myEvents.on('count', handleCount); @@ -89,9 +108,13 @@ const disposeRest = myEvents.on('rest', (required, ...optional) => { myEvents.once('nothing', () => { console.log('Nothing!'); }); +``` + +### Emit events -// We can now start emitting events! +Now that we have registered some subscriptions, we can start emitting! +```ts myEvents.emit('change', 'hello'); myEvents.emit('count'); myEvents.emit('count', 1); @@ -102,7 +125,28 @@ myEvents.emit('nothing'); // Since the `handleCollect` function was registered with `.once()`, // this 2nd call to `.emit('collect')` will not be received by anything. myEvents.emit('collect', [true, false, true]); +``` +### Unsubscribe + +When we are finished with our subscriptions, we can remove them. + +```ts +// Can manually remove an individual listener by reference. +myEvents.off('change', handleChange); + +// Or manually call a returned `dispose` function. +disposeRest(); + +// Or you can completely empty out all events + listeners. +myEvents.empty(); +``` + +### Type safety + +Because we passed our custom `EventMap` to the `Emitten` generic, we can guarantee type-safety from all of our methods. + +```ts // Attempting to `emit` an `eventName` that does // not exist in the `EventMap`, or passing a `value` // that is not compatible with the defined event’s @@ -115,34 +159,28 @@ myEvents.emit('change', '1st string', '2nd string'); myEvents.emit('count', true); myEvents.emit('rest'); myEvents.emit('nothing', 'something'); - -// Can manually remove an individual listener by reference. -myEvents.off('change', handleChange); - -// Or manually call a returned `dispose` function. -disposeRest(); - -// Or you can completely empty out all events + listeners. -myEvents.empty(); ``` ## Extending the class 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`. +### Class definition + +Just like before: define your `EventMap`, then pass it as a generic to the variant of `Emitten` you wish to extend. + ```ts type ExtendedEventMap = { custom: (value: string) => void; other: (value: number) => void; }; +// This example extends `EmittenProtected`, which protects all members. +// If required, you can selectively expose any `protected` members. +// If you only want `off/on/once` and `activeEvents`, you can +// extend from `EmittenCommon`. Otherwise, if you want ALL +// members to be `public`, you can extend from `Emitten`. class ExtendedEmitten extends EmittenProtected { - // This example extends `EmittenProtected`, which protects all members. - // If required, you can selectively expose any `protected` members. - // If you only want `off/on/once` and `activeEvents`, you can - // extend from `EmittenCommon`. Otherwise, if you want ALL - // members to be `public`, you can extend from `Emitten`. - public off( eventName: K, listener: ExtendedEventMap[K], @@ -169,7 +207,13 @@ class ExtendedEmitten extends EmittenProtected { return this.activeEvents; } } +``` + +### Usage +Now you can instantiate your new class and call it’s methods. + +```ts const extended = new ExtendedEmitten(); extended.on('custom', (value) => { @@ -185,10 +229,14 @@ extended.emit('custom', 'hello'); extended.empty(); ``` -## Another example +## Private events We can of course create classes that do not extend `Emitten`, and instead create a `private` instance of `Emitten` to perform event actions on. +### Another class definition + +Just like before, we will define our `EventMap` and `class`. + ```ts type AnotherMap = { change: (value: string) => void; @@ -201,43 +249,11 @@ class AnotherExample { #id = 'DefaultId'; #counter = 0; #names: string[] = []; - // Would not be able to call any methods // if we had used `EmittenProtected`. #events = new Emitten(); - #handleChange: AnotherMap['change']; - #handleCount: AnotherMap['count']; - #handleNames: AnotherMap['names']; - #handleDiverse: AnotherMap['diverse']; - constructor() { - this.#handleChange = (value) => { - this.#id = value.length > 0 ? value : this.#id; - console.log('#handleChange', value); - }; - - this.#handleCount = (value) => { - if (this.#counter >= 4) { - this.#events.off('count', this.#handleCount); - } else { - this.#counter++; - } - - console.log('#handleCount', value); - }; - - this.#handleNames = (...values) => { - this.#names = [...this.#names, ...values]; - console.log('#handleNames', values); - }; - - this.#handleDiverse = (first, second, ...last) => { - console.log('#handleDiverse > first', first); - console.log('#handleDiverse > second', second); - console.log('#handleDiverse > last', last); - }; - this.#events.on('change', this.#handleChange); this.#events.on('count', this.#handleCount); this.#events.on('names', this.#handleNames); @@ -287,8 +303,40 @@ class AnotherExample { console.log('Removing all listeners from newThinger...'); this.#events.empty(); } + + #handleChange: AnotherMap['change'] = (value) => { + this.#id = value.length > 0 ? value : this.#id; + console.log('#handleChange', value); + }; + + #handleCount: AnotherMap['count'] = (value) => { + if (this.#counter >= 4) { + this.#events.off('count', this.#handleCount); + } else { + this.#counter++; + } + + console.log('#handleCount', value); + }; + + #handleNames: AnotherMap['names'] = (...values) => { + this.#names = [...this.#names, ...values]; + console.log('#handleNames', values); + }; + + #handleDiverse: AnotherMap['diverse'] = (first, second, ...last) => { + console.log('#handleDiverse > first', first); + console.log('#handleDiverse > second', second); + console.log('#handleDiverse > last', last); + }; } +``` + +### Example +Now we can try out all of our event methods. + +```ts const myExample = new AnotherExample(); const otherCollection = [ ['first'],