发布于 2026-01-06 7 阅读
0

使用 Redux 开始进行状态管理 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

使用 Redux 开始进行状态管理

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

在众多用于管理 React 状态的库中,Redux 是最受欢迎的。但随之而来的,还有它学习曲线陡峭的名声。

在这篇文章中,我们将了解使用 Redux 创建一个简单的待办事项列表应用程序需要哪些步骤,并探索 Redux 提供的一些其他功能。

如果您想跟着做,我已经为本指南中创建的示例应用程序创建了一个存储库,网址为react-state-comparison

本文假设读者已了解如何在 React 中渲染组件,并对 Hooks 的工作原理有基本的了解。此外,本文还假设读者已阅读本系列的前一篇文章,即关于useReducer 和 React Context 的文章,因为我们将在本文中对其进行一些比较。

安装 Redux

首先,我们需要安装这两个reduxreact-redux。请使用以下任一命令(取决于您使用的包管理器):

yarn add redux react-redux
npm install redux react-redux
Enter fullscreen mode Exit fullscreen mode

迅速进入状态

在本系列的前一篇文章中,我们使用 React Context 创建了一个待办事项应用useReducer,它允许我们:

  • 编辑待办事项列表的名称
  • 创建、编辑和删除任务

我们将在本文中重新创建同一个示例应用程序。

我们还介绍了 store、action 和 reducer 的概念。简单回顾一下:

  • 仓库是我们存储应用程序所有状态的中心位置
  • action负责通知 reducer 修改 store。我们从 UI 分发这些 action
  • reducer负责执行 action 指示它执行的操作(即对 store 进行必要的修改)。

定义你的 reducer

在 Redux 中定义 reducer 与定义 hook 非常相似useReducer。唯一的区别在于,在 Redux 中,我们还需要通过 reducer 传递应用程序的初始状态。

// src/redux/state/reducers

