如何使用 useReducer 和 useContext hooks 管理 React 应用中的状态
在 React 应用中选择一个合适的全局状态管理库来管理和处理全局状态可能既棘手又耗时。这很大程度上取决于 React 应用的规模,而且有很多选项可供选择。
随着 React Hooks API 的引入,一种可行的方案是将 Hook 和 Context API 结合使用useReducer。本文将探讨如何使用这两者来管理 React 应用中的全局状态。
先决条件
要充分利用本教程或跟随示例运行,请确保您已在本地开发环境中安装/访问以下工具。
- Node.js 版本 >=
12.x.x已安装 - 可以使用一个软件包管理器,例如
npm或yarn create-react-app已安装或使用 clinpx- React Hooks基础知识
如果您还不熟悉 React Hooks,我建议您阅读这篇关于 React Hooks 的深入文章。
使用 useReducer 在 React 应用中进行状态管理
React 应用中需要处理两种类型的状态。第一种是局部状态,仅在 React 组件内部使用。第二种是全局状态,可以在 React 应用内的多个组件之间共享。
随着 Context API 和 Hooks API 的发布,无需安装任何额外的状态管理库即可实现全局状态。HooksuseReducer是管理复杂状态对象和状态转换的绝佳方式。您可能已经在 React 应用中见过或使用过它useState来管理简单的或局部状态。
这个useReducer钩子函数与 `reducer` 不同useState。它的主要优势在于useState,它能够处理复杂的数据结构或包含多个值的状态对象。它通过接收一个 reducer 函数和一个初始状态来更新状态。然后,它返回实际状态和一个 dispatch 函数。这个 dispatch 函数用于对状态进行修改。
创建一个新的 React 应用并安装依赖项
首先,在终端窗口中执行以下命令创建一个新的 React 项目:
npx create-react-app react-expense-tracker
cd react-expense-tracker
为了专注于本教程的主题,并使演示应用拥有美观的界面,我们将使用Reactstrap的预定义组件。它提供了基于 Flexbox 的 Bootstrap 4 组件,可用于处理 Web 应用的布局。要开始在 React 应用中使用 Bootstrap,请安装以下依赖项:
yarn add bootstrap@4.5.0 reactstrap@8.5.1 uuid@8.2.0
安装完这些依赖项后,打开你创建的 React 项目并打开文件index.js。添加一条导入语句以引入 Bootstrap CSS 文件。
// after other imports
import 'bootstrap/dist/css/bootstrap.min.css';
至此,在当前的 React 应用中设置 Bootstrap 就完成了。
定义全局状态
GlobalState.js首先在目录内创建一个名为 的新文件src/。
让我们使用 React 的 Context API 创建一个 Context 提供程序,以便在多个组件之间共享状态。您可以将此示例理解为模仿 Redux 的理念。导入所需的语句。
import React, { useReducer, createContext } from 'react';
import { v4 as uuid } from 'uuid';
接下来,创建一个空的“费用”上下文,并定义一个初始状态对象。该初始状态将包含一个费用项。这也有助于为所有其他费用项定义模式或数据模型(但请注意,这仅用于本文的演示目的)。
export const ExpenseContext = createContext();
const initialState = {
expenses: [
{
id: uuid(),
name: 'Buy Milk',
amount: 10
}
]
};
然后定义一个名为 `reducer` 的函数reducer。它将接受两个参数:当前状态和操作。这个 reducer 的作用是,当用户在应用中执行操作时,修改或更新状态对象。例如,用户添加支出就是一个操作。
在以下示例中,此reducer函数将只有一个操作类型,即添加费用。如果没有更改或修改,此reducer函数将返回当前状态(这是默认情况)。
const reducer = (state, action) => {
switch (action.type) {
case 'ADD_EXPENSE':
return {
expenses: [...state.expenses, action.payload]
};
default:
return {
state
};
}
};
接下来,定义一个ExpenseContextProvider行为类似于 store 的对象(就像Redux中的 store 一样)。
export const ExpenseContextProvider = props => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<ExpenseContext.Provider value={[state, dispatch]}>
{props.children}
</ExpenseContext.Provider>
);
};
这个useReducer钩子允许我们使用之前定义的函数创建一个 reducer reducer。它initialState作为第二个参数传递。
使用提供商包装应用程序
当ExpenseContextProviderReact 应用中的任何组件被包裹时,该组件及其子组件将能够访问当前状态并修改状态对象。
在本节中,我们将App.js按照以下步骤操作。打开文件,然后进行修改。
import React from 'react';
import { Container } from 'reactstrap';
import { ExpenseContextProvider } from './GlobalState';
import Header from './components/Header';
import Form from './components/Form';
import List from './components/List';
export default function App() {
return (
<ExpenseContextProvider>
<Container className="text-center">
<Header />
<Form />
<List />
</Container>
</ExpenseContextProvider>
);
}
在接下来的章节中,我们将创建该组件的子组件App。创建一个components/目录,然后创建三个新的组件文件:
Header.jsForm.jsList.js
添加应用标题
在本节中,我们将定义一个名为 `<prepresentational component>` 的展示组件Header。它将是 Bootstrap 中的一个简单巨幕组件,用于显示应用程序的标题和徽标。
打开Header.js并添加以下代码片段:
import React from 'react';
import { Jumbotron } from 'reactstrap';
import Logo from '../logo.svg';
export default function Headers() {
return (
<Jumbotron fluid>
<h3 className="display-6">
Expense Tracker React App
<img src={Logo} style={{ width: 50, height: 50 }} alt="react-logo" />
</h3>
</Jumbotron>
);
}
添加表单组件
打开Form.js文件并导入以下语句。
import React, { useState, useContext } from 'react';
import {
Form as BTForm,
FormGroup,
Input,
Label,
Col,
Button
} from 'reactstrap';
import { v4 as uuid } from 'uuid';
import { ExpenseContext } from '../GlobalState';
该uuid模块将为全局状态中的每个费用项目生成一个唯一的 ID。
定义一个Form将要ExpenseContext使用useContexthook 访问值的组件。
export default function Form() {
const [state, dispatch] = useContext(ExpenseContext);
//...
}
使用useStatereducer,定义两个仅在该组件中生效的状态变量。这些状态变量将帮助我们定义受控输入字段。受控输入字段接受一个 prop 来传递其当前值,并接受一个回调函数来更改该值。
name使用和amount添加以下初始状态useState。它们的初始值都将为空字符串。
const [name, setName] = useState('');
const [amount, setAmount] = useState('');
为了在用户开始输入时更新字段值,请添加以下处理方法。这两个函数都会从相应的字段中检索值。控制台语句仅用于测试目的。
const handleName = event => {
console.log('Name ', event.target.value);
setName(event.target.value);
};
const handleAmount = event => {
console.log('Amount ', event.target.value);
setAmount(event.target.value);
};
最后,要提交表单,还需要调用另一个处理方法handleSubmitForm。该方法触发后,会派发添加费用的操作ADD_EXPENSE。这就是reducer全局状态中的函数更新状态的方式。
const handleSubmitForm = event => {
event.preventDefault();
if (name !== '' && amount > 0) {
dispatch({
type: 'ADD_EXPENSE',
payload: { id: uuid(), name, amount }
});
// clean input fields
setName('');
setAmount('');
} else {
console.log('Invalid expense name or the amount');
}
};
最后,添加以下 JSX 代码来显示组件。
return (
<BTForm style={{ margin: 10 }} onSubmit={handleSubmitForm}>
<FormGroup className="row">
<Label for="exampleEmail" sm={2}>
Name of Expense
</Label>
<Col sm={4}>
<Input
type="text"
name="name"
id="expenseName"
placeholder="Name of expense?"
value={name}
onChange={handleName}
/>
</Col>
</FormGroup>
<FormGroup className="row">
<Label for="exampleEmail" sm={2}>
Amount
</Label>
<Col sm={4}>
<Input
type="number"
name="amount"
id="expenseAmount"
placeholder="$ 0"
value={amount}
onChange={handleAmount}
/>
</Col>
</FormGroup>
<Button type="submit" color="primary">
Add
</Button>
</BTForm>
);
显示项目列表
在本节中,我们将添加一个List.js组件,用于显示当前状态对象中的项目列表ExpenseContext。打开文件并添加以下导入语句:
import React, { useContext } from 'react';
import { ListGroup, ListGroupItem } from 'reactstrap';
import { ExpenseContext } from '../GlobalState';
接下来,将该state值映射为显示费用名称和费用金额的列表项。
export default function List() {
const [state] = useContext(ExpenseContext);
return (
<ListGroup>
{state.expenses.map(item => {
return (
<ListGroupItem key={item.id}>
{item.name} - $ {item.amount}
</ListGroupItem>
);
})}
</ListGroup>
);
}
运行应用程序
简易费用追踪器应用的所有组件都已完成。现在,让我们运行该应用并查看其运行效果。初始渲染时,Rect 应用界面如下所示。
它会显示初始状态下定义为对象的一个费用项目。尝试在列表中添加一个新项目,看看列表是否会更新,表单是否会被清空。
结论
结合 React 的 Context API使用useReducerContext API 可以快速上手管理状态。但是,React 的 Context API 也存在一些问题。不必要地重新渲染多个组件可能会造成严重问题,需要格外注意。
最初发布于amanhimself.dev。
文章来源:https://dev.to/amanhimself/how-to-manage-state-in-react-apps-with-usereducer-and-usecontext-hooks-4eoc

