使用 Hooks + Context,而不是 React + Redux

2025-05-25

使用 Hooks + Context,而不是 React + Redux

作者:Ebenezer Don✏️

Redux 需要大量的代码,这给我们的代码库带来了极大的复杂性。这充其量只能算作 React 应用状态管理的一个不完美的解决方案。然而,太多的 React 开发者默认使用 Redux 进行状态管理,而没有考虑其他替代方案。

在本文中,我将介绍用于状态管理的 React Context API,并向您展示为什么 React Hooks 加上 Context API 比 Redux 更好。

为什么我们需要状态管理工具

在典型的 React 中,处理不相连组件间数据的方式是通过 prop 传递。由于组件无法访问全局状态,例如,如果你想将数据从顶层组件传递到第五层组件,你就必须在树的每一层都以 prop 的形式传递数据,直到到达所需的组件。

这会导致编写大量额外的代码,并且赋予组件一些它们永远不会使用的属性,从而影响其架构设计。为了解决这个问题,我们需要一种方法来提供一个全局状态,使所有组件(无论其嵌套深度如何)都可以访问。

通过解决这个问题,用于管理应用程序状态的开源 JavaScript 库 Redux 成为了 React 开发人员的首选解决方案。

LogRocket 免费试用横幅

Redux 的工作原理

Redux 文档其描述为 JavaScript 应用程序的可预测状态容器,可帮助我们编写行为一致、在不同环境中运行且易于测试的应用程序。

prop 钻取的一个缺点是需要编写大量额外的代码才能从顶级组件访问数据。对于 Redux 来说,这个缺点更加明显,因为设置全局状态所需的额外代码带来了极大的复杂性。Redux 的运行需要三个主要构建部分:action、reducer 和 store。

行动

这些对象用于将数据发送到 Redux Store。它们通常具有两个属性:一个 type 属性,用于描述操作的作用;一个 payload 属性,用于包含应在应用状态中更改的信息。

// action.js
const reduxAction = payload => {
  return {
    type: 'action description',
    payload
  }
};

export default reduxAction;
Enter fullscreen mode Exit fullscreen mode

通常全部大写type,单词之间用下划线分隔。例如,SIGNUP_USERDELETE_USER_DATA

Reducers

这些是实现动作行为的纯函数。它们获取当前应用程序状态,执行动作,然后返回新状态:

const reducer = (state, action) => {
  const { type, payload } = action;
  switch(type){
    case "action type":
      return {
        ["action description"]: payload
      };
    default:
      return state;
  }
};

export default reducer;
Enter fullscreen mode Exit fullscreen mode

店铺

Store 是应用程序状态的存放地。任何 Redux 应用程序中只有一个 Store:

import { createStore } from 'redux'

const store = createStore(componentName);
Enter fullscreen mode Exit fullscreen mode

由于我们的应用程序只能有一个 Redux 存储,为了为所有组件创建单个根减速器,我们需要combineReducersRedux 中的方法。

设置 Redux 需要耗费大量时间,而且代码量巨大,想象一下,当我们需要处理多个组件时,代码库会是什么样子。虽然 Redux 解决了我们的状态管理问题,但它使用起来非常耗时,学习曲线也很陡峭,并且给我们的应用程序带来了全新的复杂性。

幸运的是,React Context API 解决了这个问题。与 React Hooks 结合使用,我们就能获得一个状态管理解决方案,它设置起来更省时,学习曲线也更平滑,并且只需要极少的代码。

React Context API

React 16.3 引入了新的 Context API。React文档中对 Context 的解释如下

Context 提供了一种通过组件树传递数据的方法,而无需在每个级别手动传递 props。

React 上下文 API 是 React 管理多个不直接连接的组件中的状态的方式。

为了创建上下文,我们将使用createContextReact 中的方法,该方法接受一个参数作为其默认值:

import React from 'react';

const newContext = React.createContext({ color: 'black' });
Enter fullscreen mode Exit fullscreen mode

createContext方法返回一个带有ProviderConsumer组件的对象:

const { Provider, Consumer } = newContext;
Enter fullscreen mode Exit fullscreen mode

组件Provider负责将状态提供给所有子组件,无论它们在组件层次结构中的嵌套深度如何。Provider组件接收一个valueprop。我们将当前值传递到这里:

<Provider value={color: 'blue'}>
  {children}
</Provider>
Enter fullscreen mode Exit fullscreen mode

Consumer正如其名称所暗示的那样,使用来自的数据,而Provider无需任何道具钻孔:

<Consumer>
  {value => <span>{value}</span>}}
</Consumer>
Enter fullscreen mode Exit fullscreen mode

如果没有 Hooks,Context API 与 Redux 相比可能看起来没什么,但结合useReducerHook,我们有一个最终解决状态管理问题的解决方案。

React 中的 Hooks 是什么?

Hooks 是一种允许在基础代码中执行自定义代码的函数。在 React 中,Hooks 是一种特殊的函数,允许我们“钩住”其核心功能。

React Hooks 允许我们轻松处理来自功能组件的状态管理,从而为编写基于类的组件提供了一种替代方法。

钩子useContext

如果你注意到,在解释 React Context API 时,我们需要将内容包装在一个Consumer组件中,然后传递一个函数作为子函数,以便访问(或使用)我们的状态。这引入了不必要的组件嵌套,并增加了代码的复杂性。

HookuseContext让事情变得更加简洁明了。为了使用它访问我们的状态,我们只需要调用它并将 createdcontext作为参数即可:

const newContext = React.createContext({ color: 'black' });

