Hooks:使用useReducer 时组合多个reducer? [英] Hooks: combine multiple reducers when using useReducer?

查看:58
本文介绍了Hooks:使用useReducer 时组合多个reducer?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在作为第一个参数传递给 useReducer 的主减速器中使用嵌套的减速器,而不是嵌套的 switch 语句(你能做到吗?).这是因为我的reducer函数依赖于多个switch(第一个操作,然后是水果类型).

我查过嵌套化简器",但这些问题的解决方案似乎都与 redux 和 combineReducers 相关,其中没有与 Hooks 等效的.

演示代码(即使codesandbox已关闭再次):

它实际上并没有显示在代码和盒子中(因为沙盒本身不能正常工作)但是在我自己的机器上我得到 Uncaught TypeError:fruits.apples.map is not a function我单击添加按钮.但是,在此之前,map 工作正常,所有项目都按预期呈现.

解决方案

您的代码中存在一些错误,例如 class 而不是 className,缺少 key 属性.我修改了您的示例,请查看此处.

同样重要的是,reducers 是纯函数 - 当发生变化时总是返回一个新状态由合适的操作触发,不要改变之前的状态(和嵌套的属性).如果没有reducer不能处理action,就返回之前的状态——不要把reducer扔进去,那也会让它变得不纯.

你的形状的另一种选择是让每个子减速器负责整个状态树的一些子状态,以使其更具可扩展性和可组合性(链接).所以一个用于苹果的减速器,一个用于香蕉和一个橙子(首先是水果类型,然后是操作).但原则上,您可以随意处理形状.

希望,这会有所帮助.


更新:

如果您寻求 combineReducers 对于 useReducer,也可以看看 Lauri 的回答,尽管我建议在大多数情况下使用不同的实现.在下面的示例中,每个 reducer 只得到它的 自己的状态部分(切片"),这降低了其复杂性.您也可以很好地扩展此解决方案 - 只需添加一个新属性 + 减速器:

//结合reducers ala Redux:每个都可以处理自己的切片const combineReducers = slices =>(prevState, action) =>//我喜欢用array.reduce,你也可以只写一个for..in循环Object.keys(slices).reduce((nextState, nextProp) =>({...下一个状态,[nextProp]: slices[nextProp](prevState[nextProp], action)}),上一个状态);//atomar 减速器只能获得苹果或橙色状态const apples = (state, action) =>action.type === "ADD_APPLE" ?状态 + 1 :状态;const oranges =(状态,动作)=>action.type === "ADD_ORANGE" ?状态 + 1 :状态;const App = () =>{const [状态,调度] = React.useReducer(combineReducers({ apples, oranges }),//这里我们创建了 reducer 切片{ 苹果:0,橙子:0 });const handleAddApple = () =>dispatch({ type: "ADD_APPLE" });const handleAddOrange = () =>dispatch({ type: "ADD_ORANGE" });返回 (<div><p>苹果:{state.apples},橙子:{state.oranges}</p><button onClick={handleAddApple}>添加苹果</button><button onClick={handleAddOrange}>添加橙色</button>

);};ReactDOM.render(, document.getElementById("root"));

<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 want to use nested reducers instead of having nested switch statements (can you even do that?) in the main reducer that's passed as the first argument to useReducer. This is because my reducer function depends on more than one switch (first operation, then fruit type).

I've looked up "nested reducers" but solutions to those questions seem to all be tied to redux and combineReducers, of which there is no equivalent with Hooks.

Demo code (even though codesandbox is down again):

It doesn't actually show in the codesandbox (because the sandbox itself isn't working properly) but on my own machine I get Uncaught TypeError: fruits.apples.map is not a function after I click the Add button. However, before that, map works fine and all items are rendered as expected.

解决方案

There were some slips in your code, like class instead of className, missing key attributes. I modified your sample, have a look here.

Its also important that reducers are pure functions - always return a new state when a change is triggered by a suitable action, don't mutate the previous state (and nested properties). If no the reducer can't handle the action, just return the previous state - don't throw in the reducer, that would also make it impure.

An alternative to your shape would be to make each child reducer responsible for some sub state of the whole state tree in order to make it more scalable and composeable (Link). So one reducer for apples, one for bananas and one oranges (first fruit type, then operation). But in principle, you can handle the shape like you want/need it.

Hope, that helps out.


Update:

If you seek an Redux-like implementation of combineReducers for useReducer, also have a look at Lauri's answer, though I recommend to use a different implementation for most cases. In the following sample each reducer only gets its own part ("slice") of the state, which reduces its complexity. You also can scale this solution pretty well - just add a new property + reducer:

// combine reducers ala Redux: each can handle its own slice
const combineReducers = slices => (prevState, action) =>
  // I like to use array.reduce, you can also just write a for..in loop 
  Object.keys(slices).reduce(
    (nextState, nextProp) => ({
      ...nextState,
      [nextProp]: slices[nextProp](prevState[nextProp], action)
    }),
    prevState
  );

// atomar reducers only get either apple or orange state
const apples = (state, action) => action.type === "ADD_APPLE" ? state + 1 : state;
const oranges = (state, action) => action.type === "ADD_ORANGE" ? state + 1 : state;

const App = () => {
  const [state, dispatch] = React.useReducer(
    combineReducers({ apples, oranges }), // here we create the reducer slices
    { apples: 0, oranges: 0 }
  );
  const handleAddApple = () => dispatch({ type: "ADD_APPLE" });
  const handleAddOrange = () => dispatch({ type: "ADD_ORANGE" });

  return (
    <div>
      <p>Apples: {state.apples}, Oranges: {state.oranges}</p>
      <button onClick={handleAddApple}>add Apple</button>
      <button onClick={handleAddOrange}>add Orange</button>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));

<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>

这篇关于Hooks:使用useReducer 时组合多个reducer?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