74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
import cloneDeep from 'lodash/cloneDeep';
|
|
|
|
/**
|
|
* Creates a reactive state object that batches changes and notifies all subscribers with the full old and new state.
|
|
*
|
|
* @param initialState - The initial state object
|
|
* @returns Object with { state: reactive proxy, subscribe: function to add callbacks }
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const { state, subscribe } = createBatchedReactiveState({ count: 0, user: { name: 'Alice' } });
|
|
*
|
|
* const unsubscribe1 = subscribe((newState, oldState) => {
|
|
* console.log('Subscriber 1:', oldState, '->', newState);
|
|
* });
|
|
*
|
|
* const unsubscribe2 = subscribe((newState, oldState) => {
|
|
* console.log('Subscriber 2:', newState.count);
|
|
* });
|
|
*
|
|
* state.count = 1; // Triggers both subscribers once
|
|
* state.user.name = 'Bob'; // Triggers both subscribers once (batched)
|
|
*
|
|
* unsubscribe1(); // Remove first subscriber
|
|
* state.count = 2; // Only subscriber 2 is called
|
|
* ```
|
|
*/
|
|
export function createReactiveState<T extends object>(initialState: T) {
|
|
const callbacks = new Set<(newState: T, oldState: T) => void>();
|
|
let isBatching = false;
|
|
let oldState: T | null = null;
|
|
let scheduled = false;
|
|
|
|
const rootState = cloneDeep(initialState);
|
|
|
|
function createReactiveObject(obj: any): any {
|
|
return new Proxy(obj, {
|
|
get(target, property, receiver) {
|
|
const value = Reflect.get(target, property, receiver);
|
|
return typeof value === 'object' && value !== null ? createReactiveObject(value) : value;
|
|
},
|
|
set(target, property, value, receiver) {
|
|
if (!isBatching) {
|
|
isBatching = true;
|
|
oldState = cloneDeep(rootState);
|
|
}
|
|
const success = Reflect.set(target, property, value, receiver);
|
|
if (success) {
|
|
if (!scheduled) {
|
|
scheduled = true;
|
|
queueMicrotask(() => {
|
|
callbacks.forEach((cb) => cb(rootState, oldState!));
|
|
isBatching = false;
|
|
oldState = null;
|
|
scheduled = false;
|
|
});
|
|
}
|
|
}
|
|
return success;
|
|
},
|
|
});
|
|
}
|
|
|
|
const state = createReactiveObject(rootState);
|
|
|
|
return [
|
|
state,
|
|
function subscribe(callback: (newState: T, oldState: T) => void) {
|
|
callbacks.add(callback);
|
|
return () => callbacks.delete(callback);
|
|
},
|
|
] as const;
|
|
}
|