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

IT技术 javascript reactjs react-redux react-hooks reducers
2021-03-24 00:24:42

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

假设我有一个 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;
6个回答

组合切片减速器 ( combineReducers)

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

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
  );
例子:
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>
  );
};

依次组合减速器

任意形状的状态上依次应用多个 reducer ,类似于reduce-reducers

const reduceReducers = (...reducers) => (state, action) =>
  reducers.reduce((acc, nextReducer) => nextReducer(acc, action), state);
例子:
const rootReducer2 = reduceReducers(a, b);
// rest like in first variant

组合多个useReducerHook

您还可以结合多个useReducers 的调度和/或状态,例如:

const combineDispatch = (...dispatches) => (action) =>
  dispatches.forEach((dispatch) => dispatch(action));
例子:
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>;

总之

以上是最常见的变体。还有类似use-combined-reducers这些案例的最后,看看下面结合combineReducers和 的示例reduceReducers

如果您只是想在没有任何第三方库的情况下实现组合减速器功能,请按以下步骤操作。(参考:Redux 源/代码)工作代码在这里https://codepen.io/rajeshpillai/pen/jOPWYzL?editors=0010

我创建了两个减速器,一个 dateReducer 和另一个 counterReducer。我用它作为

const [state, dispatch] = useReducer(combineReducer({ counter: counterReducer, date: dateReducer }), initialState);

combineReducers 代码

function combineReducers(reducers) {  
  return (state = {}, action) => {
    const newState = {};
    for (let key in reducers) {
      newState[key] = reducers[key](state[key], action);
    }
    return newState;
  }
}

用法:提取各自的状态

const { counter, date } = state;

注意:如果您愿意,您可以添加更多类似 redux 的功能。

完整的工作代码(以防 codepen 关闭 :))

const {useReducer, useEffect} = React;


function dateReducer(state, action) {
  switch(action.type) {
    case "set_date":
      return action.payload;
      break;
    default:
      return state;
  }  
}

function counterReducer(state, action) {
  console.log('cr:', state);
  switch (action.type) {
    case 'increment': {
      return state + 1;
    }
    case 'decrement': {
      return state - 1;
    }

    default:
      return state;
  }
}

function combineReducers(reducers) {  
  return (state = {}, action) => {
    const newState = {};
    for (let key in reducers) {
      newState[key] = reducers[key](state[key], action);
    }
    return newState;
  }
}

const initialState = {
  counter: 0,
  date: new Date
};

function App() {
  const [state, dispatch] = useReducer(combineReducers({
    counter: counterReducer,
    date: dateReducer 
  }), initialState);  

  console.log("state", state);
  const { counter, date } = state;

  return (
    <div className="app">
      <h3>Counter Reducer</h3>
      <div className="counter">
        <button onClick={() => 
          dispatch({ type: 'increment'})}>+          
        </button>

        <h2>{counter.toString()}</h2>
        <button onClick={() => 
             dispatch({ type: 'decrement'})}>-
        </button>
      </div>
      <hr/>
      <h3>Date Reducer</h3>
      {date.toString()}
      <button className="submit" 
          type="submit"
          onClick={() => 
             dispatch({ type: 'set_date', payload:new Date })}>
           Set Date
        </button>
    </div>
  );
}

const rootElement = document.querySelector("#root");
ReactDOM.render(<App />, rootElement);  

注意:这是一个快速技巧(仅用于学习和演示目的)

是否可以将此组合减速器功能转换为typescript功能?
2021-05-24 00:24:42
这个对我有用。基于减速器的减速器没有正确更新状态。
2021-06-21 00:24:42

在您的rootReducer.js文件中,您可以使用combineReducersfromredux来组合多个减速器。传统的方式是:

import { combineReducers } from 'redux';

const rootReducer = combineReducers({ name: nameReducer});

export default rootReducer;

您可以rootReducer在创建商店时将其导入为:

import { combineReducers } from 'redux';

let store = createStore(rootReducer);

使用useReducer钩子时,您可以将其传递rootReducer给它:

const [state, dispatch] = useReducer(rootReducer, initialState);

希望这对你有用。

@MuhammadZeeshan 我必须使用 redux createStore 吗?理想情况下想坚持上下文
2021-05-29 00:24:42
这与他们现在所做的有什么不同?
2021-06-05 00:24:42
OP 已经在使用useReducer. 我在问您的代码与 OP 已有的代码有何不同。
2021-06-05 00:24:42
引入了钩子以使反应应用程序更加优化。正如官方文档中所说: useReducer 可以让您优化触发深度更新的组件的性能,因为您可以向下传递调度而不是回调。
2021-06-08 00:24:42
他询问了更好的方法。我向他展示了处理场景的两种方式。
2021-06-19 00:24:42

有一个名为react combine reducer的库,专门用于将 reducer 与 context api 结合使用。下面是代码示例

import { useReducer } from 'react';
  import combineReducers from 'react-combine-reducers';
 
  const initialIdentity = {
    name: 'Harry'
  }
 
  const initialLocation = {
    country: 'UK',
    city: 'London'
  }
 
  const identityReducer = (state, action) => {
    switch (action.type) {
      case 'ACTION_A':
        return { ...state, name: 'Puli' };
      default: return state;
    }
  }
 
  const locationReducer = (state, action) => {
    switch (action.type) {
      case 'ACTION_B':
        return { ...state, city: 'Manchester' };
      default: return state;
    }
  }
 
  const [profileReducer, initialProfile] = combineReducers({
    identity: [identityReducer, initialIdentity],
    location: [locationReducer, initialLocation]
  });
 
  const [state, dispatch] = useReducer(profileReducer, initialProfile);
 
  console.log(state);
  // Outputs the following state:
  // {
  //   identity: {
  //     name: "Harry"
  //   },
  //   location: {
  //     country: "UK",
  //     city: "London"
  //   }
  // }

我玩了一会儿,正在考虑这个问题,因为我也必须处理它。

这可能不是最好的方法,但我只是将我的 reducer 定义为带有key 的对象:reducer 函数组合:

const counterRed = {
  increment: (oldState, action) => ({
    ...oldState,
    counter: oldState.counter + 1
  }),
  decrement: (oldState, action) => ({
    ...oldState,
    counter: oldState.counter - 1
  })
};

const dateRed = {
  set_date: (oldState, action) => ({ ...oldState, date: action.payload })
};

我把它们组合成这样:

const reducer = (oldState, action) => {
  const combinedReducers = { ...dateRed, ...counterRed };
  let newState = null;
  if (combinedReducers[action.type]) {
    newState = combinedReducers[action.type](oldState, action);
  }
  if (newState) {
    return { ...newState };
  }
  return oldState;
};

可以在这里看到一个工作示例:https : //codesandbox.io/s/jovial-kowalevski-25pzf?file=/src/App.js