Skip to content

Commit

Permalink
📚 [Docs] Improve some examples (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
beefchimi authored Feb 11, 2023
1 parent 6326e71 commit 2a565e0
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 72 deletions.
5 changes: 5 additions & 0 deletions .changeset/stale-rules-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'emitten': patch
---

Revise examples doc.
192 changes: 120 additions & 72 deletions docs/examples.md
Original file line number Diff line number Diff line change
@@ -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`.
Expand All @@ -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<EventMap>();
```

// 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<EventMap['change']>[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<EventMap['change']>[0];

function handleAltChange(value: ChangeValue) {
console.log('alt change', value);
}

function handleCount(value = 0) {
Expand All @@ -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);

Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -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<ExtendedEventMap> {
// 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<K extends keyof ExtendedEventMap>(
eventName: K,
listener: ExtendedEventMap[K],
Expand All @@ -169,7 +207,13 @@ class ExtendedEmitten extends EmittenProtected<ExtendedEventMap> {
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) => {
Expand All @@ -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;
Expand All @@ -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<AnotherMap>();

#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);
Expand Down Expand Up @@ -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'],
Expand Down

0 comments on commit 2a565e0

Please sign in to comment.