slice subscription
This commit is contained in:
@@ -9,11 +9,12 @@ This document is optimized for LLMs and AI coding assistants implementing or con
|
|||||||
`automat` is a lightweight (~1.1 kB minified, zero-dependency) observable state container designed specifically for React `PureComponent`.
|
`automat` is a lightweight (~1.1 kB minified, zero-dependency) observable state container designed specifically for React `PureComponent`.
|
||||||
|
|
||||||
### Key Invariants
|
### Key Invariants
|
||||||
1. **Instance Lifetime (Module Singleton or Dynamic Map Registry)**: An `Automat` instance lives outside the React render tree. While commonly instantiated as module-level singletons, instances can also be stored dynamically in a `Map` (e.g. `window.automatRegistry = new Map()` or an in-memory entity cache) keyed by ID or index. State persists in memory across component mounts, unmounts, and subscription transfers.
|
1. **Instance Lifetime (Module Singleton or Dynamic Map Registry)**: An `Automat` instance lives outside the React render tree. While commonly instantiated as module-level singletons, instances can also be stored dynamically in a `Map` (e.g. `window.automats = new Map()` or an in-memory entity cache) keyed by ID or index. State persists in memory across component mounts, unmounts, and subscription transfers.
|
||||||
2. **Direct Constructor Read**: Components read `automat.state` directly in their `constructor(props)`. State is never stale upon mounting.
|
2. **Direct Constructor Read**: Components read `automat.state` directly in their `constructor(props)`. State is never stale upon mounting.
|
||||||
3. **Lifecycle Subscription**: Components register with `automat.subscribe(this)` in `componentDidMount()` and call `this.unsubscribe()` in `componentWillUnmount()`.
|
3. **Lifecycle Subscription**: Components register with `automat.subscribe(this)` in `componentDidMount()` and call `this.unsubscribe()` in `componentWillUnmount()`.
|
||||||
4. **Hybrid State by Default**: When `automat.setState()` notifies a component, it calls `component.setState(partial)`. React's class component `setState` performs a shallow merge, preserving any component-local state fields.
|
4. **Hybrid State by Default**: When `automat.setState()` notifies a component, it calls `component.setState(partial)`. React's class component `setState` performs a shallow merge, preserving any component-local state fields.
|
||||||
5. **No Wrappers or Hooks**: No HOCs, no context providers, no hooks, no `connect()`.
|
5. **Fine-Grained Slice Subscriptions**: By default, `automat.subscribe(this)` notifies on any state change. When a subscriber only cares about a subset of state, passing a selector (`'key'`, `['keyA', 'keyB']`, or `(state) => ({ ... })`) enables internal shallow equality checking (`shallowEqual`). Updates to unrelated state fields will NOT trigger `setState` or re-renders.
|
||||||
|
6. **No Wrappers or Hooks**: No HOCs, no context providers, no hooks, no `connect()`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -41,7 +42,8 @@ new Automat<T extends object, A extends Record<string, Function>>(
|
|||||||
| `getState()` | `getState(): T` | Method returning current state snapshot. |
|
| `getState()` | `getState(): T` | Method returning current state snapshot. |
|
||||||
| `actions` | `get actions(): A` | Getter returning the actions object passed into the constructor. |
|
| `actions` | `get actions(): A` | Getter returning the actions object passed into the constructor. |
|
||||||
| `setState()` | `setState(partial: Partial<T>): T` | Shallow-merges `partial` into current state and synchronously notifies all subscribers. Returns new state. |
|
| `setState()` | `setState(partial: Partial<T>): T` | Shallow-merges `partial` into current state and synchronously notifies all subscribers. Returns new state. |
|
||||||
| `subscribe()` | `subscribe(target: PureComponent \| ((state: T) => void), selector?: (state: T) => object \| null): () => void` | Subscribes either a React component instance (`this`) or a callback function. Returns an `unsubscribe` function. |
|
| `subscribe()` | `subscribe(target: PureComponent \| ((state: T) => void), selector?: string \| string[] \| ((state: T) => object \| null)): () => void` | Subscribes either a React component instance (`this`) or a callback function. When `selector` is provided, performs shallow equality checking to ensure updates to unrelated state fields never trigger `setState` or re-renders. |
|
||||||
|
| `select()` | `select(selector: string \| string[] \| ((state: T) => any)): { readonly state: any, subscribe(target): () => void }` | Slices an automat to a specific subset of state for direct constructor reads and scoped subscriptions. |
|
||||||
| `unsubscribe()` | `unsubscribe(target: PureComponent \| Function): void` | Manually unregisters a subscriber. |
|
| `unsubscribe()` | `unsubscribe(target: PureComponent \| Function): void` | Manually unregisters a subscriber. |
|
||||||
| `subscribeTo()` | `subscribeTo<U>(upstream: Automat<U>, transform: (upstreamState: U, myState: T) => Partial<T> \| null): this` | Reactive pipeline: Derives state from an upstream automat. Returns `this` for chaining. |
|
| `subscribeTo()` | `subscribeTo<U>(upstream: Automat<U>, transform: (upstreamState: U, myState: T) => Partial<T> \| null): this` | Reactive pipeline: Derives state from an upstream automat. Returns `this` for chaining. |
|
||||||
| `dispose()` | `dispose(): void` | Unsubscribes all upstream listeners and clears all subscribers. |
|
| `dispose()` | `dispose(): void` | Unsubscribes all upstream listeners and clears all subscribers. |
|
||||||
@@ -125,26 +127,61 @@ export class CounterController extends PureComponent {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Pattern C: Passive Reader Component with State Selector
|
### Pattern C: Partial & Slice Subscriptions (Subscribing to Part of the State)
|
||||||
|
|
||||||
Use a selector function when a component only cares about a subset of the automat's state:
|
When an automat manages multiple state fields (e.g. `{ count, filter, theme, user }`), subscribing with `automat.subscribe(this)` causes any state change in the automat to notify the component. When a component only cares about a subset of the automat's state, subscribe using a **slice selector**.
|
||||||
|
|
||||||
|
`Automat` performs an internal shallow equality check (`shallowEqual(lastSlice, nextSlice)`). Updates to other, unrelated fields in the automat **will NOT trigger `setState` or re-renders** for that subscriber.
|
||||||
|
|
||||||
|
#### Supported Selector Forms:
|
||||||
|
|
||||||
|
1. **Single key string** (most concise):
|
||||||
|
```jsx
|
||||||
|
// Subscribes ONLY to 'count'. Injects { count } into component setState.
|
||||||
|
this.unsubscribe = myAutomat.subscribe(this, 'count');
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Array of keys**:
|
||||||
|
```jsx
|
||||||
|
// Subscribes ONLY to 'count' and 'step'. Injects { count, step } into component setState.
|
||||||
|
this.unsubscribe = myAutomat.subscribe(this, ['count', 'step']);
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Selector function**:
|
||||||
|
```jsx
|
||||||
|
// Computes a custom slice. Returning null or undefined skips updates.
|
||||||
|
this.unsubscribe = myAutomat.subscribe(this, (state) => ({
|
||||||
|
count: state.count,
|
||||||
|
isEven: state.count % 2 === 0,
|
||||||
|
}));
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Scoping via `.select()`**:
|
||||||
|
```jsx
|
||||||
|
// Slicing helper for both constructor reading and subscription:
|
||||||
|
const countSlice = myAutomat.select('count');
|
||||||
|
this.state = countSlice.state; // { count: 0 }
|
||||||
|
this.unsubscribe = countSlice.subscribe(this);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example: Passive Reader Subscribing Only to a Slice
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
// src/components/CountDisplay.jsx
|
// src/components/CountDisplay.jsx
|
||||||
import { PureComponent } from 'react';
|
import { PureComponent } from 'react';
|
||||||
import { counterAutomat } from '../automats/counterAutomat.js';
|
import { appAutomat } from '../automats/appAutomat.js';
|
||||||
|
|
||||||
export class CountDisplay extends PureComponent {
|
export class CountDisplay extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { count: counterAutomat.state.count };
|
// Initialize with only the slice needed:
|
||||||
|
this.state = { count: appAutomat.state.count };
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
// Selector maps state to target object. Returning null skips setState.
|
// 💡 Partial subscription: only re-renders when `count` changes.
|
||||||
this.unsubscribe = counterAutomat.subscribe(this, (state) => ({
|
// Mutations to appAutomat.theme, .user, etc. will NOT trigger setState!
|
||||||
count: state.count,
|
this.unsubscribe = appAutomat.subscribe(this, 'count');
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@@ -359,12 +396,14 @@ export class DynamicSlotObserver extends PureComponent {
|
|||||||
- **DO** create `Automat` instances in module scope or outside React components.
|
- **DO** create `Automat` instances in module scope or outside React components.
|
||||||
- **DO** initialize component state synchronously in `constructor(props)` using `this.state = myAutomat.state;` or `{ ...myAutomat.state, localField: 'val' }`.
|
- **DO** initialize component state synchronously in `constructor(props)` using `this.state = myAutomat.state;` or `{ ...myAutomat.state, localField: 'val' }`.
|
||||||
- **DO** register subscriptions in `componentDidMount()` via `this.unsubscribe = myAutomat.subscribe(this);`.
|
- **DO** register subscriptions in `componentDidMount()` via `this.unsubscribe = myAutomat.subscribe(this);`.
|
||||||
|
- **DO** use slice selectors (`myAutomat.subscribe(this, 'fieldName')` or `['fieldA', 'fieldB']`) when a component only needs part of the state, preventing unnecessary `setState` triggers and re-renders when unrelated fields change.
|
||||||
- **DO** clean up subscriptions in `componentWillUnmount()` via `this.unsubscribe();`.
|
- **DO** clean up subscriptions in `componentWillUnmount()` via `this.unsubscribe();`.
|
||||||
- **DO** invoke actions directly from event handlers (e.g. `onClick={() => myAutomat.actions.doSomething()}`).
|
- **DO** invoke actions directly from event handlers (e.g. `onClick={() => myAutomat.actions.doSomething()}`).
|
||||||
- **DO** return `null` in `subscribeTo` transforms when an update should be filtered out.
|
- **DO** return `null` in `subscribeTo` transforms when an update should be filtered out.
|
||||||
|
|
||||||
### ❌ DON'Ts
|
### ❌ DON'Ts
|
||||||
- **DON'T** instantiate `new Automat()` inside a React component's `render()`, `constructor()`, or lifecycle method.
|
- **DON'T** instantiate `new Automat()` inside a React component's `render()`, `constructor()`, or lifecycle method.
|
||||||
|
- **DON'T** subscribe with bare `myAutomat.subscribe(this)` if the component only depends on a specific subset of fields in a multi-field automat; use a slice selector instead.
|
||||||
- **DON'T** mutate state directly (e.g. `myAutomat.state.count = 5` is forbidden). Always call `myAutomat.setState({ count: 5 })` or an action.
|
- **DON'T** mutate state directly (e.g. `myAutomat.state.count = 5` is forbidden). Always call `myAutomat.setState({ count: 5 })` or an action.
|
||||||
- **DON'T** wrap components in React Context providers, HOCs, or `connect()`.
|
- **DON'T** wrap components in React Context providers, HOCs, or `connect()`.
|
||||||
- **DON'T** use React Hooks (`useState`, `useEffect`) when targeting the `automat` class component architecture. Use `PureComponent`.
|
- **DON'T** use React Hooks (`useState`, `useEffect`) when targeting the `automat` class component architecture. Use `PureComponent`.
|
||||||
|
|||||||
39
README.md
39
README.md
@@ -223,7 +223,8 @@ const counterAutomat = new Automat(
|
|||||||
| `automat.state` | Direct getter for current state snapshot (ideal for `constructor`) |
|
| `automat.state` | Direct getter for current state snapshot (ideal for `constructor`) |
|
||||||
| `automat.getState()` | Returns current state snapshot |
|
| `automat.getState()` | Returns current state snapshot |
|
||||||
| `automat.setState(partial)` | Merges partial into state and notifies all subscribers |
|
| `automat.setState(partial)` | Merges partial into state and notifies all subscribers |
|
||||||
| `automat.subscribe(target, selector?)` | Subscribes either a component instance (`this`) or a callback `(state) => ...`. Returns unsub function. |
|
| `automat.subscribe(target, selector?)` | Subscribes a component (`this`) or callback. Supports key string, key array, or selector function. Avoids unnecessary re-renders via shallow equality check. |
|
||||||
|
| `automat.select(selector)` | Returns a sliced view `{ readonly state, subscribe(target) }` for direct constructor reads and scoped subscriptions. |
|
||||||
| `automat.unsubscribe(target)` | Unsubscribes a component instance or callback function |
|
| `automat.unsubscribe(target)` | Unsubscribes a component instance or callback function |
|
||||||
| `automat.subscribeTo(upstream, transform)` | Notification cascade: derives state from an upstream automat |
|
| `automat.subscribeTo(upstream, transform)` | Notification cascade: derives state from an upstream automat |
|
||||||
| `automat.actions` | Named action callbacks passed to constructor — call directly from `onClick` |
|
| `automat.actions` | Named action callbacks passed to constructor — call directly from `onClick` |
|
||||||
@@ -231,6 +232,42 @@ const counterAutomat = new Automat(
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Subscribing to Part of the State (Slice Subscriptions)
|
||||||
|
|
||||||
|
When an automat has multiple fields (e.g. `{ count, filter, theme, user }`), subscribing without a selector will cause any state change to trigger `this.setState()` on the subscriber.
|
||||||
|
|
||||||
|
When a component only cares about a subset of the automat's state, subscribe with a **slice selector**. `Automat` performs an internal shallow equality check (`shallowEqual(lastSlice, nextSlice)`), ensuring updates to other unrelated fields **never trigger `setState` or re-renders**:
|
||||||
|
|
||||||
|
### 1. Single Key String
|
||||||
|
```jsx
|
||||||
|
// Subscribes only to changes in 'count'. Unrelated fields will NOT trigger setState:
|
||||||
|
this.unsubscribe = myAutomat.subscribe(this, 'count');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Array of Keys
|
||||||
|
```jsx
|
||||||
|
// Subscribes only to 'count' and 'step':
|
||||||
|
this.unsubscribe = myAutomat.subscribe(this, ['count', 'step']);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Custom Selector Function
|
||||||
|
```jsx
|
||||||
|
// Computes a derived slice; returning null/undefined skips updates:
|
||||||
|
this.unsubscribe = myAutomat.subscribe(this, (state) => ({
|
||||||
|
count: state.count,
|
||||||
|
isEven: state.count % 2 === 0,
|
||||||
|
}));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Automat Slicing with `.select()`
|
||||||
|
```jsx
|
||||||
|
const countSlice = myAutomat.select('count');
|
||||||
|
this.state = countSlice.state; // { count: 0 }
|
||||||
|
this.unsubscribe = countSlice.subscribe(this);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Notification Cascade (Wired Example)
|
## Notification Cascade (Wired Example)
|
||||||
|
|
||||||
### How `subscribeTo()` Works (Reactive Pipeline)
|
### How `subscribeTo()` Works (Reactive Pipeline)
|
||||||
|
|||||||
@@ -30,6 +30,26 @@
|
|||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Performs a shallow equality check between two values or objects.
|
||||||
|
* Used by Automat subscribers to avoid unnecessary setState calls when
|
||||||
|
* a subscribed slice of state has not changed.
|
||||||
|
*/
|
||||||
|
function shallowEqual(a, b) {
|
||||||
|
if (Object.is(a, b)) return true;
|
||||||
|
if (!a || !b || typeof a !== 'object' || typeof b !== 'object') return false;
|
||||||
|
const keysA = Object.keys(a);
|
||||||
|
const keysB = Object.keys(b);
|
||||||
|
if (keysA.length !== keysB.length) return false;
|
||||||
|
for (let i = 0; i < keysA.length; i++) {
|
||||||
|
const key = keysA[i];
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(b, key) || !Object.is(a[key], b[key])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export class Automat {
|
export class Automat {
|
||||||
/** @type {object} */
|
/** @type {object} */
|
||||||
#state;
|
#state;
|
||||||
@@ -75,43 +95,89 @@ export class Automat {
|
|||||||
*/
|
*/
|
||||||
setState(partial) {
|
setState(partial) {
|
||||||
this.#state = { ...this.#state, ...partial };
|
this.#state = { ...this.#state, ...partial };
|
||||||
this.#notify();
|
this.#notify(partial);
|
||||||
return this.#state;
|
return this.#state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscriber handling for React lifecycle management or listener callbacks.
|
* Subscribes a React PureComponent instance or a listener callback.
|
||||||
*
|
*
|
||||||
* Supports:
|
* Partial / Slice Subscriptions:
|
||||||
* 1. A React component instance (has `.setState`):
|
* By default, subscribing to an automat triggers updates when any state changes.
|
||||||
* `this.unsubscribe = automat.subscribe(this);`
|
* Passing a `selector` allows subscribing to only a slice of state.
|
||||||
* 2. A React component instance with an optional selector:
|
* Changes to unrelated state fields will NOT trigger setState or re-renders.
|
||||||
* `this.unsubscribe = automat.subscribe(this, state => ({ count: state.count }));`
|
|
||||||
* 3. A listener function:
|
|
||||||
* `this.unsubscribe = automat.subscribe((state) => { ... });`
|
|
||||||
*
|
*
|
||||||
* @param {object|function} target React component instance or callback function.
|
* Selector forms:
|
||||||
* @param {function} [selector] Optional selector function mapping state.
|
* 1. Single property key (string):
|
||||||
* @returns {function} Unsubscribe function for componentWillUnmount.
|
* `automat.subscribe(this, 'count')`
|
||||||
|
* 2. Multiple property keys (array of strings):
|
||||||
|
* `automat.subscribe(this, ['count', 'step'])`
|
||||||
|
* 3. Selector function:
|
||||||
|
* `automat.subscribe(this, state => ({ count: state.count }))`
|
||||||
|
* (Return null/undefined to conditionally skip updates)
|
||||||
|
* 4. Full subscription (omitted):
|
||||||
|
* `automat.subscribe(this)`
|
||||||
|
*
|
||||||
|
* @param {object|function} target React component (`this`) or callback function.
|
||||||
|
* @param {string|string[]|function} [selector] Property key, key array, or selector function.
|
||||||
|
* @returns {function} Unsubscribe function for componentWillUnmount.
|
||||||
*/
|
*/
|
||||||
subscribe(target, selector) {
|
subscribe(target, selector) {
|
||||||
let notifyFn;
|
const isComponent = target && typeof target.setState === 'function';
|
||||||
|
const isFunction = typeof target === 'function';
|
||||||
|
|
||||||
if (typeof target === 'function') {
|
if (!isComponent && !isFunction) {
|
||||||
notifyFn = target;
|
|
||||||
} else if (target && typeof target.setState === 'function') {
|
|
||||||
notifyFn = (state) => {
|
|
||||||
const next = typeof selector === 'function' ? selector(state) : state;
|
|
||||||
if (next != null) {
|
|
||||||
target.setState(next);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
'Automat.subscribe expects a callback function or a React component instance with a setState method.'
|
'Automat.subscribe expects a callback function or a React component instance with a setState method.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let getSlice;
|
||||||
|
if (typeof selector === 'string') {
|
||||||
|
getSlice = isComponent
|
||||||
|
? (state) => ({ [selector]: state[selector] })
|
||||||
|
: (state) => state[selector];
|
||||||
|
} else if (Array.isArray(selector)) {
|
||||||
|
getSlice = (state) => {
|
||||||
|
const slice = {};
|
||||||
|
for (let i = 0; i < selector.length; i++) {
|
||||||
|
const key = selector[i];
|
||||||
|
slice[key] = state[key];
|
||||||
|
}
|
||||||
|
return slice;
|
||||||
|
};
|
||||||
|
} else if (typeof selector === 'function') {
|
||||||
|
getSlice = selector;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastSlice = getSlice ? getSlice(this.#state) : undefined;
|
||||||
|
|
||||||
|
const notifyFn = (state, partial) => {
|
||||||
|
if (getSlice) {
|
||||||
|
const nextSlice = getSlice(state);
|
||||||
|
if (nextSlice == null) return;
|
||||||
|
if (shallowEqual(lastSlice, nextSlice)) return;
|
||||||
|
lastSlice = nextSlice;
|
||||||
|
|
||||||
|
if (isComponent) {
|
||||||
|
if (typeof nextSlice !== 'object') {
|
||||||
|
throw new TypeError(
|
||||||
|
'Automat: selector for a React component must return a state object, e.g. state => ({ count: state.count }).'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
target.setState(nextSlice);
|
||||||
|
} else {
|
||||||
|
target(nextSlice);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isComponent) {
|
||||||
|
target.setState(partial ?? state);
|
||||||
|
} else {
|
||||||
|
target(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.#subscribers.set(target, notifyFn);
|
this.#subscribers.set(target, notifyFn);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -119,6 +185,40 @@ export class Automat {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slices this automat to a subset of state for reading and subscription.
|
||||||
|
*
|
||||||
|
* @param {string|string[]|function} selector
|
||||||
|
* @returns {{ readonly state: any, subscribe(target: object|function): function }}
|
||||||
|
*/
|
||||||
|
select(selector) {
|
||||||
|
let getSlice;
|
||||||
|
if (typeof selector === 'string') {
|
||||||
|
getSlice = (state) => ({ [selector]: state[selector] });
|
||||||
|
} else if (Array.isArray(selector)) {
|
||||||
|
getSlice = (state) => {
|
||||||
|
const slice = {};
|
||||||
|
for (let i = 0; i < selector.length; i++) {
|
||||||
|
const key = selector[i];
|
||||||
|
slice[key] = state[key];
|
||||||
|
}
|
||||||
|
return slice;
|
||||||
|
};
|
||||||
|
} else if (typeof selector === 'function') {
|
||||||
|
getSlice = selector;
|
||||||
|
} else {
|
||||||
|
getSlice = (state) => state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
return {
|
||||||
|
get state() {
|
||||||
|
return getSlice(self.state);
|
||||||
|
},
|
||||||
|
subscribe: (target) => self.subscribe(target, selector),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsubscribes a component or listener function.
|
* Unsubscribes a component or listener function.
|
||||||
*
|
*
|
||||||
@@ -171,9 +271,9 @@ export class Automat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
#notify() {
|
#notify(partial) {
|
||||||
for (const notifyFn of this.#subscribers.values()) {
|
for (const notifyFn of this.#subscribers.values()) {
|
||||||
notifyFn(this.#state);
|
notifyFn(this.#state, partial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user