与 useState 相比,useReducer 实际上有哪些优势?

useReducer 相比,我很难理解 useState 何时以及为何具有优势。那里有很多争论,但对我来说,没有一个是有意义的,在这篇文章中,我试图将它们应用到一个简单的例子中。

也许我遗漏了一些东西,但我不明白为什么应该在 useReducer 的任何地方使用 useState 。我希望你能帮助我澄清这一点。

让我们看这个例子:

版本 A - 带有 useState

function CounterControls(props) {
  return (
    <>
      <button onClick={props.increment}>increment</button>
      <button onClick={props.decrement}>decrement</button>
    </>
  );
}

export default function App() {
  const [complexState, setComplexState] = useState({ nested: { deeply: 1 } });

  function increment() {
    setComplexState(state => {
      // do very complex logic here that depends on previous complexState
      state.nested.deeply += 1;
      return { ...state };
    });
  }

  function decrement() {
    setComplexState(state => {
      // do very complex logic here that depends on previous complexState
      state.nested.deeply -= 1;
      return { ...state };
    });
  }

  return (
    <div>
      <h1>{complexState.nested.deeply}</h1>
      <CounterControls increment={increment} decrement={decrement} />
    </div>
  );
}

看到这个 stackblitz

版本 B - 使用 useReducer

import React from "react";
import { useReducer } from "react";

function CounterControls(props) {
  return (
    <>
      <button onClick={() => props.dispatch({ type: "increment" })}>
        increment
      </button>
      <button onClick={() => props.dispatch({ type: "decrement" })}>
        decrement
      </button>
    </>
  );
}

export default function App() {
  const [complexState, dispatch] = useReducer(reducer, {
    nested: { deeply: 1 }
  });

  function reducer(state, action) {
    switch (action.type) {
      case "increment":
        state.nested.deeply += 1;
        return { ...state };
      case "decrement":
        state.nested.deeply -= 1;
        return { ...state };
      default:
        throw new Error();
    }
  }

  return (
    <div>
      <h1>{complexState.nested.deeply}</h1>
      <CounterControls dispatch={dispatch} />
    </div>
  );
}

看到这个 stackblitz

在很多文章(包括 docs )中,两个论点似乎很流行:

“useReducer 适用于复杂的状态逻辑”。 在我们的示例中,假设 complexState 很复杂,有很多修改操作,每个操作都有很多逻辑。 useReducer 在这里有什么帮助?对于复杂的状态,拥有单独的函数而不是拥有一个 200 行的 reducer 函数甚至不是 better 吗?

“如果下一个状态依赖于前一个状态,useReducer 就很好”。 我可以用 useState 做同样的事情,不是吗?只需写 setState(oldstate => {...})

潜在的其他优势:

  • “我不必传递多个函数,而只传递一个 reducer”:好的,但我也可以使用 useCallback 等将我的函数包装到一个“动作”对象中。正如已经提到的,在不同的函数中具有不同的逻辑似乎对我来说是件好事。
  • “我可以为 reducer 提供一个上下文,这样我的复杂状态就可以在整个应用程序中轻松修改”。是的,但是您也可以从该上下文中提供单个函数(可能由 useCallback 包装)

我看到的缺点:

  • 单个超长函数中的多个不同动作似乎令人困惑
  • 更容易出错,因为您必须检查 reducer 函数或依赖打字稿等来找出可以传递给 reducer 的字符串以及附带的参数。调用函数时,这要简单得多。

考虑到这一切:你能给我一个很好的例子,说明 useReducer 真的很出色,并且不能轻易地用 useState 重写为一个版本吗?

stack overflow What advantages does useReducer actually have over useState?
原文答案
author avatar

接受的答案

我相信这可能最终以意见论点。但是,从一篇简单的文章中提取的这种提取为我说话,因此,这里是指向整个文章的链接。

userEducer()是usestate()的替代方法,它使您对状态管理有了更多的控制,并且可以使测试更加容易。所有情况都可以使用usestate()方法来完成,因此总而言之,请使用您舒适的方法,并且对您和同事来说更容易理解。

