refactor(store): remove partial slice state updates (#296)

This commit is contained in:
rahim
2026-01-06 14:23:00 +11:00
committed by GitHub
parent 0354e3ef9c
commit bb2b54e86f
12 changed files with 56 additions and 89 deletions
+2 -3
View File
@@ -22,9 +22,8 @@ export const bufferSlice = createSlice<HTMLMediaElement>()({
}),
subscribe: ({ target, update, signal }) => {
const sync = () => update();
listen(target, 'progress', sync, { signal });
listen(target, 'emptied', sync, { signal });
listen(target, 'progress', update, { signal });
listen(target, 'emptied', update, { signal });
},
request: {},
@@ -28,12 +28,11 @@ export const playbackSlice = createSlice<HTMLMediaElement>()({
}),
subscribe: ({ target, update, signal }) => {
const sync = () => update();
listen(target, 'play', sync, { signal });
listen(target, 'pause', sync, { signal });
listen(target, 'ended', sync, { signal });
listen(target, 'playing', sync, { signal });
listen(target, 'waiting', sync, { signal });
listen(target, 'play', update, { signal });
listen(target, 'pause', update, { signal });
listen(target, 'ended', update, { signal });
listen(target, 'playing', update, { signal });
listen(target, 'waiting', update, { signal });
},
request: {
+4 -5
View File
@@ -22,11 +22,10 @@ export const sourceSlice = createSlice<HTMLMediaElement>()({
}),
subscribe: ({ target, update, signal }) => {
const sync = () => update();
listen(target, 'canplay', sync, { signal });
listen(target, 'canplaythrough', sync, { signal });
listen(target, 'loadstart', sync, { signal });
listen(target, 'emptied', sync, { signal });
listen(target, 'canplay', update, { signal });
listen(target, 'canplaythrough', update, { signal });
listen(target, 'loadstart', update, { signal });
listen(target, 'emptied', update, { signal });
},
request: {
+5 -6
View File
@@ -22,12 +22,11 @@ export const timeSlice = createSlice<HTMLMediaElement>()({
}),
subscribe: ({ target, update, signal }) => {
const sync = () => update();
listen(target, 'timeupdate', sync, { signal });
listen(target, 'durationchange', sync, { signal });
listen(target, 'seeked', sync, { signal });
listen(target, 'loadedmetadata', sync, { signal });
listen(target, 'emptied', sync, { signal });
listen(target, 'timeupdate', update, { signal });
listen(target, 'durationchange', update, { signal });
listen(target, 'seeked', update, { signal });
listen(target, 'loadedmetadata', update, { signal });
listen(target, 'emptied', update, { signal });
},
request: {
+1 -2
View File
@@ -22,8 +22,7 @@ export const volumeSlice = createSlice<HTMLMediaElement>()({
}),
subscribe: ({ target, update, signal }) => {
const sync = () => update();
listen(target, 'volumechange', sync, { signal });
listen(target, 'volumechange', update, { signal });
},
request: {
+14 -21
View File
@@ -63,7 +63,7 @@ const audioSlice = createSlice<HTMLMediaElement>()({
}),
subscribe: ({ target, update, signal }) => {
target.addEventListener('volumechange', () => update(), { signal });
target.addEventListener('volumechange', update, { signal });
},
request: {
@@ -263,44 +263,37 @@ const unsubscribe = store.subscribe((state) => {
// Single value - only fires when volume changes
store.subscribe(
(s) => s.volume,
(volume) => console.log('Volume:', volume)
s => s.volume,
volume => console.log('Volume:', volume)
);
// Multiple values - auto-optimized with key-based subscription
store.subscribe(
(s) => ({ volume: s.volume, muted: s.muted }),
s => ({ volume: s.volume, muted: s.muted }),
({ volume, muted }) => updateAudioUI(volume, muted)
);
// Derived value
store.subscribe(
(s) => Math.round(s.volume * 100),
(percent) => console.log(`${percent}%`)
s => Math.round(s.volume * 100),
percent => console.log(`${percent}%`)
);
// Custom equality function
store.subscribe(
(s) => s.playlist,
(playlist) => renderPlaylist(playlist),
s => s.playlist,
playlist => renderPlaylist(playlist),
{ equalityFn: shallowEqual }
);
```
Slices can push partial updates to avoid full syncs:
Slices sync state from the target via `getSnapshot`. The `update` callback triggers a sync, and the store only notifies subscribers for keys that actually changed:
```ts
subscribe: ({ target, update, signal }) => {
// Partial - only update currentTime
target.addEventListener(
'timeupdate',
() => {
update({ currentTime: target.currentTime });
},
{ signal }
);
// Full sync
// Each event triggers a full sync via getSnapshot
// Only changed keys notify their subscribers
target.addEventListener('timeupdate', update, { signal });
target.addEventListener('durationchange', update, { signal });
};
```
@@ -542,7 +535,7 @@ const store = createStore({
],
queue: createQueue({
// Default scheduler for requests without schedule
scheduler: (flush) => queueMicrotask(flush),
scheduler: flush => queueMicrotask(flush),
// Lifecycle hooks
onDispatch: (request) => {
@@ -628,7 +621,7 @@ const store = createStore({
slices: [
/* ... */
],
state: (initial) => new VueStateAdapter(initial),
state: initial => new VueStateAdapter(initial),
});
```
+4 -6
View File
@@ -35,16 +35,14 @@ export interface SliceGetSnapshotContext<Target, State> {
export type SliceSubscribe<Target, State extends object> = (ctx: SliceSubscribeContext<Target, State>) => void;
export interface SliceSubscribeContext<Target, State extends object> {
export interface SliceSubscribeContext<Target, _State extends object> {
target: Target;
update: SliceUpdate<State>;
update: SliceUpdate;
signal: AbortSignal;
}
export interface SliceUpdate<State extends object> {
(): void;
(state: Partial<State>): void;
}
/** Sync slice state from target via getSnapshot. */
export type SliceUpdate = () => void;
export interface SliceConfig<
Target,
+11 -14
View File
@@ -1,6 +1,13 @@
import type { PendingTask, Task, TaskContext } from './queue';
import type { RequestMeta, RequestMetaInit, ResolvedRequestConfig } from './request';
import type { AnySlice, Slice, UnionSliceRequests, UnionSliceState, UnionSliceTarget, UnionSliceTasks } from './slice';
import type {
AnySlice,
SliceUpdate,
UnionSliceRequests,
UnionSliceState,
UnionSliceTarget,
UnionSliceTasks,
} from './slice';
import type { StateFactory } from './state';
import { getSelectorKeys } from '@videojs/utils/object';
@@ -115,20 +122,10 @@ export class Store<Target, Slices extends AnySlice<Target>[] = AnySlice<Target>[
return () => this.#detach();
}
#createUpdate<State extends object>(slice: Slice<Target, State, any>) {
return (partial?: Partial<State>) => {
#createUpdate(slice: AnySlice<Target>): SliceUpdate {
return () => {
const target = this.#target;
if (!target) return;
try {
if (partial === undefined) {
this.#syncSlice(slice, target);
} else {
this.#state.patch(partial as Partial<UnionSliceState<Slices>>);
}
} catch (error) {
this.#handleError({ error });
}
if (target) this.#syncSlice(slice, target);
};
}
@@ -15,7 +15,7 @@ describe('store lifecycle integration', () => {
getSnapshot: ({ target }) => ({ count: target.value }),
subscribe: ({ target, update, signal }) => {
events.push('subscribe');
target.addEventListener('change', () => update(), { signal });
target.addEventListener('change', update, { signal });
signal.addEventListener('abort', () => events.push('unsubscribe'));
},
request: {
@@ -231,7 +231,7 @@ describe('request coordination', () => {
});
describe('state syncing', () => {
it('partial updates only trigger relevant subscriptions', async () => {
it('updates only trigger subscriptions for changed keys', async () => {
const volumeUpdates: number[] = [];
const mutedUpdates: boolean[] = [];
@@ -250,21 +250,8 @@ describe('state syncing', () => {
muted: target.muted,
}),
subscribe: ({ target, update, signal }) => {
target.addEventListener(
'volumechange',
() => {
update({ volume: target.volume });
},
{ signal },
);
target.addEventListener(
'mutechange',
() => {
update({ muted: target.muted });
},
{ signal },
);
target.addEventListener('volumechange', update, { signal });
target.addEventListener('mutechange', update, { signal });
},
request: {
setVolume: (volume: number, { target }) => {
+2 -3
View File
@@ -21,10 +21,9 @@ describe('store', () => {
muted: target.muted,
}),
subscribe: ({ target, update, signal }) => {
const handler = () => update();
target.addEventListener('volumechange', handler);
target.addEventListener('volumechange', update);
signal.addEventListener('abort', () => {
target.removeEventListener('volumechange', handler);
target.removeEventListener('volumechange', update);
});
},
request: {
@@ -21,10 +21,9 @@ describe('createStore', () => {
muted: target.muted,
}),
subscribe: ({ target, update, signal }) => {
const handler = () => update();
target.addEventListener('volumechange', handler);
target.addEventListener('volumechange', update);
signal.addEventListener('abort', () => {
target.removeEventListener('volumechange', handler);
target.removeEventListener('volumechange', update);
});
},
request: {
@@ -20,10 +20,9 @@ describe('react hooks', () => {
muted: target.muted,
}),
subscribe: ({ target, update, signal }) => {
const handler = () => update();
target.addEventListener('volumechange', handler);
target.addEventListener('volumechange', update);
signal.addEventListener('abort', () => {
target.removeEventListener('volumechange', handler);
target.removeEventListener('volumechange', update);
});
},
request: {