mirror of
https://github.com/zoriya/v10.git
synced 2026-08-09 15:46:42 +00:00
refactor(store): remove partial slice state updates (#296)
This commit is contained in:
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
@@ -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),
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user