export const reducer = (state = initialState, action) => {
    const { listName, tasks } = state;
    switch (action.type) {
        case 'updateListName': {
            const { name } = action.payload;
            return { listName: name, tasks }
        }        
        default: {
            return state;
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

如果你之前没见过类似的东西state = initialState,这就是JavaScript 中的默认参数。这里我们说的是,如果state参数未定义,则使用initialState

初始状态大致如下:

const initialState = {
  listName: 'My new list',
  tasks: {},
};
Enter fullscreen mode Exit fullscreen mode

关于 reducer 的最后一点是,永远不要直接修改我们接收到的状态对象。例如,不要这样做:

state.listName = 'New list name';
Enter fullscreen mode Exit fullscreen mode

我们需要应用在 store 中的值发生变化时重新渲染,但如果直接修改状态对象,则无法实现这一点。随着 store 结构变得越来越复杂,可以使用像Immer这样的库来帮你解决这个问题。

创建并初始化我们的商店

接下来,您可以使用 reducer 创建 Redux store:

// src/redux/state/store

import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducer from '../reducers';

const store = createStore(reducer);

export const TasksProvider = ({ children }) => (
    <Provider store={store}>{children}</Provider>
);
Enter fullscreen mode Exit fullscreen mode

我们使用提供商来封装我们的应用程序。

要使用我们的商店,我们需要在src/redux/components文件夹中创建 React 应用,并将其包裹在TasksProvider

// src/redux/components
import React from 'react';
import { TasksProvider } from '../state/store';
import Name from './name';
import Tasks from './tasks';
import CreateTask from './create-task';

const ReduxApp = () => (
    <>
        <h2>Redux</h2>
        <TasksProvider>
            <Name />
            <Tasks />
            <CreateTask />
        </TasksProvider>
    </>
);

export default ReduxApp;
Enter fullscreen mode Exit fullscreen mode

使用选择器获取数据

使用 `getState useReducer()`,我们总是获取整个状态对象,然后从中获取我们需要的内容(例如通过执行 `getState( state.tasks)`)。

在 Redux 中,我们使用选择器从 store 中仅获取所需的数据。
要从 store 中获取任务列表,您可以创建一个tasksSelector

// src/redux/state/selectors
export const tasksSelector = (state) => state.tasks;
Enter fullscreen mode Exit fullscreen mode

我们在钩子函数中使用这些选择器useSelector

import React from 'react';
import { useSelector } from 'react-redux';
import { tasksSelector } from '../../state/selectors';
import TasksView from '../../../common/components/tasks';
import Task from '../task';

const Tasks = () => {
    const tasks = useSelector(tasksSelector);

    return <TasksView Task={Task} tasks={tasks} />;
};

export default Tasks;
Enter fullscreen mode Exit fullscreen mode

为什么需要选择器?

如果Tasks组件接收了整个state对象并通过获取任务数据,则每次状态的任何state.tasks部分发生变化时,React 都会重新渲染组件。Tasks

通过使用选择器,Tasks组件只会在数据发生变化时重新渲染state.tasks。例如,如果我们更改了列表的名称,组件将不再Tasks重新渲染。

派遣行动

分发 action 的方式也与我们使用 `.dispatch` 的方式非常相似useReducer。这里我们使用useDispatchhook 来分发 action。

// src/redux/components/name
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import NameView from '../../../common/components/name';
import { nameSelector } from '../../state/selectors';

const Name = () => {
    const dispatch = useDispatch();
    const listName = useSelector(nameSelector);

    const onSetName = (name) =>
        dispatch({ type: 'updateListName', payload: { name } });

    return <NameView name={listName} onSetName={onSetName} />;
};

export default Name;
Enter fullscreen mode Exit fullscreen mode

定义好 actions、reducer、store 和 selectors 之后,你的状态管理设置就完成了!

Redux 与 useReducer

现在我们又回到了上一篇文章中提到的那个阶段useReducer。你会注意到,我们编写的代码实际上并没有太大的区别。

随着应用程序规模的扩大,您将开始使用 Redux 提供的一些附加功能,而复杂性也正是在这里开始悄然增加。

将您的操作移至单独的文件

在大型应用程序中,您需要将操作定义为一个或多个单独的文件中的常量:

// src/redux-advanced/state/actions
export const UPDATE_LIST_NAME = 'UPDATE_LIST_NAME';
Enter fullscreen mode Exit fullscreen mode

我们这样做的原因之一是,它可以防止您在引用操作时出现拼写错误。将所有操作集中在一个地方,可以更方便地查看代码库中的所有操作,也便于在创建新操作时遵循命名约定。

除了将动作定义为常量之外,还有动作创建器的概念。这些函数会为你创建动作:

export const updateListName = (name) => ({
    type: UPDATE_LIST_NAME,
    payload: { name }
});
Enter fullscreen mode Exit fullscreen mode

它可以让你的代码简化成这样:

dispatch({ type: UPDATE_LIST_NAME, payload: { name } });
Enter fullscreen mode Exit fullscreen mode

对此:

dispatch(updateListName(name));
Enter fullscreen mode Exit fullscreen mode

定义操作和操作创建器可以使你的代码库更易于维护,但代价是需要编写额外的代码。

拆开你的减径器

随着应用功能的增加,reducer 文件也会越来越大。在某个阶段,你可能需要将其拆分成多个函数。

回到待办事项清单的例子,我们的商店包含listNametasks

{
    listName: 'My new list',
    tasks: {},
}
Enter fullscreen mode Exit fullscreen mode

我们可以将 reducer 分成一个用于 `for`listName和一个用于 `for` 两个子类taskslistName用于 `for` 的 reducer 如下所示:

// src/redux-advanced/state/reducers/list-name
import { UPDATE_LIST_NAME } from '../actions';

const initialState = 'Default name';

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case UPDATE_LIST_NAME: {
            const { name } = action.payload;
            return name;
        }

        default: {
            return state;
        }
    }
};

export default reducer;
Enter fullscreen mode Exit fullscreen mode

传递给上述函数的状态仅包含listName。我们还会为 创建一个单独的 reducer tasks
然后,我们使用以下函数将这两个 reducer 合并combineReducers

// src/redux-advanced/state/reducers

import { combineReducers } from 'redux';

import listNameReducer from './list-name';
import tasksReducer from './tasks';

const reducer = combineReducers(listNameReducer, tasksReducer);

export default reducer;
Enter fullscreen mode Exit fullscreen mode

连接功能

在如今的 Redux 中,你可以使用useDispatch`dispatch` 来分发 action,并useSelector使用 `get` 从 store 中获取数据。在 React Hooks 出现之前,所有 Redux 应用都使用一个名为 `get` 的函数connect

你可以将此connect函数封装在你的组件中,它会将参数(作为 props)传递给组件:

  • mapStateToProps你需要从选择器(使用)获取的数据
  • 将分发操作的函数(使用mapDispatchToProps

这里我们已经connect()Name组件进行了封装:

// src/redux-advanced/components/name/index.js

import { connect } from 'react-redux';
import { nameSelector } from '../../state/selectors';
import { updateListName } from '../../state/actions';
import Name from './view';

const mapStateToProps = (state) => ({
    name: nameSelector(state),
});

const mapDispatchToProps = (dispatch) => ({
    updateListName: (name) => dispatch(updateListName(name))
});

export default connect(mapStateToProps, mapDispatchToProps)(Name);
Enter fullscreen mode Exit fullscreen mode

mapStateToProps

mapStateToProps它接收整个状态对象作为参数。使用选择器,您可以返回组件所需的任何值。在本例中,我们需要从 store 中获取列表名称值。该值将作为 prop 在Name组件中使用。

mapDispatchToProps

mapDispatchToProps它接受一个分发函数作为参数。利用它,我们可以定义一个用于分发 action 的函数。该函数也会作为 prop 在我们的Name组件中使用。mapDispatchToProps它还可以简化为以下简写形式:

const mapDispatchToProps = {
    updateListName,
};
Enter fullscreen mode Exit fullscreen mode

“视图”组件

connect()它允许你将所有状态管理放在一个文件中,并允许你拥有一个“视图”文件,你只需专注于组件的渲染方式:

// src/redux-advanced/components/name/view.js

import React from 'react';
import NameView from '../../../common/components/name';

const Name = ({ name, updateListName }) =>
    <NameView name={name} onSetName={updateListName} />;

export default Name;
Enter fullscreen mode Exit fullscreen mode

该组件不再需要担心分发操作或使用选择器,而是可以直接使用已传递给它的 props。

connect() 函数现在还有用吗?

虽然现在有了 Hooks,但这并不意味着connect()旧方法就过时了。Hooks 不仅有助于将状态管理与“视图”组件分离,还能带来一些性能提升。

目前我们的Tasks组件:

  • 获取所有任务tasksSelector
  • 循环遍历每个Task组件以渲染它们

这意味着在使用 Redux hooks 时,如果您编辑一个任务,所有任务都会重新渲染。

通过这种方式connect(),你可以传递组件mapStateToProps。在我们的组件的 connect 函数中Tasks,我们可以传递以下信息Task

// src/react-advanced/components/tasks/index.js
import { connect } from 'react-redux';
import { tasksSelector } from '../../state/selectors';
import Task from '../task';
import Tasks from './view';

const mapStateToProps = (state) => ({
    Task,
    tasks: tasksSelector(state),
})

export default connect(mapStateToProps, null)(Tasks);
Enter fullscreen mode Exit fullscreen mode

经过传递的组件mapStateToProps只有在需要时才会重新渲染。在我们的例子中,这意味着如果我们编辑某个任务,只有该任务会重新渲染。

如果您想了解更多关于 useSelector 与 Redux hooks 的优缺点connect(),我建议您阅读这篇关于useSelector 与 connect 的文章。

Redux 工具包

Redux 以冗长和大量样板代码而闻名。一个很好的例子就是如何定义 action 和 action creator。你从一行代码开始:

dispatch({ type: 'updateListName', payload: { name } });
Enter fullscreen mode Exit fullscreen mode

超过五人:

// Actions file
export const UPDATE_LIST_NAME = 'UPDATE_LIST_NAME';

export const updateListName = (name) => ({
    type: UPDATE_LIST_NAME,
    payload: { name }
});

// Usage
dispatch(updateListName(name));
Enter fullscreen mode Exit fullscreen mode

将操作和操作创建器定义在单独的文件中,可以简化 UI 代码并降低出错的可能性。但缺点是,每次想为应用添加新功能时,都需要预先编写更多代码。

Redux Toolkit是 Redux 为解决这些样板代码问题而推出的方案。它提供了一些实用的函数来简化你编写的代码。例如,它可以createAction将创建 action 的代码量减少到仅需两行:

// Defining your action
const updateListName = createAction('updateListName');

// Using your action
dispatch(updateListName({ name }));
Enter fullscreen mode Exit fullscreen mode

要了解 Redux Toolkit 还提供了哪些其他功能,我建议查看他们的基础教程

Redux DevTools 扩展

最后一点,Redux DevTools 扩展(可在 Chrome 和 Firefox 等浏览器上使用)是调试 React + Redux 应用的极其有用的工具。它可以让您实时查看:

  • 当动作被触发时
  • 这些处罚措施被执行后,您的商店会发生哪些变化?

如果你想用 Redux 开发应用,我强烈建议你了解一下。

结论

使用 Redux 构建待办事项应用与 React 的useReducerHook 非常相似。但是,如果您正在开发大型应用(或 Hook 出现之前的应用),您可能还需要了解类似 ` combineReducers()and` 这样的函数connect()。如果您希望减少样板代码,Redux Toolkit看起来是一个很有前景的选择,它可以减少您使用 Redux 入门所需的代码量。

我学习 Redux 的时间比较晚(实际上是和 React 同时学习的),虽然一开始理解起来有些困难,但我后来真的非常喜欢它!希望这篇文章能让大家更容易理解,如果还有任何疑问,请随时告诉我。

为了检验我们今天讨论过的代码,我创建了两个应用程序:

感谢阅读!

文章来源:https://dev.to/emma/getting-started-with-state-management-using-redux-1acc