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

  1. Some complex page states need to be transferred from parent to grandson
  2. 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+useContextcomponent

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

  1. 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
  1. Use StoreProvider on the container page

    import {StoreProvider} from './context'
    
    export default function() { 
      return <StoreProvider>
      	<! -- Container contents -->
      </StoreProvider>
    }
    Copy the code
  2. 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