使用 Redux 开始进行状态管理
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
在众多用于管理 React 状态的库中,Redux 是最受欢迎的。但随之而来的,还有它学习曲线陡峭的名声。
在这篇文章中,我们将了解使用 Redux 创建一个简单的待办事项列表应用程序需要哪些步骤,并探索 Redux 提供的一些其他功能。
如果您想跟着做,我已经为本指南中创建的示例应用程序创建了一个存储库,网址为react-state-comparison。
本文假设读者已了解如何在 React 中渲染组件,并对 Hooks 的工作原理有基本的了解。此外,本文还假设读者已阅读本系列的前一篇文章,即关于useReducer 和 React Context 的文章,因为我们将在本文中对其进行一些比较。
安装 Redux
首先,我们需要安装这两个redux库react-redux。请使用以下任一命令(取决于您使用的包管理器):
yarn add redux react-redux
npm install redux react-redux
迅速进入状态
在本系列的前一篇文章中,我们使用 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;
}
}
};
如果你之前没见过类似的东西
state = initialState,这就是JavaScript 中的默认参数。这里我们说的是,如果state参数未定义,则使用initialState。
初始状态大致如下:
const initialState = {
listName: 'My new list',
tasks: {},
};
关于 reducer 的最后一点是,永远不要直接修改我们接收到的状态对象。例如,不要这样做:
state.listName = 'New list name';
我们需要应用在 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>
);
我们使用提供商来封装我们的应用程序。
要使用我们的商店,我们需要在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;
使用选择器获取数据
使用 `getState useReducer()`,我们总是获取整个状态对象,然后从中获取我们需要的内容(例如通过执行 `getState( state.tasks)`)。
在 Redux 中,我们使用选择器从 store 中仅获取所需的数据。
要从 store 中获取任务列表,您可以创建一个tasksSelector:
// src/redux/state/selectors
export const tasksSelector = (state) => state.tasks;
我们在钩子函数中使用这些选择器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;
为什么需要选择器?
如果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;
定义好 actions、reducer、store 和 selectors 之后,你的状态管理设置就完成了!
Redux 与 useReducer
现在我们又回到了上一篇文章中提到的那个阶段useReducer。你会注意到,我们编写的代码实际上并没有太大的区别。
随着应用程序规模的扩大,您将开始使用 Redux 提供的一些附加功能,而复杂性也正是在这里开始悄然增加。
将您的操作移至单独的文件
在大型应用程序中,您需要将操作定义为一个或多个单独的文件中的常量:
// src/redux-advanced/state/actions
export const UPDATE_LIST_NAME = 'UPDATE_LIST_NAME';
我们这样做的原因之一是,它可以防止您在引用操作时出现拼写错误。将所有操作集中在一个地方,可以更方便地查看代码库中的所有操作,也便于在创建新操作时遵循命名约定。
除了将动作定义为常量之外,还有动作创建器的概念。这些函数会为你创建动作:
export const updateListName = (name) => ({
type: UPDATE_LIST_NAME,
payload: { name }
});
它可以让你的代码简化成这样:
dispatch({ type: UPDATE_LIST_NAME, payload: { name } });
对此:
dispatch(updateListName(name));
定义操作和操作创建器可以使你的代码库更易于维护,但代价是需要编写额外的代码。
拆开你的减径器
随着应用功能的增加,reducer 文件也会越来越大。在某个阶段,你可能需要将其拆分成多个函数。
回到待办事项清单的例子,我们的商店包含listName:tasks
{
listName: 'My new list',
tasks: {},
}
我们可以将 reducer 分成一个用于 `for`listName和一个用于 `for` 两个子类tasks。listName用于 `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;
传递给上述函数的状态仅包含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;
连接功能
在如今的 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);
mapStateToProps
mapStateToProps它接收整个状态对象作为参数。使用选择器,您可以返回组件所需的任何值。在本例中,我们需要从 store 中获取列表名称值。该值将作为 prop 在Name组件中使用。
mapDispatchToProps
mapDispatchToProps它接受一个分发函数作为参数。利用它,我们可以定义一个用于分发 action 的函数。该函数也会作为 prop 在我们的Name组件中使用。mapDispatchToProps它还可以简化为以下简写形式:
const mapDispatchToProps = {
updateListName,
};
“视图”组件
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;
该组件不再需要担心分发操作或使用选择器,而是可以直接使用已传递给它的 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);
经过传递的组件mapStateToProps只有在需要时才会重新渲染。在我们的例子中,这意味着如果我们编辑某个任务,只有该任务会重新渲染。
如果您想了解更多关于 useSelector 与 Redux hooks 的优缺点connect(),我建议您阅读这篇关于useSelector 与 connect 的文章。
Redux 工具包
Redux 以冗长和大量样板代码而闻名。一个很好的例子就是如何定义 action 和 action creator。你从一行代码开始:
dispatch({ type: 'updateListName', payload: { name } });
超过五人:
// Actions file
export const UPDATE_LIST_NAME = 'UPDATE_LIST_NAME';
export const updateListName = (name) => ({
type: UPDATE_LIST_NAME,
payload: { name }
});
// Usage
dispatch(updateListName(name));
将操作和操作创建器定义在单独的文件中,可以简化 UI 代码并降低出错的可能性。但缺点是,每次想为应用添加新功能时,都需要预先编写更多代码。
Redux Toolkit是 Redux 为解决这些样板代码问题而推出的方案。它提供了一些实用的函数来简化你编写的代码。例如,它可以createAction将创建 action 的代码量减少到仅需两行:
// Defining your action
const updateListName = createAction('updateListName');
// Using your action
dispatch(updateListName({ name }));
要了解 Redux Toolkit 还提供了哪些其他功能,我建议查看他们的基础教程。
Redux DevTools 扩展
最后一点,Redux DevTools 扩展(可在 Chrome 和 Firefox 等浏览器上使用)是调试 React + Redux 应用的极其有用的工具。它可以让您实时查看:
- 当动作被触发时
- 这些处罚措施被执行后,您的商店会发生哪些变化?
如果你想用 Redux 开发应用,我强烈建议你了解一下。
结论
使用 Redux 构建待办事项应用与 React 的useReducerHook 非常相似。但是,如果您正在开发大型应用(或 Hook 出现之前的应用),您可能还需要了解类似 ` combineReducers()and` 这样的函数connect()。如果您希望减少样板代码,Redux Toolkit看起来是一个很有前景的选择,它可以减少您使用 Redux 入门所需的代码量。
我学习 Redux 的时间比较晚(实际上是和 React 同时学习的),虽然一开始理解起来有些困难,但我后来真的非常喜欢它!希望这篇文章能让大家更容易理解,如果还有任何疑问,请随时告诉我。
为了检验我们今天讨论过的代码,我创建了两个应用程序:
- Redux - 带有 Hooks 的 Redux
- redux-advanced - Redux with
connect()andcombineReducer()
感谢阅读!
文章来源:https://dev.to/emma/getting-started-with-state-management-using-redux-1acc