ReactJS 教程

ReactJS 主页 ReactJS 简介 ReactJS 路线图 ReactJS 下载安装和环境配置 ReactJS 功能 ReactJS 优势和缺点 ReactJS 架构 ReactJS 创建 React 应用程序 ReactJS JSX ReactJS 组件 ReactJS 嵌套组件 ReactJS 使用组件 ReactJS 集合组件 ReactJS 样式 ReactJS 属性 (props) ReactJS 使用属性创建组件 ReactJS props 验证 ReactJS 构造函数 ReactJS 组件生命周期 ReactJS 事件管理 ReactJS 创建事件感知组件 ReactJS Expense Manager 事件 ReactJS 状态管理 ReactJS 状态管理 API ReactJS 无状态组件 ReactJS Hooks 进行状态管理 ReactJS Hooks 的组件生命周期 ReactJS 布局组件 ReactJS 分页 ReactJS Material UI ReactJS Http 客户端编程 ReactJS 表单编程 ReactJS 受控组件 ReactJS 非受控组件 ReactJS Formik ReactJS 条件渲染 ReactJS 列表 ReactJS Key 键 ReactJS 路由 ReactJS Redux ReactJS 动画 ReactJS Bootstrap ReactJS Map ReactJS 表格 ReactJS 使用 Flux 管理状态 ReactJS 测试 ReactJS CLI 命令 ReactJS 构建和部署 ReactJS 示例

Hooks

ReactJS Hooks 简介 ReactJS 使用 useState ReactJS 使用 useEffect ReactJS 使用 useContext ReactJS 使用 useRef ReactJS 使用 useReducer ReactJS 使用 useCallback ReactJS 使用 useMemo ReactJS 自定义 Hooks

ReactJS 高级

ReactJS 可访问性 ReactJS 代码拆分 ReactJS 上下文 ReactJS 错误边界 ReactJS 转发 Refs ReactJS 片段 ReactJS 高阶组件 ReactJS 与其他库集成 ReactJS 优化性能 ReactJS Profiler API ReactJS Portals ReactJS 不使用 ES6 ECMAScript ReactJS 不使用 JSX 的 React ReactJS Reconciliation ReactJS Refs 和 DOM ReactJS 渲染道具 ReactJS 静态类型检查 ReactJS 严格模式 ReactJS Web 组件

其他概念

ReactJS 日期选择器 ReactJS Helmet ReactJS 内联样式 ReactJS PropTypes ReactJS BrowserRouter ReactJS DOM ReactJS 轮播 ReactJS 图标 ReactJS 表单组件 ReactJS 参考 API

ReactJS 有用资源

ReactJS 快速指南 ReactJS 备忘录 Axios 备忘录 ReactJS 有用资源 ReactJS 讨论


ReactJS - Redux

React redux 是 React 的高级状态管理库。正如我们之前所了解的,React 仅支持组件级状态管理。在大型复杂的应用程序中,会使用大量组件。React 建议将状态移动到顶级组件,并使用属性将状态传递给嵌套组件。这在一定程度上有帮助,但当组件增加时会变得复杂。

React redux 介入并帮助在应用程序级别维护状态。React redux 允许任何组件随时访问状态。此外,它还允许任何组件随时更改应用程序的状态。

让我们在本章中学习如何使用 React redux 编写 React 应用程序。

概念

React redux 将应用程序的状态维护在一个名为 Redux 存储的地方。React 组件可以从存储中获取最新状态,也可以随时更改状态。 Redux 提供了一个简单的过程来获取和设置应​​用程序的当前状态,并涉及以下概念。

Store − 存储应用程序状态的中心位置。

Actions − Action 是一个普通对象,具有要执行的操作的类型和执行操作所需的输入(称为有效负载)。例如,在商店中添加商品的操作包含 ADD_ITEM 作为类型,以及包含商品详细信息的对象作为有效负载。该操作可以表示为 −

{
    type: 'ADD_ITEM',
    payload: { name: '..', ... }
}

Reducers − Reducers 是纯函数,用于根据现有状态和当前操作创建新状态。它返回新创建的状态。例如,在添加项目场景中,它会创建一个新的项目列表,并将状态和新项目中的项目合并,然后返回新创建的列表。

Action creators(动作创建者) − 动作创建者 创建一个具有适当动作类型和动作所需数据的动作并返回该动作。例如,addItem 动作创建者返回以下对象 −

{ 
   type: 'ADD_ITEM', 
   payload: { name: '..', ... }
}

Component − 组件可以连接到 store 以获取当前状态并将操作分派到 store,以便 store 执行操作并更新其当前状态。

典型的 redux store 的工作流程可以表示如下。

