基于 React Hook 封装 Store 的三种方案
方案一:基于 useSyncExternalStore 的轻量级 Store(推荐)
tsx
import { useSyncExternalStore } from 'react';
type Store<T> = {
state: T;
listeners: Set<() => void>;
};
function createStore<T>(initialState: T) {
const store: Store<T> = {
state: initialState,
listeners: new Set(),
};
const setState = (updater: (prev: T) => T) => {
store.state = updater(store.state);
store.listeners.forEach(listener => listener());
};
const subscribe = (listener: () => void) => {
store.listeners.add(listener);
return () => store.listeners.delete(listener);
};
const useStore = <S>(selector: (state: T) => S): S => {
return useSyncExternalStore(
subscribe,
() => selector(store.state),
() => selector(initialState)
);
};
return [useStore, setState] as const;
}特性:零依赖、无 Provider、精准渲染
方案二:基于 Context + useReducer
tsx
import { createContext, useContext, useReducer } from 'react';
type State = { count: number };
type Action = { type: 'INCREMENT' } | { type: 'DECREMENT' };
const StoreContext = createContext<{
state: State;
dispatch: React.Dispatch<Action>;
} | null>(null);
function StoreProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer((prev: State, action: Action) => {
switch (action.type) {
case 'INCREMENT': return { count: prev.count + 1 };
case 'DECREMENT': return { count: prev.count - 1 };
default: return prev;
}
}, { count: 0 });
return (
<StoreContext.Provider value={{ state, dispatch }}>
{children}
</StoreContext.Provider>
);
}
function useStore() {
const context = useContext(StoreContext);
if (!context) throw new Error('useStore must be used within StoreProvider');
return context;
}方案三:基于 useReducer + useContext
tsx
import { useReducer, useContext, createContext } from 'react';
const initialState = { count: 0 };
function reducer(state: typeof initialState, action: { type: string }) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
}
const CounterContext = createContext<{
state: typeof initialState;
dispatch: React.Dispatch<{ type: string }>;
} | null>(null);
export function CounterProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<CounterContext.Provider value={{ state, dispatch }}>
{children}
</CounterContext.Provider>
);
}