jtwang7 / React-Note

React 学习笔记
8 stars 2 forks source link

Combine Reducer #47

Open jtwang7 opened 2 years ago

jtwang7 commented 2 years ago

useReducer - combine reducers

参考链接:React useReducer: How to combine multiple reducers?

原文+注解

Combine slice reducers (combineReducers)

The most common approach is to let each reducer manage its own property ("slice") of the state:

独立每个state和reducer的逻辑,将相应的state和reducer打包为一个slice,然后合并。

type A = any;
type B = any;
type STATE = {
  a: A;
  b: B;
}
type ACTION = 
    | {type: 'changeA', payload: any}
    | {type: 'changeB', payload: any}
type REDUCER = <T extends {}>(state: T, action: ACTION) => STATE
type SLICES = {
  a: REDUCER<A>;
  b: REDUCER<B>;
}
// reduce 写法
const combineReducers = (slices: SLICES) => (state:STATE, action:ACTION):STATE =>
  // use for..in loop, if you prefer it
  Object.keys(slices).reduce( 
    (state, key) => ({
      ...acc,
      [key]: slices[key](state[key], action),
    }),
    state
  );

// for...in 写法
const combineReducers = (slices: SLICES) => (state:STATE, action:ACTION):STATE => {
  let newState = {}
  for (let [key, reducer] of Object.entries(slices)) {
    newState = {
      ...state,
      [key]: reducer(state[key], action)
    }
  }
  return newState
}

Example:

import a from "./Reducer1";
import b from "./Reducer2";

const initialState = { a: {}, b: {} }; // some state for props a, b
const rootReducer = combineReducers({ a, b });

const StoreProvider = ({ children }) => {
  const [state, dispatch] = useReducer(rootReducer, initialState);
  // Important(!): memoize array value. Else all context consumers update on *every* render
  const store = React.useMemo(() => [state, dispatch], [state]);
  return (
    <StoreContext.Provider value={store}> {children} </StoreContext.Provider>
  );
};

Combine reducers in sequence

Apply multiple reducers in sequence on state with arbitrary shape, akin to reduce-reducers:

const reduceReducers = (...reducers) => (state, action) =>
  reducers.reduce((acc, nextReducer) => nextReducer(acc, action), state);

Example:

const rootReducer2 = reduceReducers(a, b);
// rest like in first variant

Combine multiple useReducer Hooks

You could also combine dispatch and/or state from multiple useReducers, like:

合并多个 useReducer 实现 combine reducer

主要思想:遍历所有 dispatch,最终会找到 action 所对应的 dispatch 然后执行

const combineDispatch = (...dispatches) => (action) =>
  dispatches.forEach((dispatch) => dispatch(action));

Example:

const [s1, d1] = useReducer(a, {}); // some init state {} 
const [s2, d2] = useReducer(b, {}); // some init state {} 

// don't forget to memoize again
const combinedDispatch = React.useCallback(combineDispatch(d1, d2), [d1, d2]);
const combinedState = React.useMemo(() => ({ s1, s2, }), [s1, s2]);

// This example uses separate dispatch and state contexts for better render performance
<DispatchContext.Provider value={combinedDispatch}>
  <StateContext.Provider value={combinedState}> {children} </StateContext.Provider>
</DispatchContext.Provider>;

In summary

Above are the most common variants. There are also libraries like use-combined-reducers for these cases. Last, take a look at following sample combining both combineReducers and reduceReducers