Redux Store
  • React 组件在应用程序初始化期间订阅 store 并获取最新状态。
  • 要更改状态,React 组件会创建必要的操作并分派该操作。
  • Reducer 根据操作创建一个新状态并返回它。 Store 使用新状态进行自我更新。
  • 一旦状态发生变化,store 会将更新后的状态发送给其订阅的所有组件。

Redux API

Redux 提供了一个单独的 api,connect,它将组件连接到 store,并允许组件获取和设置 store 的状态。

connect API 的签名是 −

function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)

所有参数都是可选的,它返回一个 HOC(高阶组件)。高阶组件是一个包装组件并返回新组件的函数。

let hoc = connect(mapStateToProps, mapDispatchToProps)
let ConnectedComponent = hoc(component)

让我们看看前两个参数,它们在大多数情况下已经足够了。

  • mapStateToProps − 接受具有以下签名的函数。

(state, ownProps?) => Object

这里,state 指的是商店的当前状态,Object 指的是组件的新道具。每当 store 的状态更新时,它都会被调用。

(state) => { prop1: this.state.anyvalue }
  • mapDispatchToProps − 接受具有以下签名的函数。

Object | (dispatch, ownProps?) => Object

此处,dispatch 指的是用于在 redux store 中调度操作的调度对象,而 Object 指的是作为组件 props 的一个或多个调度函数。

(dispatch) => {
   addDispatcher: (dispatch) => dispatch({ type: 'ADD_ITEM', payload: { } }),
   removeispatcher: (dispatch) => dispatch({ type: 'REMOVE_ITEM', payload: { } }),
}

Provider 组件

React Redux 提供了一个 Provider 组件,其唯一目的是使 Redux 存储可供使用 connect API 连接到存储的所有嵌套组件使用。示例代码如下 −

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { App } from './App'
import createStore from './createReduxStore'

const store = createStore()

ReactDOM.render(
   <Provider store={store}>
      <App />
   </Provider>,
   document.getElementById('root')
)

现在,App 组件内的所有组件都可以通过 connect API 访问 Redux 存储。

工作示例

让我们重新创建Expense Manager(费用管理器)应用程序,并使用 React redux 概念来维护应用程序的状态。

首先,按照创建 React 应用程序章节中的说明,使用 Create React App 或 Rollup 捆绑器创建一个新的 React 应用程序 react-message-app。

接下来,安装 Redux 和 React redux 库。

npm install redux react-redux --save

接下来,安装 uuid 库以生成新费用的唯一标识符。

npm install uuid --save

接下来,在您最喜欢的浏览器中打开该应用程序编辑器。

接下来,在应用程序的根目录下创建 src 文件夹。

接下来,在 src 文件夹下创建 actions 文件夹。

接下来,在 src/actions 文件夹下创建一个文件 types.js 并开始编辑。

接下来,添加两个操作类型,一个用于添加费用,一个用于删除费用。

export const ADD_EXPENSE = 'ADD_EXPENSE';
export const DELETE_EXPENSE = 'DELETE_EXPENSE';

接下来,在 src/actions 文件夹下创建一个文件 index.js,以添加操作并开始编辑。

接下来,导入 uuid 以创建唯一标识符。

import { v4 as uuidv4 } from 'uuid';

接下来,导入操作类型。

import { ADD_EXPENSE, DELETE_EXPENSE } from './types';

接下来,添加一个新函数以返回添加费用的操作类型并将其导出。

export const addExpense = ({ name, amount, spendDate, category }) => ({
   type: ADD_EXPENSE,
   payload: {
      id: uuidv4(),
      name,
      amount,
      spendDate,
      category
   }
});

此处,该函数需要费用对象并返回 ADD_EXPENSE 的操作类型以及费用信息的有效负载。

接下来,添加一个新函数以返回删除费用的操作类型并将其导出。

export const deleteExpense = id => ({
   type: DELETE_EXPENSE,
   payload: {
      id
   }
});

此处,该函数需要删除费用项目的 id,并返回"DELETE_EXPENSE"操作类型以及费用 id 的有效负载。

操作的完整源代码如下所示 −

import { v4 as uuidv4 } from 'uuid';
import { ADD_EXPENSE, DELETE_EXPENSE } from './types';

export const addExpense = ({ name, amount, spendDate, category }) => ({
   type: ADD_EXPENSE,
   payload: {
      id: uuidv4(),
      name,
      amount,
      spendDate,
      category
   }
});
export const deleteExpense = id => ({
   type: DELETE_EXPENSE,
   payload: {
      id
   }
});

接下来,在 src 文件夹下创建一个新文件夹,reducers。

