mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
refactor(store): rename slice to feature (#318)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import type { AnyFeature, UnionFeatureRequests, UnionFeatureState, UnionFeatureTarget, UnionFeatureTasks } from '../core/feature';
|
||||
import type { TasksRecord } from '../core/queue';
|
||||
import type { AnySlice, UnionSliceRequests, UnionSliceState, UnionSliceTarget, UnionSliceTasks } from '../core/slice';
|
||||
import type { StoreConfig } from '../core/store';
|
||||
|
||||
import { isNull, isUndefined } from '@videojs/utils/predicate';
|
||||
@@ -15,21 +15,21 @@ import { useRequest as useRequestBase, useSnapshot as useSnapshotBase, useTasks
|
||||
// Types
|
||||
// ----------------------------------------
|
||||
|
||||
export interface CreateStoreConfig<Slices extends AnySlice[]> extends StoreConfig<UnionSliceTarget<Slices>, Slices> {
|
||||
export interface CreateStoreConfig<Features extends AnyFeature[]> extends StoreConfig<UnionFeatureTarget<Features>, Features> {
|
||||
/**
|
||||
* Display name for React DevTools.
|
||||
*/
|
||||
displayName?: string;
|
||||
}
|
||||
|
||||
export interface ProviderProps<Slices extends AnySlice[]> {
|
||||
export interface ProviderProps<Features extends AnyFeature[]> {
|
||||
children: ReactNode;
|
||||
/**
|
||||
* Optional pre-created store instance.
|
||||
* If provided, the Provider will use this store instead of creating one.
|
||||
* The Provider will NOT destroy this store on unmount.
|
||||
*/
|
||||
store?: Store<UnionSliceTarget<Slices>, Slices>;
|
||||
store?: Store<UnionFeatureTarget<Features>, Features>;
|
||||
/**
|
||||
* If true, inherits the store from a parent Provider context instead of creating a new one.
|
||||
* Useful when wrapping a skin with your own Provider to add custom hooks.
|
||||
@@ -38,42 +38,42 @@ export interface ProviderProps<Slices extends AnySlice[]> {
|
||||
inherit?: boolean;
|
||||
}
|
||||
|
||||
export interface CreateStoreResult<Slices extends AnySlice[]> {
|
||||
export interface CreateStoreResult<Features extends AnyFeature[]> {
|
||||
/**
|
||||
* Provider component that creates and manages the store lifecycle.
|
||||
*/
|
||||
Provider: FC<ProviderProps<Slices>>;
|
||||
Provider: FC<ProviderProps<Features>>;
|
||||
|
||||
/**
|
||||
* Returns the typed store instance from context.
|
||||
*/
|
||||
useStore: () => Store<UnionSliceTarget<Slices>, Slices>;
|
||||
useStore: () => Store<UnionFeatureTarget<Features>, Features>;
|
||||
|
||||
/**
|
||||
* Returns a snapshot of the store state.
|
||||
* Re-renders when state changes.
|
||||
*/
|
||||
useSnapshot: () => UnionSliceState<Slices>;
|
||||
useSnapshot: () => UnionFeatureState<Features>;
|
||||
|
||||
/**
|
||||
* Returns the request map or a specific request by name.
|
||||
*/
|
||||
useRequest: {
|
||||
(): UnionSliceRequests<Slices>;
|
||||
<Name extends keyof UnionSliceRequests<Slices>>(name: Name): UnionSliceRequests<Slices>[Name];
|
||||
(): UnionFeatureRequests<Features>;
|
||||
<Name extends keyof UnionFeatureRequests<Features>>(name: Name): UnionFeatureRequests<Features>[Name];
|
||||
};
|
||||
|
||||
/**
|
||||
* Subscribes to task state changes.
|
||||
* Returns the current tasks map from the queue.
|
||||
*/
|
||||
useTasks: () => TasksRecord<UnionSliceTasks<Slices>>;
|
||||
useTasks: () => TasksRecord<UnionFeatureTasks<Features>>;
|
||||
|
||||
/**
|
||||
* Creates a new store instance.
|
||||
* Useful for imperative access or creating a store before render.
|
||||
*/
|
||||
create: () => Store<UnionSliceTarget<Slices>, Slices>;
|
||||
create: () => Store<UnionFeatureTarget<Features>, Features>;
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
@@ -83,22 +83,22 @@ export interface CreateStoreResult<Slices extends AnySlice[]> {
|
||||
/**
|
||||
* Creates a store factory that returns a Provider and typed hooks.
|
||||
*
|
||||
* @param config - Store configuration including slices and optional lifecycle hooks
|
||||
* @param config - Store configuration including features and optional lifecycle hooks
|
||||
* @returns An object containing Provider, hooks, and a create function
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { Provider, useStore, useSnapshot, useRequest, useTasks, create } = createStore({
|
||||
* slices: [playbackSlice, presentationSlice],
|
||||
* features: [playbackFeature, presentationFeature],
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function createStore<Slices extends AnySlice[]>(config: CreateStoreConfig<Slices>): CreateStoreResult<Slices> {
|
||||
type Target = UnionSliceTarget<Slices>;
|
||||
type State = UnionSliceState<Slices>;
|
||||
type Requests = UnionSliceRequests<Slices>;
|
||||
type Tasks = UnionSliceTasks<Slices>;
|
||||
type StoreType = Store<Target, Slices>;
|
||||
export function createStore<Features extends AnyFeature[]>(config: CreateStoreConfig<Features>): CreateStoreResult<Features> {
|
||||
type Target = UnionFeatureTarget<Features>;
|
||||
type State = UnionFeatureState<Features>;
|
||||
type Requests = UnionFeatureRequests<Features>;
|
||||
type Tasks = UnionFeatureTasks<Features>;
|
||||
type StoreType = Store<Target, Features>;
|
||||
|
||||
function create(): StoreType {
|
||||
return new Store(config);
|
||||
@@ -112,7 +112,7 @@ export function createStore<Slices extends AnySlice[]>(config: CreateStoreConfig
|
||||
* 2. If `inherit={true}` and parent store exists, uses parent store (no cleanup)
|
||||
* 3. Otherwise, creates a new store and destroys it on unmount
|
||||
*/
|
||||
function Provider({ children, store: providedStore, inherit = false }: ProviderProps<Slices>): ReactNode {
|
||||
function Provider({ children, store: providedStore, inherit = false }: ProviderProps<Features>): ReactNode {
|
||||
const parentStore = useParentStore();
|
||||
const shouldInherit = inherit && !isNull(parentStore);
|
||||
|
||||
@@ -172,7 +172,7 @@ export function createStore<Slices extends AnySlice[]>(config: CreateStoreConfig
|
||||
Provider,
|
||||
useStore,
|
||||
useSnapshot,
|
||||
useRequest: useRequest as CreateStoreResult<Slices>['useRequest'],
|
||||
useRequest: useRequest as CreateStoreResult<Features>['useRequest'],
|
||||
useTasks,
|
||||
create,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { noop } from '@videojs/utils/function';
|
||||
|
||||
import { createSlice } from '../../../core/slice';
|
||||
import { createFeature } from '../../../core/feature';
|
||||
import { createStore as createCoreStore } from '../../../core/store';
|
||||
|
||||
// Shared mock target for synchronous tests
|
||||
@@ -9,8 +9,8 @@ export class MockMedia extends EventTarget {
|
||||
muted = false;
|
||||
}
|
||||
|
||||
// Shared slice for synchronous tests
|
||||
export const audioSlice = createSlice<MockMedia>()({
|
||||
// Shared feature for synchronous tests
|
||||
export const audioFeature = createFeature<MockMedia>()({
|
||||
initialState: { volume: 1, muted: false },
|
||||
getSnapshot: ({ target }) => ({
|
||||
volume: target.volume,
|
||||
@@ -37,7 +37,7 @@ export const audioSlice = createSlice<MockMedia>()({
|
||||
});
|
||||
|
||||
export function createTestStore() {
|
||||
const store = createCoreStore({ slices: [audioSlice] });
|
||||
const store = createCoreStore({ features: [audioFeature] });
|
||||
const target = new MockMedia();
|
||||
store.attach(target);
|
||||
return { store, target };
|
||||
@@ -49,7 +49,7 @@ export class AsyncMockMedia extends EventTarget {
|
||||
muted = false;
|
||||
}
|
||||
|
||||
export const asyncAudioSlice = createSlice<AsyncMockMedia>()({
|
||||
export const asyncAudioFeature = createFeature<AsyncMockMedia>()({
|
||||
initialState: { volume: 1, muted: false },
|
||||
getSnapshot: ({ target }) => ({
|
||||
volume: target.volume,
|
||||
@@ -96,7 +96,7 @@ export const asyncAudioSlice = createSlice<AsyncMockMedia>()({
|
||||
|
||||
export function createAsyncTestStore() {
|
||||
const store = createCoreStore({
|
||||
slices: [asyncAudioSlice],
|
||||
features: [asyncAudioFeature],
|
||||
onError: noop,
|
||||
});
|
||||
|
||||
@@ -106,8 +106,8 @@ export function createAsyncTestStore() {
|
||||
return { store, target };
|
||||
}
|
||||
|
||||
/** Slice with custom keys (name !== key) for testing superseding behavior. */
|
||||
export const customKeySlice = createSlice<MockMedia>()({
|
||||
/** Feature with custom keys (name !== key) for testing superseding behavior. */
|
||||
export const customKeyFeature = createFeature<MockMedia>()({
|
||||
initialState: { volume: 1, muted: false },
|
||||
getSnapshot: ({ target }) => ({
|
||||
volume: target.volume,
|
||||
@@ -146,7 +146,7 @@ export const customKeySlice = createSlice<MockMedia>()({
|
||||
|
||||
export function createCustomKeyTestStore() {
|
||||
const store = createCoreStore({
|
||||
slices: [customKeySlice],
|
||||
features: [customKeyFeature],
|
||||
onError: noop,
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { renderHook } from '@testing-library/react';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { createSlice } from '../../core/slice';
|
||||
import { createFeature } from '../../core/feature';
|
||||
import { createStore as createCoreStore } from '../../core/store';
|
||||
import { StoreContextProvider, useStoreContext } from '../context';
|
||||
|
||||
@@ -14,7 +14,7 @@ describe('context', () => {
|
||||
volume = 1;
|
||||
}
|
||||
|
||||
const audioSlice = createSlice<MockMedia>()({
|
||||
const audioFeature = createFeature<MockMedia>()({
|
||||
initialState: { volume: 1 },
|
||||
getSnapshot: ({ target }) => ({ volume: target.volume }),
|
||||
subscribe: () => {},
|
||||
@@ -29,7 +29,7 @@ describe('context', () => {
|
||||
});
|
||||
|
||||
it('returns store from context', () => {
|
||||
const store = createCoreStore({ slices: [audioSlice] });
|
||||
const store = createCoreStore({ features: [audioFeature] });
|
||||
|
||||
const { result } = renderHook(() => useStoreContext(), {
|
||||
wrapper: ({ children }: { children: ReactNode }) => (
|
||||
|
||||
@@ -4,7 +4,7 @@ import { act, renderHook } from '@testing-library/react';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { createSlice } from '../../core/slice';
|
||||
import { createFeature } from '../../core/feature';
|
||||
import { createStore } from '../create-store';
|
||||
|
||||
describe('createStore', () => {
|
||||
@@ -14,7 +14,7 @@ describe('createStore', () => {
|
||||
muted = false;
|
||||
}
|
||||
|
||||
const audioSlice = createSlice<MockMedia>()({
|
||||
const audioFeature = createFeature<MockMedia>()({
|
||||
initialState: { volume: 1, muted: false },
|
||||
getSnapshot: ({ target }) => ({
|
||||
volume: target.volume,
|
||||
@@ -37,7 +37,7 @@ describe('createStore', () => {
|
||||
|
||||
describe('create', () => {
|
||||
it('creates a store instance', () => {
|
||||
const { create } = createStore({ slices: [audioSlice] });
|
||||
const { create } = createStore({ features: [audioFeature] });
|
||||
|
||||
const store = create();
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('createStore', () => {
|
||||
|
||||
describe('provider', () => {
|
||||
it('creates store on mount', () => {
|
||||
const { Provider, useStore } = createStore({ slices: [audioSlice] });
|
||||
const { Provider, useStore } = createStore({ features: [audioFeature] });
|
||||
|
||||
const { result } = renderHook(() => useStore(), {
|
||||
wrapper: ({ children }: { children: ReactNode }) => <Provider>{children}</Provider>,
|
||||
@@ -59,7 +59,7 @@ describe('createStore', () => {
|
||||
});
|
||||
|
||||
it('destroys store on unmount', () => {
|
||||
const { Provider, useStore } = createStore({ slices: [audioSlice] });
|
||||
const { Provider, useStore } = createStore({ features: [audioFeature] });
|
||||
|
||||
const { result, unmount } = renderHook(() => useStore(), {
|
||||
wrapper: ({ children }: { children: ReactNode }) => <Provider>{children}</Provider>,
|
||||
@@ -74,7 +74,7 @@ describe('createStore', () => {
|
||||
});
|
||||
|
||||
it('uses provided store prop without destroying on unmount', () => {
|
||||
const { Provider, useStore, create } = createStore({ slices: [audioSlice] });
|
||||
const { Provider, useStore, create } = createStore({ features: [audioFeature] });
|
||||
const providedStore = create();
|
||||
|
||||
const { result, unmount } = renderHook(() => useStore(), {
|
||||
@@ -90,7 +90,7 @@ describe('createStore', () => {
|
||||
});
|
||||
|
||||
it('inherits store from parent when inherit=true', () => {
|
||||
const { Provider, useStore, create } = createStore({ slices: [audioSlice] });
|
||||
const { Provider, useStore, create } = createStore({ features: [audioFeature] });
|
||||
const parentStore = create();
|
||||
|
||||
const { result } = renderHook(() => useStore(), {
|
||||
@@ -105,7 +105,7 @@ describe('createStore', () => {
|
||||
});
|
||||
|
||||
it('creates isolated store by default', () => {
|
||||
const { Provider, useStore, create } = createStore({ slices: [audioSlice] });
|
||||
const { Provider, useStore, create } = createStore({ features: [audioFeature] });
|
||||
const parentStore = create();
|
||||
|
||||
const { result } = renderHook(() => useStore(), {
|
||||
@@ -122,7 +122,7 @@ describe('createStore', () => {
|
||||
|
||||
it('sets displayName on Provider', () => {
|
||||
const { Provider } = createStore({
|
||||
slices: [audioSlice],
|
||||
features: [audioFeature],
|
||||
displayName: 'TestStore',
|
||||
});
|
||||
|
||||
@@ -132,7 +132,7 @@ describe('createStore', () => {
|
||||
|
||||
describe('useStore', () => {
|
||||
it('returns the store from context', () => {
|
||||
const { Provider, useStore, create } = createStore({ slices: [audioSlice] });
|
||||
const { Provider, useStore, create } = createStore({ features: [audioFeature] });
|
||||
const store = create();
|
||||
|
||||
const { result } = renderHook(() => useStore(), {
|
||||
@@ -145,7 +145,7 @@ describe('createStore', () => {
|
||||
|
||||
describe('useSnapshot', () => {
|
||||
it('returns state from context store', () => {
|
||||
const { Provider, useSnapshot, create } = createStore({ slices: [audioSlice] });
|
||||
const { Provider, useSnapshot, create } = createStore({ features: [audioFeature] });
|
||||
const store = create();
|
||||
const target = new MockMedia();
|
||||
store.attach(target);
|
||||
@@ -158,7 +158,7 @@ describe('createStore', () => {
|
||||
});
|
||||
|
||||
it('updates when state changes', async () => {
|
||||
const { Provider, useSnapshot, create } = createStore({ slices: [audioSlice] });
|
||||
const { Provider, useSnapshot, create } = createStore({ features: [audioFeature] });
|
||||
const store = create();
|
||||
const target = new MockMedia();
|
||||
store.attach(target);
|
||||
@@ -180,7 +180,7 @@ describe('createStore', () => {
|
||||
|
||||
describe('useRequest', () => {
|
||||
it('returns request map from context store', () => {
|
||||
const { Provider, useRequest, create } = createStore({ slices: [audioSlice] });
|
||||
const { Provider, useRequest, create } = createStore({ features: [audioFeature] });
|
||||
const store = create();
|
||||
|
||||
const { result } = renderHook(() => useRequest(), {
|
||||
@@ -191,7 +191,7 @@ describe('createStore', () => {
|
||||
});
|
||||
|
||||
it('returns request by name', () => {
|
||||
const { Provider, useRequest, create } = createStore({ slices: [audioSlice] });
|
||||
const { Provider, useRequest, create } = createStore({ features: [audioFeature] });
|
||||
const store = create();
|
||||
|
||||
const { result } = renderHook(() => useRequest('setVolume'), {
|
||||
@@ -204,7 +204,7 @@ describe('createStore', () => {
|
||||
|
||||
describe('useTasks', () => {
|
||||
it('returns tasks from context store', () => {
|
||||
const { Provider, useTasks, create } = createStore({ slices: [audioSlice] });
|
||||
const { Provider, useTasks, create } = createStore({ features: [audioFeature] });
|
||||
const store = create();
|
||||
|
||||
const { result } = renderHook(() => useTasks(), {
|
||||
|
||||
Reference in New Issue
Block a user