const value = useContext(newContext);

console.log(value); // this will return { color: 'black' }
Enter fullscreen mode Exit fullscreen mode

现在,我们无需将内容包装在Consumer组件中,而是可以通过变量简单地访问我们的状态value

钩子useReducer

HookuseReducer是 React 16.7.0 中引入的。与 JavaScript 中的方法类似reduce()useReducerHook 接收两个值作为参数(在本例中为当前状态和一个动作),然后返回一个新状态:

const [state, dispatch] = useReducer((state, action) => {
  const { type } = action;
  switch(action) {
    case 'action description':
      const newState = // do something with the action
      return newState;
    default:
      throw new Error()
  }
}, []);
Enter fullscreen mode Exit fullscreen mode

在上面的块中,我们定义了状态以及相应的方法dispatch来处理它。当我们调用该dispatch方法时,useReducer()Hook 将根据type该方法在其 action 参数中接收到的 执行相应的操作:

...
return (
  <button onClick={() =>
    dispatch({ type: 'action type'})}>
  </button>
)
Enter fullscreen mode Exit fullscreen mode

HookuseReducer加上 Context API

设置我们的商店

现在我们已经了解了 Context API 和useReducerHook 各自的工作原理,接下来看看将它们组合起来会发生什么,以便为我们的应用程序获得理想的全局状态管理解决方案。我们将在一个文件中创建全局状态store.js

// store.js
import React, {createContext, useReducer} from 'react';

const initialState = {};
const store = createContext(initialState);
const { Provider } = store;

const StateProvider = ( { children } ) => {
  const [state, dispatch] = useReducer((state, action) => {
    switch(action.type) {
      case 'action description':
        const newState = // do something with the action
        return newState;
      default:
        throw new Error();
    };
  }, initialState);

  return <Provider value={{ state, dispatch }}>{children}</Provider>;
};

export { store, StateProvider }
Enter fullscreen mode Exit fullscreen mode

store.js在我们的文件中,我们使用了之前解释过的createContext()from 方法React来创建一个新的上下文。记住,该createContext()方法返回一个带有ProviderConsumer组件的对象。这次,我们只使用Provider组件,然后useContext在需要访问状态时使用 Hook。

注意我们是如何useReducer在 中使用 Hook的StateProvider。当需要操作状态时,我们将调用该dispatch方法并传入一个包含所需状态的对象type作为参数。

在我们的中StateProvider,我们Provider通过Hookvalue的 propstatedispatch返回了我们的组件useReducer

在全球范围内访问我们的状态

为了全局访问我们的状态,我们需要在我们的函数中渲染之前将我们的根<App/>组件包装在我们的中StoreProviderReactDOM.render()

// root index.js file
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { StateProvider } from './store.js';

const app = (
  <StateProvider>
    <App />
  </StateProvider>
);

ReactDOM.render(app, document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

现在,context组件树中的任何组件都可以访问我们的 store 了。为此,我们将从文件useContext中导入 Hookreactstore./store.js

// exampleComponent.js
import React, { useContext } from 'react';
import { store } from './store.js';

const ExampleComponent = () => {
  const globalState = useContext(store);
  console.log(globalState); // this will return { color: red }
};
Enter fullscreen mode Exit fullscreen mode

添加和删​​除我们的状态数据

我们已经了解了如何访问全局状态。为了在状态中添加和删除数据,我们需要dispatch上下文中的方法store。我们只需要调用该dispatch方法并传入一个对象type(组件中定义的动作描述StateProvider)作为参数:

// exampleComponent.js
import React, { useContext } from 'react';
import { store } from './store.js';

const ExampleComponent = () => {
  const globalState = useContext(store);
  const { dispatch } = globalState;

  dispatch({ type: 'action description' })
};
Enter fullscreen mode Exit fullscreen mode

结论

在很大程度上,Redux 适用于 React 应用程序中的状态管理,并且具有一些优点,但它的冗长性使得它很难掌握,并且在我们的应用程序中运行它所需的大量额外代码引入了很多不必要的复杂性。

另一方面,有了useContextAPI 和 React Hooks,我们无需安装外部库或添加大量文件和文件夹即可运行应用。这使得在 React 应用程序中处理全局状态管理变得更加简单、直接。


编者注:觉得这篇文章有什么问题?您可以在这里找到正确版本

插件:LogRocket,一个用于 Web 应用的 DVR

 
LogRocket 仪表板免费试用横幅
 
LogRocket是一款前端日志工具,可让您重放问题,就像它们发生在您自己的浏览器中一样。您无需猜测错误发生的原因,也无需要求用户提供屏幕截图和日志转储,LogRocket 允许您重放会话以快速了解问题所在。它可与任何应用程序完美兼容,无论使用哪种框架,并且提供插件来记录来自 Redux、Vuex 和 @ngrx/store 的更多上下文。
 
除了记录 Redux 操作和状态之外,LogRocket 还记录控制台日志、JavaScript 错误、堆栈跟踪、带有标头 + 正文的网络请求/响应、浏览器元数据以及自定义日志。它还会对 DOM 进行插桩,以记录页面上的 HTML 和 CSS,即使是最复杂的单页应用程序,也能重现像素完美的视频。
 
免费试用


文章使用 Hooks + Context,而不是 React + Redux最先出现在LogRocket 博客上。

文章来源:https://dev.to/bnevilleoneill/use-hooks-context-not-react-redux-1j08
PREV
纯粹而简单 - 使用 Javascript 的井字游戏
NEXT
如何像专业人士一样使用 CSS 变量