接下来,在 src/reducers 下创建一个文件,index.js,用于编写 Reducer 函数并开始编辑。

接下来,导入操作类型。

import { ADD_EXPENSE, DELETE_EXPENSE } from '../actions/types';

接下来,添加一个函数,expensesReducer,用于在 redux store 中添加和更新费用的实际功能。

export default function expensesReducer(state = [], action) {
   switch (action.type) {
      case ADD_EXPENSE:
         return [...state, action.payload];
      case DELETE_EXPENSE:
         return state.filter(expense => expense.id !== action.payload.id);
      default:
         return state;
   }
}

下面给出reducer的完整源代码 −

import { ADD_EXPENSE, DELETE_EXPENSE } from '../actions/types';

export default function expensesReducer(state = [], action) {
   switch (action.type) {
      case ADD_EXPENSE:
         return [...state, action.payload];
      case DELETE_EXPENSE:
         return state.filter(expense => expense.id !== action.payload.id);
      default:
         return state;
   }
}

在这里,reducer 检查操作类型并执行相关代码。

接下来,在 src 文件夹下创建 components 文件夹。

接下来,在 src/components 文件夹下创建一个文件 ExpenseEntryItemList.css,并为 html 表添加通用样式。

html {
   font-family: sans-serif;
}
table {
   border-collapse: collapse;
   border: 2px solid rgb(200,200,200);
   letter-spacing: 1px;
   font-size: 0.8rem;
}
td, th {
   border: 1px solid rgb(190,190,190);
   padding: 10px 20px;
}
th {
   background-color: rgb(235,235,235);
}
td, th {
   text-align: left;
}
tr:nth-child(even) td {
   background-color: rgb(250,250,250);
}
tr:nth-child(odd) td {
   background-color: rgb(245,245,245);
}
caption {
   padding: 10px;
}
tr.highlight td { 
   background-color: #a6a8bd;
}

接下来,在 src/components 文件夹下创建一个文件 ExpenseEntryItemList.js 并开始编辑。

接下来,导入 React 和 React redux 库。

import React from 'react'; 
import { connect } from 'react-redux';

接下来,导入 ExpenseEntryItemList.css 文件。

import './ExpenseEntryItemList.css';

接下来,导入动作创建者。

import { deleteExpense } from '../actions'; 
import { addExpense } from '../actions';

接下来,创建一个类 ExpenseEntryItemList,并使用 props 调用构造函数。

class ExpenseEntryItemList extends React.Component {
   constructor(props) {
      super(props);
   }
}

接下来,创建 mapStateToProps 函数。

const mapStateToProps = state => {
   return {
      expenses: state
   };
};

在这里,我们将输入状态复制到组件的 expenses props 中。

接下来,创建 mapDispatchToProps 函数。

const mapDispatchToProps = dispatch => {
   return {
      onAddExpense: expense => {
         dispatch(addExpense(expense));
      },
      onDelete: id => {
         dispatch(deleteExpense(id));
      }
   };
};

在这里,我们创建了两个函数,一个用于分派添加费用 (addExpense) 函数,另一个用于分派删除费用 (deleteExpense) 函数,并将这些函数映射到组件的 props。

接下来,使用 connect api 导出组件。

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(ExpenseEntryItemList);

现在,组件获得了下面给出的三个新属性 −

  • expenses − 费用清单

  • onAddExpense − 函数用于分派 addExpense 函数

  • onDelete −函数来调度 deleteExpense 函数

接下来,使用 onAddExpense 属性在构造函数中将一些费用添加到 redux 存储中。

