何时使用原生的React.useReducer Hook,它与Redux有何不同?

76

所以,Hooks从React 16.8开始提供。从它们的文档中可以看出,Hooks是用于在函数组件中替代state的。基本的hooks有:useStateuseEffectuseContext,但还有其他一些额外的hooks,其中之一就是useReducer,它似乎使用了与Redux相同的action-dispatch架构。

问题是,因为类似,Hooks是否成为Redux的替代品?

它适合特定的项目吗?

它适用于哪些情况?


8
Redux和useReducer都是全局状态和局部状态的实现方式。useReducer的显著区别在于它不涉及中间件。如果你需要从useReducer中获取Redux的功能,那么你最终可能需要完全从头开始编写它。 - Estus Flask
2
也许这篇文章可以为这个话题提供一些启示:https://www.robinwieruch.de/redux-vs-usereducer/ - Robin Wieruch
4个回答

79

Redux是一个鼓励特定数据流程的库。

另一方面,react-redux 实现了 React 友好的方法并提供了许多中间件和包装器,以便库使用者不必自己设置整个过程。

虽然 useReducer 是 Redux 工作中的一部分,但它并不是完整的 Redux。为了能够在组件的深层使用 dispatch 和 state,您仍然需要结合使用 useContextuseReducer,这就像重新发明轮子。

此外,useReducer 只提供了一个 dispatch 方法,您可以使用它来分派纯对象作为操作。目前还没有办法添加像 thunksaga 等中间件。

您还可以在应用程序中使用多个 reducers,但是将它们组合成一个单一的 store 的方式仍然需要由开发人员处理。

React 文档 还指出,当状态逻辑复杂时,useReducer 是替代 useState 的选择。

  

useReducer 通常比 useState 更可取,当您有涉及多个子值的复杂状态逻辑或下一个状态与先前状态无关时。

useContextuseReducer这样的钩子可以消除小型应用程序对Redux的依赖关系。此外,useReducer还让您可以传递分派而不是回调函数,以优化触发深度更新的组件性能。

此外,useReducer 只是提供了一个 dispatch 方法,您可以使用它来分派普通的对象作为操作。目前还没有办法添加 middlewares,例如 thunksaga 等等。- 谢谢 - sledgeweight
1
useReducer 有什么好的使用案例吗?乍一看,它似乎有点过于复杂,仅用于本地状态。 - Robo Robok

24

因此,如果要比较Redux和useReducer

Redux:

  • 中心化状态
  • 更多的解耦合
  • 有中间件:Redux thunk和Redux logger
  • 操作只能命中一个Store
  • 也许更适合大型项目

useReducer:

  • 本地状态
  • 没有包装器组件
  • 需要useContext来重新发明轮子
  • 附带其他本机Hooks
  • 不需要额外的依赖项
  • 可以拥有多个Reducer作为Store
  • 也许更适合小型项目

