Last active: 2 years ago
Create a Reducer Store with useSyncExternalStore
export type RUAState = Record<string, unknown> | unknown[];
export type StateKey = keyof RUAState;
export type RUAAction<P = unknown, T extends string = string> = {
payload: P;
type: T;
};
export type RUAReducer<S extends RUAState, A extends RUAAction> = (
state: S,
action: A
) => S;
export type RUADispatch<A extends RUAAction> = (action: A) => void;
export type GetSnapshot<S> = () => S;
export type Subscribe = (listener: () => void) => () => void;
export const createStore = <S extends RUAState, A extends RUAAction>(
reducer: RUAReducer<S, A>,
initialState: S
) => {
let state = initialState;
const listeners = new Set<() => void>();
const getSnapshot = () => state;
const dispatch: RUADispatch<A> = (action) => {
state = reducer(state, action);
listeners.forEach((listener) => listener());
};
const subscribe = (listener: () => void) => {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
};
return {
subscribe,
getSnapshot,
dispatch,
};
};
import { useSyncExternalStore } from 'react';
export type Todo = {
id: number;
content: string;
}[];
const initialTodo: Todo = [
{ id: 0, content: 'React' },
{ id: 1, content: 'Vue' },
];
export type TodoAction = RUAAction<number | string, 'add' | 'delete'>;
const reducer: RUAReducer<Todo, TodoAction> = (state, action) => {
switch (action.type) {
case 'add': {
if (action.payload == null) throw new Error('Add todo without payload!');
return [
...state,
{
id: state[state.length - 1].id + 1,
content: action.payload.toString(),
},
];
}
case 'delete': {
if (action.payload == null)
throw new Error('Delete todo without payload!');
return state.filter((todo) => todo.id !== action.payload);
}
default:
throw new Error('Dispatch a reducer without action!');
}
};
const todoStore = createStore(reducer, initialTodo);
export const useTodoStore = (): [Todo, RUADispatch<TodoAction>] => [
useSyncExternalStore(todoStore.subscribe, todoStore.getSnapshot),
todoStore.dispatch,
];