if (this.props.expenses.length == 0)
{
   const items = [
      { id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
      { id: 2, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
      { id: 3, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
      { id: 4, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
      { id: 5, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
      { id: 6, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
      { id: 7, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
      { id: 8, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
      { id: 9, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
      { id: 10, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
   ]
   items.forEach((item) => {
      this.props.onAddExpense(
         { 
            name: item.name, 
            amount: item.amount, 
            spendDate: item.spendDate, 
            category: item.category 
         }
      );
   })
}

接下来,添加一个事件处理程序以使用费用 ID 删除费用项目。

handleDelete = (id,e) => {
   e.preventDefault();
   this.props.onDelete(id);
}

此处,事件处理程序调用 onDelete 调度程序,该调度程序连同费用 ID 一起调用 deleteExpense。

接下来,添加一个方法来计算所有费用的总金额。

getTotal() {
   let total = 0;
   for (var i = 0; i < this.props.expenses.length; i++) {
      total += this.props.expenses[i].amount
   }
   return total;
}

接下来,添加render()方法,并以表格形式列出费用项目。

render() {
   const lists = this.props.expenses.map(
      (item) =>
      <tr key={item.id}>
         <td>{item.name}</td>
         <td>{item.amount}</td>
         <td>{new Date(item.spendDate).toDateString()}</td>
         <td>{item.category}</td>
         <td><a href="#"
            onClick={(e) => this.handleDelete(item.id, e)}>Remove</a></td>
      </tr>
   );
   return (
      <div>
         <table>
            <thead>
               <tr>
                  <th>Item</th>
                  <th>Amount</th>
                  <th>Date</th>
                  <th>Category</th>
                  <th>Remove</th>
               </tr>
            </thead>
            <tbody>
               {lists}
               <tr>
                  <td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
                  <td colSpan="4" style={{ textAlign: "left" }}>
                     {this.getTotal()}
                  </td>
               </tr>
            </tbody>
         </table>
      </div>
   );
}

在这里,我们设置事件处理程序handleDelete以从商店中删除费用。

下面给出了ExpenseEntryItemList组件的完整源代码 −

import React from 'react';
import { connect } from 'react-redux';
import './ExpenseEntryItemList.css';
import { deleteExpense } from '../actions';
import { addExpense } from '../actions';

class ExpenseEntryItemList extends React.Component {
   constructor(props) {
      super(props);

      if (this.props.expenses.length == 0){
         const items = [
            { id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
            { id: 2, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
            { id: 3, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
            { id: 4, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
            { id: 5, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
            { id: 6, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
            { id: 7, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
            { id: 8, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
            { id: 9, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
            { id: 10, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
         ]
         items.forEach((item) => {
            this.props.onAddExpense(
               { 
                  name: item.name, 
                  amount: item.amount, 
                  spendDate: item.spendDate, 
                  category: item.category 
               }
            );
         })
      }
   }
   handleDelete = (id,e) => {
      e.preventDefault();
      this.props.onDelete(id);
   }
   getTotal() {
      let total = 0;
      for (var i = 0; i < this.props.expenses.length; i++) {
         total += this.props.expenses[i].amount
      }
      return total;
   }
   render() {
      const lists = this.props.expenses.map((item) =>
         <tr key={item.id}>
            <td>{item.name}</td>
            <td>{item.amount}</td>
            <td>{new Date(item.spendDate).toDateString()}</td>
            <td>{item.category}</td>
            <td><a href="#"
               onClick={(e) => this.handleDelete(item.id, e)}>Remove</a></td>
         </tr>
      );
      return (
         <div>
            <table>
               <thead>
                  <tr>
                     <th>Item</th>
                     <th>Amount</th>
                     <th>Date</th>
                     <th>Category</th>
                     <th>Remove</th>
                  </tr>
               </thead>
               <tbody>
                  {lists}
                  <tr>
                     <td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
                     <td colSpan="4" style={{ textAlign: "left" }}>
                        {this.getTotal()}
                     </td>
                  </tr>
               </tbody>
            </table>
         </div>
      );
   }
}
const mapStateToProps = state => {
   return {
      expenses: state
   };
};
const mapDispatchToProps = dispatch => {
   return {
      onAddExpense: expense => {
         dispatch(addExpense(expense));
      },
      onDelete: id => {
         dispatch(deleteExpense(id));
      }
   };
};
export default connect(
   mapStateToProps,
   mapDispatchToProps
)(ExpenseEntryItemList);

接下来,在 src/components 文件夹下创建一个文件 App.js,并使用 ExpenseEntryItemList 组件。

import React, { Component } from 'react';
import ExpenseEntryItemList from './ExpenseEntryItemList';

class App extends Component {
   render() {
      return (
         <div>
            <ExpenseEntryItemList />
         </div>
      );
   }
}
export default App;

接下来,在 src 文件夹下创建一个文件 index.js。

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './reducers';
import App from './components/App';

const store = createStore(rootReducer);

ReactDOM.render(
   <Provider store={store}>
      <App />
   </Provider>,
   document.getElementById('root')
);

这里,

  • 通过附加我们的 Reducer,使用 createStore 创建存储。

  • 使用 React redux 库中的 Provider 组件并将存储设置为 props,这使得所有嵌套组件能够使用 connect api connect 连接到存储。

最后,在根文件夹下创建一个 public 文件夹并创建 index.html 文件。

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="utf-8">
      <title>React Containment App</title>
   </head>
   <body>
      <div id="root"></div>
      <script type="text/JavaScript" src="./index.js"></script>
   </body>
</html>

接下来,使用 npm 命令为应用程序提供服务。

npm start

接下来,打开浏览器并在地址栏中输入 http://localhost:3000 并按 Enter。

单击删除链接将从 redux 商店中删除该项目。

Redux