u
This commit is contained in:
144
README.md
144
README.md
@@ -83,6 +83,7 @@ Creates an observable state container with optional persistence.
|
||||
- `persist` *(boolean)*: Persistence strategy when `name` is provided:
|
||||
- `false` (default): Persists in the `window` object (session memory, survives component unmounts and HMR).
|
||||
- `true`: Persists in `IndexedDB` (survives page reloads and browser restarts).
|
||||
- `loader` *(function, optional)*: Asynchronous loader `(automat) => Promise<object>`. Invoked when `getData()` is called while state is `'idle'`.
|
||||
|
||||
```js
|
||||
// Window-persisted (session memory)
|
||||
@@ -92,16 +93,57 @@ const sessionStore = new Automat(
|
||||
{ name: 'filter', persist: false }
|
||||
);
|
||||
|
||||
// IndexedDB-persisted (survives browser reload)
|
||||
const cartStore = new Automat(
|
||||
{ items: [] },
|
||||
{ addItem(item) { cartStore.setState({ items: [...cartStore.state.items, item] }); } },
|
||||
{ name: 'cart', persist: true }
|
||||
// Async API-backed with loader
|
||||
const userStore = new Automat(
|
||||
{ status: 'idle', data: null },
|
||||
{},
|
||||
{
|
||||
name: 'user',
|
||||
loader: async (store) => {
|
||||
const res = await fetch('/api/user');
|
||||
return { data: await res.json() };
|
||||
},
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `automat.getData(options?)` / `automat.load(options?)`
|
||||
|
||||
Reads state for PureComponent constructor initialization, triggering asynchronous loading if state is idle:
|
||||
|
||||
1. **First Mount (Data Not Loaded)**:
|
||||
- Sets `{ status: 'pending' }` and kicks off the async loader.
|
||||
- Synchronously returns the pending state for the first render.
|
||||
- When the fetch completes, `setState()` notifies subscribers, causing a 2nd render with data.
|
||||
2. **Subsequent Mounts / Remounts (Data Already Loaded)**:
|
||||
- Directly returns `{ status: 'success', data }` synchronously in the constructor.
|
||||
- Component renders completed data immediately. **Zero 2nd render occurs!**
|
||||
|
||||
```jsx
|
||||
class UserProfile extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// 💡 Synchronous read in constructor:
|
||||
this.state = userStore.getData();
|
||||
}
|
||||
componentDidMount() {
|
||||
this.unsubscribe = userStore.subscribe(this);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.unsubscribe();
|
||||
}
|
||||
render() {
|
||||
const { status, data } = this.state;
|
||||
if (status === 'pending') return <Spinner />;
|
||||
return <div>{data.name}</div>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `automat.state` / `automat.getState()`
|
||||
|
||||
Returns the current state snapshot.
|
||||
@@ -245,6 +287,28 @@ const filterStore = Automat.get('filter');
|
||||
|
||||
---
|
||||
|
||||
### `Automat.combine(upstreamAutomats, combiner, options?)`
|
||||
|
||||
Combines multiple upstream automats into a single derived container.
|
||||
|
||||
- **Concurrent Orchestration**: When accessed via `.getData()` while any upstream is idle, triggers all idle upstreams to fetch concurrently in parallel.
|
||||
- **Instant Constructor Resolution**: If all upstream automats have already resolved (`status === 'success'`), `.getData()` computes the projection synchronously in the component constructor, returning complete combined data with zero delay and a single render.
|
||||
|
||||
```js
|
||||
const dashboardAutomat = Automat.combine(
|
||||
[userStatsAutomat, systemMetricsAutomat],
|
||||
(statsState, metricsState) => ({
|
||||
stats: statsState.data,
|
||||
metrics: metricsState.data,
|
||||
})
|
||||
);
|
||||
|
||||
// In component constructor:
|
||||
this.state = dashboardAutomat.getData();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `automat.dispose()`
|
||||
|
||||
Tears down all upstream subscriptions set up via `subscribeTo()`, clears all subscribers, and removes named instance registrations from the window registry.
|
||||
@@ -365,6 +429,76 @@ export class CartView extends PureComponent {
|
||||
|
||||
---
|
||||
|
||||
### 4. Async Fetch with Constructor Caching (Instant Re-Mount)
|
||||
|
||||
When a component accesses `myAutomat.getData()` in its constructor while data is loading, it receives `{ status: 'pending' }` and renders the pending state. When the fetch completes, it renders again (2 renders total).
|
||||
|
||||
When another component mounts (or the component remounts via a collapsible toggle) with data already loaded, `getData()` returns `{ status: 'success', data }` synchronously in the constructor — displaying final data in **1 render with zero 2nd render**:
|
||||
|
||||
```jsx
|
||||
class UserCard extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// 💡 Synchronous read in constructor:
|
||||
// If already loaded, returns immediately -> 1 render only!
|
||||
this.state = profileAsyncAutomat.getData();
|
||||
}
|
||||
componentDidMount() {
|
||||
this.unsubscribe = profileAsyncAutomat.subscribe(this);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.unsubscribe();
|
||||
}
|
||||
render() {
|
||||
const { status, data } = this.state;
|
||||
if (status === 'pending') return <div className="spinner" />;
|
||||
return <div>{data.name}</div>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Combined Multi-API Automats (`Automat.combine`)
|
||||
|
||||
Combines two delayed APIs. If accessed while upstreams are idle, it triggers both to load concurrently in parallel. If both had already been accessed earlier, it resolves instantly in the constructor:
|
||||
|
||||
```js
|
||||
const combinedDashboard = Automat.combine(
|
||||
[userStatsAutomat, systemMetricsAutomat],
|
||||
(stats, metrics) => ({
|
||||
stats: stats.data,
|
||||
metrics: metrics.data,
|
||||
})
|
||||
);
|
||||
|
||||
class Dashboard extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// 💡 If both upstreams were loaded earlier, resolves instantly in constructor:
|
||||
this.state = combinedDashboard.getData();
|
||||
}
|
||||
componentDidMount() {
|
||||
this.unsubscribe = combinedDashboard.subscribe(this);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.unsubscribe();
|
||||
}
|
||||
render() {
|
||||
const { status, stats, metrics } = this.state;
|
||||
if (status === 'pending') return <div>Loading dual sources in parallel...</div>;
|
||||
return (
|
||||
<div>
|
||||
<h1>{stats.name}</h1>
|
||||
<div>Cluster: {metrics.clusterHealth}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ DO
|
||||
|
||||
Reference in New Issue
Block a user