24
1.) useReducer是否可以替代Redux?
让我们先澄清Redux是什么,这样我们才能将它的主要部分与使用useReducer的纯React解决方案进行比较: enter image description here(灵感来自于this article
如何用纯React替换所有部分
动作调度器/状态选择器
这是React Redux所做的替代品:
import { useReducer, useContext, useMemo } from "react"
import { rootReducer } from "./reducers"

const GlobalContext = React.createContext()

const Provider = ({ children }) => {
  const [state, dispatch] = useReducer(rootReducer, { count: 0 });
  const store = useMemo(() => [state, dispatch], [state]);

  // You can also separate dispatch and state context providers
  return (
    <GlobalContext.Provider value={store}>{children}</GlobalContext.Provider>
  );
};

// Provider is placed at top-level to emulate a global state store
ReactDOM.render(<Provider> <App /> </Provider>, document.getElementById('root'))

const Comp = () => {
  // extract this UI logic in its own custom hook for better encapsulation
  const [state, dispatch] = useContext(GlobalContext);
  // ...
};

数据存储

Redux store可以灵活地实例化(createStore)和访问。在原生React中,store绑定到UI中的单个useReducer。我们可以通过上下文或props将其状态向下传递。

Store Reducer

与Redux一样,完全相同的纯rootReducer函数用于原生React。

中间件API

redux-thunkredux-saga是Redux中最流行的两个middlewares,用于异步操作和副作用。使用useReducer时,我们没有任何内置的中间件API。相反,我们首先进行异步处理,然后将结果转发给dispatch

const [state, dispatch] = useContext(GlobalContext);
// ...
<button onClick={() => dispatchAsync(dispatch)}> Process </button>

const dispatchAsync = dispatch => {
  fetchData().then(data => dispatch({type: "increment"}, data)) 
};

只要现有的Redux中间件涵盖了公共API,就仍然可以将其与useReducer集成 - 您可以查看这个答案获取更多信息。

Redux开发工具(时间旅行,调试支持)

useReducer没有直接的集成方式,因此您可能会错过一个重要的工作流工具。 reinspect库使用Redux DevTools来检查useStateuseReducer(尚未经过测试)。


2.) 它适用于特定的项目吗?它适用于哪些项目? useReducer 的使用始于本地状态和组件范围。正如您所见,它也可以提升到全局范围,接管大部分 Redux 的角色。
在某些情况下,useReducer 甚至可以提供更多的灵活性,因为全局状态可以在多个上下文之间划分。例如:在不同的状态树/上下文中分离低优先级和高优先级状态更改。
使用原始的 React,您将主要错过 Redux DevTools 和流行的中间件库。另一方面,像 combineReducers 这样的函数可以很容易地重新实现为 useReducer,就像 这里 所示。
经验法则是:开始使用原始的 React 构建您的应用程序,并逐步添加功能,例如全局 Redux 存储,一旦您感觉 这变得必要(还取决于应用程序大小)。

const GlobalContext = React.createContext();

const Provider = ({ children }) => {
  const [state, dispatch] = useReducer(rootReducer, { count: 0 });
  const store = useMemo(() => [state, dispatch], [state]);

  // You can also separate dispatch and state context providers
  return (
    <GlobalContext.Provider value={store}>{children}</GlobalContext.Provider>
  );
};

const rootReducer = (state, action) =>
  action === "increment" ? { count: state.count + 1 } : state;

const dispatchAsync = dispatch => {
  // just do the async operation before and invoke dispatch afterwards
  setTimeout(() => dispatch("increment"), 1000);
};

const Comp = () => {
  // You can extract this UI logic in its own custom hook for better encapsulation
  const [state, dispatch] = useContext(GlobalContext);

  return (
    <div>
      <div>Counter: {state.count}</div>
      <button onClick={() => dispatchAsync(dispatch)}>
        Increment async (1sec delay)
      </button>
    </div>
  );
};

ReactDOM.render(<Provider><Comp /></Provider>, 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>
<script>var { useReducer, useContext, useMemo } = React</script>


3
好的,这应该被接受为正确答案!干得好! - Michel Engelen
上下文将重新渲染所有子组件,这不是一个问题吗?Redux 不会重新渲染子组件。 - mestarted
在上面的代码片段中,我们在Provider内部有一个稳定的children引用,因此它永远不会在上下文值更改时重新渲染(还假设Provider放置在组件层次结构的顶部/根部以进行无/少量更新)。虽然你是对的,在更大的应用程序中,性能是应该在这些概念之间仔细评估的一个因素。 - ford04

15

useReducer 的状态是局限于单个组件的 - 如果你想在整个应用程序中使用这个状态,你需要通过props将其 (或者 dispatch 函数) 传递下去。实际上,它只是更有结构的 useState 的一个版本 - 事实上,useState 在幕后使用了 useReducer!

另一方面,Redux 做了更多的事情 - 其中之一就是通过上下文(Context)使状态在整个应用程序中可用,并提供 API 将您深度嵌套的组件连接到此状态,而无需传递props。

换句话说:

  • useReducer 提供了结构化的本地状态更新。
  • Redux 提供了结构化和集中化的状态更新。

如果你想用 Hooks 来实现自己的 Redux,你需要使用一些组合使用 useReduceruseContext 的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接