参考。文章: [https://dev.to/spukas/3-reasons-to-usereducer-over-usestate-43ad#:~:text=useReducer()%20is%20an%20alternative,understand%20for%20you%20and%20colleagues](https://dev.to/spukas/3-reasons-to-usereducer-over-usestate-43ad#:%7E:text=useReducer() % 20is % 20an % 20 alternative,请理解% 20 for % 20you {jq =====} 20and {)。


答案:

作者头像

几个月后,我觉得我必须为这个话题添加一些见解。如果在 useReduceruseState 之间进行选择只是个人喜好问题,为什么人们会写这样的东西:

Dan Abramov 上的 twitter

useReducer 是真正的 Hooks 的作弊模式。一开始您可能不喜欢它,但它避免了在依赖于 useState 的类和组件中出现的大量潜在问题。了解 useReducer。

React docs

当您有涉及多个子值的复杂状态逻辑或下一个状态取决于前一个状态时,useReducer 通常比 useState 更可取。 useReducer 还允许您优化触发深度更新的组件的性能,因为您可以向下传递调度而不是回调。

React docs

我们建议在上下文中传递调度而不是在 props 中单独回调。

因此,让我们尝试确定并找到一个场景,其中 useReducer 明显优于 useState

如果需要从嵌套组件中的 useEffect 调用更新函数怎么办?

VersionA 的方法( useState & 传递回调)可能会遇到问题:

  • 出于语义和 linting 的原因,效果应该具有 update-function 作为依赖项。
  • 但是,这意味着每次重新声明更新函数时都会调用效果。在问题的示例“版本 A”中,这将出现在 App 的每次渲染中!
  • 在函数上调用 useCallback 会有所帮助,但这种模式很快就会变得乏味,尤其是当我们需要在 useMemo 对象上额外调用 actions 时。 (我也不是这方面的专家,但从性能的角度来看,这听起来不太令人信服)
  • 此外,如果函数具有经常更改的依赖项(如用户输入),即使 useCallback 也无济于事。

如果我们改用减速器:

  • reducer 的 dispatch 函数始终具有稳定的标识! (见 react docs
  • 这意味着,我们可以放心地在效果中使用它,知道它在正常情况下不会改变!即使 reducer-function 发生变化,dispatch 的标识保持不变并且不会触发效果。
  • 但是,当我们调用它时,我们仍然会得到最新版本的 reducer-function!

同样,参见 Dan Abramov 的 Twitter Post

并且“dispatch”身份始终是稳定的,即使 reducer 是内联的。因此,您可以依靠它进行性能优化,并将调度作为静态值免费传递给上下文。

实际例子

在这段代码中,我试图强调使用 useReducer 的一些优势,我之前试图描述过这些优势:

import React, { useEffect } from "react";
import { useState, useReducer } from "react";

function MyControls({ dispatch }) {
  // Cool, effect won't be called if reducer function changes.
  // dispatch is stable!
  // And still the up-to-date reducer will be used if we call it
  useEffect(() => {
    function onResize() {
      dispatch({ type: "set", text: "Resize" });
    }

    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, [dispatch]);

  return (
    <>
      <button onClick={() => dispatch({ type: "set", text: "ABC" })}>
        Set to "ABC"
      </button>
      <button onClick={() => dispatch({ type: "setToGlobalState" })}>
        Set to globalAppState
      </button>
      <div>Resize to set to "Resized"</div>
    </>
  );
}

function MyComponent(props) {
  const [headlineText, dispatch] = useReducer(reducer, "ABC");

  function reducer(state, action) {
    switch (action.type) {
      case "set":
        return action.text;
      case "setToGlobalState":
        // Cool, we can simply access props here. No dependencies
        // useCallbacks etc.
        return props.globalAppState;
      default:
        throw new Error();
    }
  }

  return (
    <div>
      <h1>{headlineText}</h1>
      <MyControls dispatch={dispatch} />
    </div>
  );
}

export default function App() {
  const [globalAppState, setGlobalAppState] = useState("");

  return (
    <div>
      global app state:{" "}
      <input
        value={globalAppState}
        onChange={(e) => setGlobalAppState(e.target.value)}
      />
      <MyComponent globalAppState={globalAppState} />
    </div>
  );
}

看到这个 codesandbox

  • 即使 reducer 函数在每次用户输入时都会发生变化,但 dispatch 的身份保持不变!不会触发效果
  • 每次调用它时,我们仍然会得到最新版本的函数!它可以完全访问组件的道具。
  • 无需记忆/使用回调等。在我看来,仅此一项就可以使代码更简洁,尤其是因为我们应该“依靠 useMemo 作为性能优化,而不是作为语义保证”( react docs