background
When developing a React project, redux is often used to manage global data. However, it is not necessary to use Redux in some scenarios, such as the following
- Some complex page states need to be transferred from parent to grandson
- The project is small, with at least a small amount of global data to maintain
For both scenarios, you can use useReducer + useContext to simulate a REdux for state management
Encapsulates auseReducer
+useContext
component
import React, {
Dispatch,
useReducer,
createContext,
useContext,
useEffect,
Reducer,
} from 'react';
export interface ReducerContextResult<T, A> {
useStore: () = > T;
useDispatch: () = > Dispatch<A>;
StoreProvider: React.FC<any>;
}
function createReducerContext<T.A> (initState: T, reducer: Reducer<T, A>, initData? : (state: T, dispatch: Dispatch<A>) =>void.) :ReducerContextResult<T.A> {
const StateContext = createContext(initState);
const DispatchContext = createContext({} as Dispatch<A>);
function useStore() :T {
return useContext(StateContext);
}
function useDispatch() :Dispatch<A> {
return useContext(DispatchContext);
}
function StoreProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer<Reducer<T, A>>(reducer, initState);
useEffect(() = > {
/*eslint-disable*/initData && initData(initState, dispatch); } []);return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
return {
useStore,
useDispatch,
StoreProvider,
};
}
export default createReducerContext;
Copy the code
In the code above, we have encapsulated a generic createReducerContext. How can we use this component
Use encapsulated components
- Initialize the context
Declare an action_type
export enum ACTION_TYPE {
INIT = 'init',
UPDATE = 'update'
// Add other types
}
export interface IUser{
name: string;
sex: number;
}
export interface IState {
userInfo: IUser[];
/ / the other state
}
export default IAction{
type: ACTION_TYPE;
data: any;
}
const initState: IState {
userInfo: [];
}
function reducer(state: IState, action: IAction) :IState {
switch (action.type) {
case ACTION_TYPE.INIT:
return { ...action.data };
default:
returnstate; }}const contextResult: ReducerContextResult<IState, IAction> = createReducerContext<
IState,
IAction
>(initState, reducer, (state: IState, dispatch: Dispatch<IAction>) = > {
// This is where the data can be initialized
loadData().then(data= > {
dispatch({
type: ACTION_TYPE.INIT,
data
})
})
});
export const { useStore, useDispatch, StoreProvider } = contextResult;
Copy the code
-
Use StoreProvider on the container page
import {StoreProvider} from './context' export default function() { return <StoreProvider> <! -- Container contents --> </StoreProvider> } Copy the code
-
Use useStore and useDispatch in child components
import {useStore, useDispatch, ACTION_TYPE} from './context' const UserList = (props) = > { / / data const {data} = useStore() // Update data with dispatch const dispatch = useDispatch() // Update data with dispatch function updateData(result) { dispatch({ type: ACTION_TYPE.UPDATE, data: result }) } } Copy the code