React useReducer:如何组合多个减速器?

人气:389 发布:2022-10-16 标签: javascript reactjs react-hooks react-redux reducers

问题描述

我不是 Javascript 专家,所以我想知道是否有人有一种优雅"的方式来组合多个减速器来创建全局状态(如 Redux).一个状态更新多个组件等时不影响性能的函数.

假设我有一个 store.js

import React, { createContext, useReducer } from "react";从./Rootreducer"导入 Rootreducer导出 const StoreContext = createContext();常量初始状态 = {....};export const StoreProvider = props =>{const [state, dispatch] = useReducer(Rootreducer, initialState);返回 (<StoreContext.Provider value={[state, dispatch]}>{props.children}<StoreContext.Provider>);};

Rootreducer.js

从./Reducer1"导入Reducer1从./Reducer2"导入Reducer2从./Reducer3"导入Reducer3从./Reducer4"导入Reducer4const rootReducer = combineReducers({减速机1,减速机2,减速机3,减速机4})导出默认的 rootReducer;

解决方案

Combine slice reducers (combineReducers)

最常见的方法是让每个 reducer 管理自己的状态属性(切片"):

const combineReducers = (slices) =>(状态,动作)=>Object.keys(slices).reduce(//使用 for..in 循环,如果你喜欢的话(acc, prop) =>({...acc,[prop]: slices[prop](acc[prop], action),}),状态);

例子:

从./Reducer1"导入一个;从./Reducer2"导入b;const initialState = { a: {}, b: {} };//道具 a, b 的一些状态const rootReducer = combineReducers({ a, b });const StoreProvider = ({ children }) =>{const [state, dispatch] = useReducer(rootReducer, initialState);//重要(!):记住数组值.否则所有上下文消费者都会在 *every* 渲染时更新const store = React.useMemo(() => [state, dispatch], [state]);返回 (<StoreContext.Provider value={store}>{children} </StoreContext.Provider>);};

按顺序组合reducer

应用多个reducer 按顺序在具有任意形状的状态上,类似于reduce-reducers:

const reduceReducers = (...reducers) =>(状态,动作)=>reducers.reduce((acc, nextReducer) => nextReducer(acc, action), state);

例子:

const rootReducer2 = reduceReducers(a, b);//像第一个变体一样休息

组合多个useReducer Hooks

您还可以组合来自多个 useReducer 的调度和/或状态,例如:

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

例子:

const [s1, d1] = useReducer(a, {});//一些初始化状态 {}const [s2, d2] = useReducer(b, {});//一些初始化状态 {}//不要忘记再次记忆const combineDispatch = React.useCallback(combineDispatch(d1, d2), [d1, d2]);const combineState = React.useMemo(() => ({ s1, s2, }), [s1, s2]);//此示例使用单独的分派和状态上下文以获得更好的渲染性能<DispatchContext.Provider value={combinedDispatch}><StateContext.Provider value={combinedState}>{children} </StateContext.Provider></DispatchContext.Provider>;

总结

以上是最常见的变体.还有像 use-combined-reducers 用于这些情况.最后,看看下面结合 combineReducersreduceReducers 的示例:

const StoreContext = React.createContext();const initialState = { a: 1, b: 1 };//为简洁起见省略不同的动作类型const plusOneReducer = (state, _action) =>状态+1;const timesTwoReducer = (state, _action) =>状态 * 2;const rootReducer = combineReducers({a: reduceReducers(plusOneReducer, plusOneReducer),//aNew = aOld + 1 + 1b: reduceReducers(timesTwoReducer, plusOneReducer)//bNew = bOld * 2 + 1});const StoreProvider = ({ children }) =>{const [state, dispatch] = React.useReducer(rootReducer, initialState);const store = React.useMemo(() => [state, dispatch], [state]);返回 (<StoreContext.Provider value={store}>{children} </StoreContext.Provider>);};const Comp = () =>{const [globalState, globalDispatch] = React.useContext(StoreContext);返回 (<div><p>a: {globalState.a}, b: {globalState.b}</p><button onClick={globalDispatch}>点击我</button>

);};const App = () =><StoreProvider><比较/></StoreProvider>ReactDOM.render(, document.getElementById("root"));////帮手//功能 combineReducers(切片){返回(状态,动作)=>Object.keys(slices).reduce((acc, prop) =>({...acc,[prop]: slices[prop](acc[prop], action)}),状态)}函数 reduceReducers(...reducers){返回(状态,动作)=>reducers.reduce((acc, nextReducer) => nextReducer(acc, action), state)}

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4="crossorigin="anonymous"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w="crossorigin="匿名"></script><div id="root"></div>

I'm not a Javascript expert so I wondered if anyone has an "elegant" way to combine multiple reducers to create a global state(Like Redux). A function that does not affect performance when a state updating multiple components etc..

Let's say I have a store.js

import React, { createContext, useReducer } from "react";
import Rootreducer from "./Rootreducer"

export const StoreContext = createContext();

const initialState = {
    ....
};

export const StoreProvider = props => {
  const [state, dispatch] = useReducer(Rootreducer, initialState);

  return (
    <StoreContext.Provider value={[state, dispatch]}>
      {props.children}
    <StoreContext.Provider>
  );
};

Rootreducer.js

import Reducer1 from "./Reducer1"
import Reducer2 from "./Reducer2"
import Reducer3 from "./Reducer3"
import Reducer4 from "./Reducer4"

const rootReducer = combineReducers({
Reducer1,
Reducer2,
Reducer3,
Reducer4
})

export default rootReducer;

解决方案

Combine slice reducers (combineReducers)

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

const combineReducers = (slices) => (state, action) =>
  Object.keys(slices).reduce( // use for..in loop, if you prefer it
    (acc, prop) => ({
      ...acc,
      [prop]: slices[prop](acc[prop], action),
    }),
    state
  );

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:

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:

const StoreContext = React.createContext();
const initialState = { a: 1, b: 1 };

// omit distinct action types for brevity
const plusOneReducer = (state, _action) => state + 1;
const timesTwoReducer = (state, _action) => state * 2;
const rootReducer = combineReducers({
  a: reduceReducers(plusOneReducer, plusOneReducer), // aNew = aOld + 1 + 1
  b: reduceReducers(timesTwoReducer, plusOneReducer) // bNew = bOld * 2 + 1
});

const StoreProvider = ({ children }) => {
  const [state, dispatch] = React.useReducer(rootReducer, initialState);
  const store = React.useMemo(() => [state, dispatch], [state]);
  return (
    <StoreContext.Provider value={store}> {children} </StoreContext.Provider>
  );
};

const Comp = () => {
  const [globalState, globalDispatch] = React.useContext(StoreContext);
  return (
    <div>
      <p>
        a: {globalState.a}, b: {globalState.b}
      </p>
      <button onClick={globalDispatch}>Click me</button>
    </div>
  );
};

const App = () => <StoreProvider> <Comp /> </StoreProvider>
ReactDOM.render(<App />, document.getElementById("root"));

//
// helpers
//

function combineReducers(slices) {
  return (state, action) =>
    Object.keys(slices).reduce(
      (acc, prop) => ({
        ...acc,
        [prop]: slices[prop](acc[prop], action)
      }),
      state
    )
}

function reduceReducers(...reducers){ 
  return (state, action) =>
    reducers.reduce((acc, nextReducer) => nextReducer(acc, action), state)
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>

670