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 - 快速指南

ReactJS - 简介

ReactJS 是一个免费的开源前端 JavaScript 库,用于开发各种交互式用户界面。它是一个简单、功能丰富且基于组件的 UI 库。当我们说基于组件时,我们的意思是 React 通过创建各种可重复使用和独立的代码来开发应用程序。因此,这个 UI 库在 Web 开发中被广泛使用。

ReactJS 可用于开发小型应用程序以及大型复杂应用程序。ReactJS 提供最小且可靠的功能集来启动 Web 应用程序。React 社区通过提供大量现成的组件来在创纪录的时间内开发 Web 应用程序,从而对 React 库进行了补充。 React 社区还在 React 库的基础上提供了状态管理、路由等高级概念。

React 版本

Reactjs 库由 Facebook 软件工程师 Jordan Walke 于 2011 年创立。随后,React 的初始版本 0.3.0 于 2013 年 5 月发布,最新版本 17.0.1 于 2020 年 10 月发布。主要版本引入了重大更改,次要版本引入了新功能,而不会破坏现有功能。必要时会发布错误修复。React 遵循语义版本控制 (semver)原则。

为什么需要 ReactJS?

尽管有各种库提供了开发用户界面的媒介,但 ReactJS 在受欢迎程度方面仍然占据主导地位。原因如下 −

  • 基于组件 − ReactJS 使用多个组件来构建应用程序。这些组件是独立的,具有自己的逻辑,这使得它们在整个开发过程中可重复使用。这将大大减少应用程序的开发时间。

  • 更好更快的性能 − ReactJS 使用虚拟 DOM。虚拟 DOM 将应用程序组件的先前状态与当前状态进行比较,并且仅更新真实 DOM 中的更改。而传统的 Web 应用程序会再次更新所有组件。这有助于 ReactJS 更快地创建 Web 应用程序。

  • 极其灵活 − React 允许开发人员和团队设置他们认为最适合的约定,并以他们认为合适的方式实现它,因为 React 中没有严格的代码约定规则。

  • 轻松创建动态应用程序 − 动态 Web 应用程序需要更少的编码,同时提供更多的功能。因此,ReactJS 可以轻松创建它们。

  • 也可以开发移动应用程序 − React 不仅可以开发 Web 应用程序,还可以使用 React Native 开发移动应用程序。React Native 是一个开源 UI 软件框架,源自 React 本身。它使用 React 框架为 Android、macOS、Web、Windows 等开发应用程序。

  • 调试很容易 − React 中的数据流是单向的,即在使用 React 设计应用程序时,子组件嵌套在父组件中。由于数据流是单向的,因此调试错误和发现错误变得更加容易。

应用程序

下面列出了一些由 React 库 支持的流行网站 −

  • Facebook,流行的社交媒体应用程序 − React 最初由 Facebook(或 Meta)开发,因此他们使用它来运行他们的应用程序是很自然的。至于他们的移动应用程序,它使用 React Native 来显示 Android 和 iOS 组件,而不是 DOM。Facebook 的代码库现在包含超过 20,000 个组件,并使用公开的 React 版本。
  • Instagram,流行的照片共享应用程序 − Instagram 也完全基于 React,因为它也由 Meta 提供支持。其主要用途包括地理位置、标签、Google Maps API 等。
  • Netflix,流行的流媒体应用程序 − Netflix 于 2015 年改用 React。影响这一决定的主要因素是 1) 启动速度以减少呈现主页的处理时间并在 UI 中启用动态元素,2) 模块化以允许必须与控制体验共存的各种功能和 3) 运行时性能以实现高效的 UI 渲染。
  • Code Academy,流行的在线培训应用程序 − Code Academy 使用 React 作为"脚本经过实战测试,易于思考,使 SEO 变得简单,与遗留代码兼容,并且足够灵活以应对未来"。
  • Reddit,流行的内容共享应用程序 − Reddit 也是使用 React 从头开发的。

如您所见,每个领域中最流行的应用程序都是由 React Library 开发的。

ReactJS - 安装

本章介绍如何在您的机器上安装 React 库及其相关工具。在进行安装之前,让我们先验证先决条件。

React 为开发人员提供 CLI 工具,以快速创建、开发和部署基于 React 的 Web 应用程序。React CLI 工具依赖于 Node.js,必须安装在您的系统中。希望您已经在机器上安装了 Node.js。我们可以使用以下命令检查它 −

node --version

您可以查看您可能已安装的 Nodejs 版本。对我来说,如下所示,

v14.2.0

如果未安装 Nodejs,您可以通过访问 https://nodejs.org/en/download/ 下载并安装。

工具链

要开发表单验证、模型对话框等轻量级功能,可以通过内容分发网络 (CDN) 将 React 库直接包含在 Web 应用程序中。这类似于在 Web 应用程序中使用 jQuery 库。对于中大型应用程序,建议将应用程序编写为多个文件,然后使用捆绑程序(如 webpack、parcel、rollup 等)在部署代码之前编译和捆绑应用程序。

React 工具链有助于创建、构建、运行和部署 React 应用程序。 React 工具链基本上提供了一个启动项目模板,其中包含启动应用程序所需的所有代码。

一些用于开发 React 应用程序的流行工具链是 −

  • 创建 React App − 面向 SPA 的工具链
  • Next.js − 面向服务器端渲染的工具链
  • Gatsby −面向静态内容的工具链

开发 React 应用程序所需的工具是 −

  • serve,一个在开发期间为我们的应用程序提供服务的静态服务器
  • Babel 编译器
  • 创建 React App CLI

让我们在本章中学习上述工具的基础知识以及如何安装它们。

serve 静态服务器

serve 是一个轻量级 Web 服务器。它为静态站点和单页应用程序提供服务。它加载速度快,占用的内存最少。它可用于为 React 应用程序提供服务。让我们使用系统中的 npm 包管理器安装该工具。

npm install serve -g

让我们创建一个简单的静态站点并使用 serve 应用程序为该应用程序提供服务。

打开命令提示符并转到您的工作区。

cd /go/to/your/workspace

创建一个新文件夹 static_site 并将目录更改为新创建的文件夹。

mkdir static_site
cd static_site

接下来,使用您最喜欢的 html 编辑器在文件夹中创建一个简单的网页。

<!DOCTYPE html> 
<html> 
   <head> 
      <meta charset="UTF-8" /> 
      <title>Static website</title> 
   </head> 
   <body> 
      <div><h1>Hello!</h1></div> 
   </body> 
</html>

接下来,运行 serve 命令。

serve .

我们还可以提供单个文件 index.html,而不是整个文件夹。

serve ./index.html

接下来,打开浏览器并在地址栏中输入 http://localhost:5000 并按回车键。serve 应用程序将提供我们的网页,如下所示。

Hello

serve 将使用默认端口 5000 应用程序提供服务。如果不可用,它将选择一个随机端口并指定它。

│ Serving!                                     │   
   │                                              │ 
   │ - Local: http://localhost:57311              │ 
   │ - On Your Network: http://192.168.56.1:57311 │ 
   │                                              │ 
   │ This port was picked because 5000 is in use. │ 
   │                                              │ 
   │ Copied local address to clipboard!

Babel 编译器

Babel 是一个 JavaScript 编译器,它将 JavaScript 的许多变体(es2015、es6 等)编译为所有浏览器支持的标准 JavaScript 代码。React 使用 JSX(JavaScript 的扩展)来设计用户界面代码。Babel 用于将 JSX 代码编译为 JavaScript 代码。

要安装 Babel 及其 React 配套程序,请运行以下命令 −

npm install babel-cli@6 babel-preset-react-app@3 -g
... 
... 
+ babel-cli@6.26.0 
+ babel-preset-react-app@3.1.2 
updated 2 packages in 8.685s

Babel 帮助我们使用下一代高级 JavaScript 语法编写应用程序。

Create React App 工具链

Create React App 是一个用于创建单页 React 应用程序的现代 CLI 工具。它是 React 社区支持的标准工具。它还可以处理 babel 编译器。让我们在本地系统中安装 Create React App

> npm install -g create-react-app
+ create-react-app@4.0.1 
added 6 packages from 4 contributors, removed 37 packages and updated 12 packages in 4.693s

更新工具链

React Create App 工具链使用 react-scripts 包来构建和运行应用程序。一旦我们开始开发应用程序,我们就可以随时使用 npm 包管理器将 react-script 更新到最新版本。

npm install react-scripts@latest

使用 React 工具链的优势

React 工具链提供了许多开箱即用的功能。使用 React 工具链的一些优点是 −

  • 应用程序的预定义和标准结构。
  • 适用于不同类型应用程序的现成项目模板。
  • 包含开发 Web 服务器。
  • 轻松包含第三方 React 组件。
  • 默认设置以测试应用程序。

ReactJS - 功能

ReactJS 正逐渐成为 Web 开发人员中最好的 JavaScript 框架之一。它在前端生态系统中扮演着重要的角色。以下是 ReactJS 的重要功能

  • 虚拟 DOM

  • 组件

  • JSX

  • 单向数据绑定

  • 可扩展

  • 灵活

  • 模块化

虚拟 DOM

虚拟 DOM 是 React 创建的特殊 DOM。虚拟 DOM 代表当前 HTML 文档的真实 DOM。每当 HTML 文档发生变化时,React 都会检查更新后的虚拟 DOM 与虚拟 DOM 的先前状态,并仅更新实际 DOM 中的不同部分。这提高了 HTML 文档渲染的性能。

例如,如果我们创建一个 React 组件,通过 setInterval() 方法定期更新时间来显示当前时间,那么 React 将仅更新当前时间,而不是更新组件的整个内容。

组件

React 建立在组件概念之上。所有现代前端框架都依赖于组件架构。组件架构使开发人员能够将大型应用程序分解为更小的组件,这些组件可以进一步分解为更小的组件。将应用程序分解为更小的组件可以简化应用程序,使其更易于理解和管理。

JSX

JSX 是一个 JavaScript 扩展,用于使用类似于 HTML 的语法创建任意 HTML 元素。这将简化 HTML 文档的创建,并使文档更易于理解。React 将在执行 JSX 之前将其转换为由 React 的 createElement() 函数调用组成的 JavaScript 对象。它提高了应用程序的性能。此外,React 还允许使用纯 createElement() 函数创建 HTML 文档,而无需 JSX。这使开发人员能够在 JSX 不太适合的情况下直接创建 HTML 文档。

单向数据绑定

单向数据绑定可防止组件中的数据向后流动。组件只能将数据传递给其子组件。在任何情况下,组件都不能将数据传递给其父组件。这将简化数据处理并降低复杂性。双向数据绑定乍一看似乎是强制性的,但仔细观察表明,应用程序可以仅使用单向数据绑定来完成,这也简化了应用程序概念。

可扩展

React 可用于创建任何规模的应用程序。 React 组件架构、虚拟 DOM 和单向数据绑定能够在前端应用程序所需的合理时间范围内正确处理大型应用程序。这些功能使 React 成为可扩展的解决方案。

灵活

React 仅提供一些基本概念来创建真正可扩展的应用程序。React 不会以任何方式限制开发人员遵循严格的流程。这使开发人员能够在基本概念之上应用自己的架构并使其具有灵活性。

模块化

React 组件可以在单独的 JavaScript 文件中创建,并且可以导出。这使开发人员能够将某些组件分类并分组到模块中,以便可以在需要时导入和使用。

ReactJS - 优点和缺点

React 是一个用于构建可组合用户界面的库。它鼓励创建可重复使用的 UI 组件,这些组件呈现随时间变化的数据。许多人使用 React 作为 MVC 中的 V。

React 将 DOM 抽象出来,提供更简单的编程模型和更好的性能。React 还可以使用 Node 在服务器上渲染,并且可以使用 React Native 为原生应用提供支持。 React 实现了单向响应式数据流,这减少了样板代码,并且比传统数据绑定更容易推理。

ReactJS 的优势

以下是 ReactJS 的主要优势 −

  • 性能

  • 易于学习

  • 大量第三方组件

  • 大型社区

  • SEO 友好性

  • 轻松启动 React 项目

  • 丰富的开发人员工具集

  • 处理大型应用程序

性能

React 使用虚拟 DOM 概念来检查和更新 HTML 文档。虚拟 DOM 是 React 创建的特殊 DOM。虚拟 DOM 代表当前文档的真实 DOM。每当文档发生变化时,React 都会检查更新后的虚拟 DOM 与虚拟 DOM 的先前状态,并仅更新实际/真实 DOM 中的不同部分。这提高了 HTML 文档渲染的性能。

例如,如果我们创建一个 React 组件,通过 setInterval() 方法定期更新时间来显示当前时间,那么 React 将仅更新当前时间,而不是更新组件的整个内容。

易于学习

React 的核心概念可以在不到一天的时间内学会。React 可以用纯 Javascript(ES6)或 Typescript 编写。要开始使用 React,JavaScript 的基本知识就足够了。对于高级开发人员,Typescript 提供类型安全和丰富的语言功能。开发人员可以通过学习 JSX(类似于 HTML)和属性(props)在几个小时内创建一个 React 组件。学习 React 状态管理将使开发人员能够创建动态组件,该组件在状态发生变化时更新内容。React 为其组件提供了简单的生命周期,可用于正确设置和销毁组件。

大量第三方组件

除了核心 React 库(大小仅为几 KB)之外,React 社区还为从简单的 UI 组件到功能齐全的 PDF 查看器组件的广泛应用提供了大量组件。React 在每个类别中都提供了多个选项。例如,可以使用 Redux 或 MobX 库进行高级状态管理。Redux 和 Mobx 只是两个流行的状态管理库。React 有 10 多个库来存档相同的功能。同样,React 社区在每个类别中都提供了大量第三方库,如路由、数据网格、日历、表单编程等,

大型社区

React 开发者社区是一个活动丰富的大型社区。 React 社区非常活跃,你可以在几分钟内通过 google、stackoverflow 等获得任何与 React 相关的问题/疑问的答案,

SEO 友好性

React 是少数支持 SEO 功能的 JavaScript 库之一。由于 React 组件和 JSX 与 HTML 元素相似,因此无需太多代码/设置即可轻松实现 SEO。

轻松启动 React 项目

React 提供了一个 CLI 应用程序 create-react-app 来创建新的 React 应用程序。create-react-app 应用程序不仅可以创建新的应用程序,还可以在本地环境中构建和运行应用程序,而无需任何其他依赖项。create-react-app 允许开发人员选择模板,这允许应用程序在初始设置期间包含更多样板代码。这使得开发人员只需单击几下即可从小型应用程序启动到大型应用程序。

除了 create-react-app,React 社区还提供 nextjs、gatsby 等其他工具,使开发人员能够在短时间内创建高级应用程序。

丰富的开发人员工具集

React 社区提供必要的开发人员工具来提高开发人员的工作效率。适用于 chrome、edge 和 firefox 浏览器的 React 开发人员工具 使开发人员能够选择一个 React 组件并查看该组件的当前状态。此外,它使开发人员能够通过在浏览器的"开发人员"选项卡中将其显示为组件树来清楚地了解组件层次结构的视图。

处理大型应用程序

React 使用组合将多个组件合并为一个更大的组件,从而允许创建更大的组件。React 组件可以在单个 JavaScript 文件中创建,并且可以设置为可导出。此功能允许将多个组件分组到公共类别下作为模块,并可以在其他地方重复使用。

React 库的可组合和模块化功能允许开发人员创建大型应用程序,与其他前端框架相比,该应用程序相对易于维护。

React 的缺点

尽管 React 库有很多优点,但它也有一些缺点。一些缺点如下 −

  • 缺乏质量文档

  • 没有开发应用程序的标准/推荐方法

  • 开发速度快

  • JavaScript 的高级使用

  • JavaScript 扩展

  • 只是一个 UI 库

缺乏质量文档

React 库在其主网站上有一个不错的文档。它通过几个示例涵盖了基本概念。尽管,它是了解 React 概念基础的一个很好的开始,但它没有提供带有多个示例的深入详细的解释。React 社区介入并提供了大量复杂程度和质量各不相同的文章。但是,它们并没有被组织在一个开发人员可以轻松学习的地方。

没有或更少的标准方法来开发应用程序

React 只是一个 UI 库,几乎没有概念和标准推荐。即使 React 可以用来创建大型/复杂的应用程序,也没有标准或推荐的方法来创建应用程序。由于没有标准方法,React 社区使用多种架构来构建他们的应用程序。开发人员可以自由地为他们的应用程序选择一种方法。在应用程序开发开始时的错误选择会使应用程序复杂化,并延迟应用程序的开发。

快速的开发速度

React 每年发布几次新版本的库。每个版本都有几个附加功能和一些重大更改。开发人员需要快速学习并应用新概念来稳定应用程序。

JavaScript 的高级使用

尽管 React 库的核心概念非常简单易学,但高级概念却非常复杂,因为它利用了 JavaScript 的高级功能。此外,React 拥有大量高级概念来解决 HTML/表单编程的许多复杂场景。对于开发人员来说,学习和掌握如此多的高级概念确实是一个相当大的挑战。

JavaScript 扩展

JSX 是 JavaScript 语言的扩展。JSX 类似于 HTML,简化了组件开发。JSX 与 HTML 编程也几乎没有区别,需要小心正确应用它。此外,JSX 需要在浏览器中执行之前转换为 JavaScript,这对应用程序来说是额外的步骤/负担。

只是一个 UI 库

正如我们之前了解到的,React 只是一个 UI 库,而不是一个框架。创建具有良好架构的 React 应用程序需要仔细选择和应用附加的第三方 React 库。糟糕的设计可能会在应用程序开发的后期/最后阶段影响应用程序。

ReactJS - 架构

React 库建立在坚实的基础之上。它简单、灵活且可扩展。正如我们之前所了解的,React 是一个在 Web 应用程序中创建用户界面的库。React 的主要目的是使开发人员能够使用纯 JavaScript 创建用户界面。通常,每个用户界面库都会引入一种新的模板语言(我们需要学习)来设计用户界面,并提供在模板内或单独编写逻辑的选项。

React 并没有引入新的模板语言,而是引入了下面给出的三个简单概念 −

React 元素

HTML DOM 的 JavaScript 表示。React 提供了一个 API,React.createElement创建 React 元素

JSX

用于设计用户界面的 JavaScript 扩展。JSX 是一种基于 XML 的可扩展语言,只需稍加修改即可支持 HTML 语法。 JSX 可以编译为 React 元素并用于创建用户界面。

React 组件

React 组件是 React 应用程序的主要构建块。它使用 React 元素和 JSX 来设计其用户界面。React 组件基本上是一个 JavaScript 类(扩展 React.component 类)或纯 JavaScript 函数。React 组件具有属性、状态管理、生命周期和事件处理程序。React 组件可以执行简单和高级逻辑。

让我们在 React 组件章节中了解有关组件的更多信息。

React 应用程序的架构

React 库只是 UI 库,它不强制任何特定模式来编写复杂的应用程序。开发人员可以自由选择他们喜欢的设计模式。React 社区提倡某些设计模式。其中一种模式是 Flux 模式。 React 库还提供了许多概念,如高阶组件、上下文、渲染道具、Refs 等,以便编写更好的代码。React Hooks 是一个不断发展的概念,用于在大型项目中进行状态管理。让我们尝试了解 React 应用程序的高级架构。

React App
  • React 应用程序从单个根组件开始。

  • 根组件使用一个或多个组件构建。

  • 每个组件都可以与其他组件嵌套到任何级别。

  • 组合是 React 库的核心概念之一。因此,每个组件都是通过组合较小的组件来构建的,而不是从另一个组件继承一个组件。

  • 大多数组件都是用户界面组件。

  • React 应用程序可以包含第三方组件,用于特定目的,例如路由、动画、状态管理等。

React 应用程序的工作流程

让我们通过创建和分析一个简单的 React 应用程序来了解本章中 React 应用程序的工作流程。

打开命令提示符并转到您的工作区。

cd /go/to/your/workspace

接下来,创建一个文件夹 static_site 并将目录更改为新创建的文件夹。

mkdir static_site
cd static_site

示例

接下来,创建一个文件 hello.html 并编写一个简单的 React 应用程序。

<!DOCTYPE html> 
<html> 
   <head> 
      <meta charset="UTF-8" /> 
      <title>React Application</title> 
   </head> 
   <body> 
      <div id="react-app"></div> 
      <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script> 
      <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script> 
      <script language="JavaScript"> 
         element = React.createElement('h1', {}, 'Hello React!') 
         ReactDOM.render(element, document.getElementById('react-app')); 
      </script> 
   </body> 
</html>

接下来,使用 serve web 服务器为应用程序提供服务。

serve ./hello.html

输出

接下来,打开您最喜欢的浏览器。在地址栏中输入 http://localhost:5000,然后按 Enter。

React Hello

让我们分析代码并进行一些修改,以更好地理解 React 应用程序。

在这里,我们使用 React 库提供的两个 API。

React.createElement

用于创建 React 元素。它需要三个参数 −

  • 元素标签
  • 元素属性作为对象
  • 元素内容 - 它也可以包含嵌套的 React 元素

ReactDOM.render

用于将元素渲染到容器中。它需要两个参数 −

  • React 元素或 JSX
  • 网页的根元素

嵌套的 React 元素

由于 React.createElement 允许嵌套 React 元素,让我们添加嵌套元素,如下所示 −

示例

<script language="JavaScript">
   element = React.createElement('div', {}, React.createElement('h1', {}, 'Hello React!'));
   ReactDOM.render(element, document.getElementById('react-app')); 
</script>

输出

它将生成以下内容 −

<div><h1> Hello React!</h1></div>

使用 JSX

接下来,让我们完全删除 React 元素并引入 JSX 语法,如下所示 −

<!DOCTYPE html> 
<html> 
   <head> 
      <meta charset="UTF-8" /> 
      <title>React Application</title> 
   </head> 
   <body> 
      <div id="react-app"></div> 
      <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script> 
      <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script> 
      <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> 
      <script type="text/babel"> 
         ReactDOM.render(
            <div><h1>Hello React!</h1></div>, 
            document.getElementById('react-app') 
         ); 
     </script> 
   </body> 
</html>

在这里,我们引入了 babel 将 JSX 转换为 JavaScript,并在脚本标签中添加了 type="text/babel"

<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> 
<script type="text/babel"> 
   ... 
   ... 
</script>

接下来,运行应用程序并打开浏览器。应用程序的输出如下 −

Hello Jsx

接下来,让我们创建一个新的 React 组件 Greeting,然后尝试在网页中使用它。

<script type="text/babel"> 
   function Greeting() {
      return <div><h1>Hello JSX!</h1></div> 
   } 
   ReactDOM.render(<Greeting />, document.getElementById('react-app') ); 
</script>

结果相同,如下所示 −

Hello Jsx

通过分析应用程序,我们可以直观地看到 React 应用程序的工作流程,如下图所示。

Workflow Jsx

React 应用程序通过传递使用 React 组件(以 JSX 或 React 元素格式编码)创建的用户界面和容器来调用 ReactDOM.render 方法以呈现用户界面。

ReactDOM.render 处理 JSX 或 React 元素并发出虚拟 DOM。

虚拟 DOM 将合并并呈现到容器中。

React 的架构应用程序

React 库只是一个 UI 库,它不强制任何特定模式来编写复杂的应用程序。开发人员可以自由选择他们喜欢的设计模式。React 社区提倡某些设计模式。其中一种模式是 Flux 模式。React 库还提供了许多概念,如高阶组件、上下文、Render props、Refs 等,以编写更好的代码。React Hooks 是一个不断发展的概念,用于在大型项目中进行状态管理。让我们尝试了解 React 应用程序的高级架构。

ReactJS - 创建 React 应用程序

正如我们之前所了解的,React 库既可用于简单应用程序,也可用于复杂应用程序。简单应用程序通常在其脚本部分中包含 React 库。在复杂应用程序中,开发人员必须将代码拆分为多个文件,并将代码组织成标准结构。在这里,React 工具链提供了预定义的结构来引导应用程序。此外,开发人员可以自由使用自己的项目结构来组织代码。

让我们看看如何创建简单和复杂的 React 应用程序 −

使用 Rollup 捆绑器

Rollup 是一款小巧快速的 JavaScript 捆绑器。让我们在本章中学习如何使用 Rollup Bundler。

以下是使用 Rollup Bundler 创建应用程序的步骤 −

步骤 1 − 打开终端并转到您的工作区。

cd /go/to/your/workspace

步骤 2 − 接下来,创建一个文件夹 expense-manager-rollup 并移动到新创建的文件夹。另外,在您最喜欢的编辑器或 IDE 中打开该文件夹。

mkdir fee-manager-rollup
cd fee-manager-rollup

然后,创建并初始化项目。

npm init -y

步骤 3 −要安装 React 库(react 和 react-dom),请按照以下命令操作。

npm install react@^17.0.0 react-dom@^17.0.0 --save

然后使用以下命令安装 babel 及其预设库作为开发依赖项。

npm install @babel/preset-env @babel/preset-react
@babel/core @babel/plugin-proposal-class-properties -D

接下来,安装 rollup 及其插件库作为开发依赖项。

npm i -D rollup postcss@8.1 @rollup/plugin-babel 
@rollup/plugin-commonjs @rollup/plugin-node-resolve 
@rollup/plugin-replace rollup-plugin-livereload 
rollup-plugin-postcss rollup-plugin-serve postcss@8.1 
postcss-modules@4 rollup-plugin-postcss

接下来,安装 corejs 和 regenerator 运行时,用于异步编程。

npm i regenerator-runtime core-js

步骤 4 − 稍后,在根文件夹下创建一个 babel 配置文件 .babelrc,用于配置 babel 编译器。

{
   "presets": [
      [
         "@babel/preset-env",
         {
            "useBuiltIns": "usage",
            "corejs": 3,
            "targets": "> 0.25%, not dead"
         }
      ],
      "@babel/preset-react"
   ],
   "plugins": [
      "@babel/plugin-proposal-class-properties"
   ]
}
rollup.config.js:

接下来,在根文件夹中创建一个 rollup.config.js 文件来配置汇总捆绑器。

import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import replace from '@rollup/plugin-replace';
import serve from 'rollup-plugin-serve';
import livereload from 'rollup-plugin-livereload';
import postcss from 'rollup-plugin-postcss'

export default {
   input: 'src/index.js',
   output: {
      file: 'public/index.js',
      format: 'iife',
   },
   plugins: [
      commonjs({
         include: [
            'node_modules/**',
         ],
         exclude: [
            'node_modules/process-es6/**',
         ],
      }),
      resolve(),
      babel({
         exclude: 'node_modules/**'
      }),
      replace({
         'process.env.NODE_ENV': JSON.stringify('production'),
      }),
      postcss({
         autoModules: true
      }),
      livereload('public'),
      serve({
         contentBase: 'public',
         port: 3000,
         open: true,
      }), // index.html should be in root of project
   ]
}
package.json

接下来,更新 package.json 并包含我们的入口点 (public/index.js 和 public/styles.css) 以及用于构建和运行应用程序的命令。

...
"main": "public/index.js",
"style": "public/styles.css",
"files": [
   "public"
],
"scripts": {
   "start": "rollup -c -w",
   "build": "rollup"
},
...

步骤 5 − 接下来,在应用程序的根目录中创建一个 src 文件夹,该文件夹将保存应用程序的所有源代码。

接下来,在 src 下创建一个文件夹 components 以包含我们的 React 组件。我们的想法是创建两个文件,<component>.js 来编写组件逻辑,<component.css> 来包含组件特定的样式。

应用程序的最终结构将如下所示 −

|-- package-lock.json
|-- package.json
|-- rollup.config.js
|-- .babelrc
`-- public
   |-- index.html
`-- src
   |-- index.js
   `-- components
   |  |-- mycom.js
   |  |-- mycom.css

现在,让我们创建一个新组件 HelloWorld 来确认我们的设置运行正常。

HelloWorld.js

在 components 文件夹下创建一个文件 HelloWorld.js,并编写一个简单的组件来发出 Hello World 消息。

import React from "react";

class HelloWorld extends React.Component {
   render() {
      return (
         <div>
            <h1>Hello World!</h1>
         </div>
      );
   }
}
export default HelloWorld;

index.js

接下来,在 src 文件夹下创建我们的主要文件 index.js,并调用我们新创建的组件。

import React from 'react';
import ReactDOM from 'react-dom';
import HelloWorld from './components/HelloWorld';

ReactDOM.render(
   <React.StrictMode>
      <HelloWorld />
   </React.StrictMode>,
   document.getElementById('root')
);

在根目录中创建一个 public 文件夹。

index.html

接下来,创建一个 html 文件 index.html(在 public 文件夹* 下),它将是我们应用程序的入口点。

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="utf-8">
      <title> Expense Manager :: Rollup version</title>
   </head>
   <body>
      <div id="root"></div>
      <script type="text/JavaScript" src="./index.js"></script>
   </body>
</html>

最后,构建并运行应用程序。

npm start

npm build 命令将执行 rollup 并将我们的应用程序捆绑到单个文件 dist/index.js 中,并开始为应用程序提供服务。dev 命令将在源代码更改时重新编译代码,并在浏览器中重新加载更改。

> expense-manager-rollup@1.0.0 build /path/to/your/workspace/expense-manager-rollup 
> rollup -c 
rollup v2.36.1 
bundles src/index.js → dist\index.js... 
LiveReload enabled 
http://localhost:10001 -> /path/to/your/workspace/expense-manager-rollup/dist 
created dist\index.js in 4.7s 

waiting for changes...

打开浏览器,在地址栏中输入 http://localhost:3000,然后按 Enter。serve 应用程序将提供我们的网页,如下所示。

Hello World

使用 Parcel 捆绑器

Parcel 是零配置的快速捆绑器。它只需要应用程序的入口点,它将自行解析依赖项并捆绑应用程序。让我们在本章中学习如何使用 parcel 捆绑器。

步骤 1 −首先,安装 parcel bundler。

npm install -g parcel-bundler

然后,打开终端并转到您的工作区。

cd /go/to/your/workspace

步骤 2 − 接下来,创建一个文件夹 expense-manager-parcel 并移动到新创建的文件夹。此外,在您最喜欢的编辑器或 IDE 中打开该文件夹。

mkdir fee-manager-parcel
cd fee-manager-parcel

使用以下命令创建并初始化项目。

npm init -y

步骤 3 −安装 React 库(react 和 react-dom)

npm install react@^17.0.0 react-dom@^17.0.0 --save

安装 babel 及其预设库作为开发依赖项。

npm install @babel/preset-env @babel/preset-react @babel/core @babel/plugin-proposal-class-properties -D

然后,在根文件夹下创建一个 babel 配置文件 .babelrc 来配置 babel 编译器。

{
   "presets": [
      "@babel/preset-env",
      "@babel/preset-react"
   ],
   "plugins": [
      "@babel/plugin-proposal-class-properties"
   ]
}

步骤 4 − 更新 package.json 并包含我们的入口点 (src/index.js) 以及用于构建和运行应用程序的命令。

... 
"main": "src/index.js", 
"scripts": {
   "start": "parcel public/index.html",
   "build": "parcel build public/index.html --out-dir dist" 
},
...

步骤 5 − 在应用程序的根目录中创建一个 src 文件夹,该文件夹将保存应用程序的所有源代码。

接下来,在 src 下创建一个文件夹 components 以包含我们的 React 组件。我们的想法是创建两个文件,<component>.js 来编写组件逻辑,<component.css> 来包含组件特定的样式。

应用程序的最终结构将如下所示 −

|-- package-lock.json
|-- package.json
|-- .babelrc
`-- public
   |-- index.html
`-- src
   |-- index.js
   `-- components
   |  |-- mycom.js
   |  |-- mycom.css

让我们创建一个新组件 HelloWorld 来确认我们的设置运行正常。在 components 文件夹下创建一个文件 HelloWorld.js,并编写一个简单的组件来 发出 Hello World 消息。

HelloWorld.js

import React from "react";

class HelloWorld extends React.Component {
   render() {
      return (
         <div>
            <h1>Hello World!</h1>
         </div>
      );
   }
}
export default HelloWorld;

index.js

现在,在 src 文件夹下创建我们的主要文件 index.js,并调用我们新创建的组件。

import React from 'react';
import ReactDOM from 'react-dom';
import HelloWorld from './components/HelloWorld';

ReactDOM.render(
   <React.StrictMode>
      <HelloWorld />
   </React.StrictMode>,
   document.getElementById('root')
);

接下来,在根目录中创建一个 public 文件夹。

index.html

创建一个 html 文件 index.html(在 public 文件夹中),它将成为我们应用程序的入口点。

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="utf-8">
      <title> Expense Manager :: Parcel version</title>
   </head>
   <body>
      <div id="root"></div>
      <script type="text/JavaScript" src="../src/index.js"></script>
   </body>
</html>

最后,构建并运行应用程序。

npm start

npm build 命令将执行 parcel 命令。它将动态捆绑并提供应用程序。每当源代码发生更改时,它都会重新编译,并在浏览器中重新加载更改。

> expense-manager-parcel@1.0.0 dev /go/to/your/workspace/expense-manager-parcel 
> parcel index.html Server running at http://localhost:1234 
√ Built in 10.41s.

打开浏览器,在地址栏中输入 http://localhost:1234,然后按 Enter。

Hello World

要创建应用程序的生产包以将其部署到生产服务器中,请使用 build 命令。它将在 dist 文件夹下生成一个包含所有捆绑源代码的 index.js 文件。

npm run build
> expense-manager-parcel@1.0.0 build /go/to/your/workspace/expense-manager-parcel
> parcel build index.html --out-dir dist

&sqrt;  Built in 6.42s.

dist\src.80621d09.js.map    270.23 KB     79ms
dist\src.80621d09.js        131.49 KB    4.67s
dist\index.html                 221 B    1.63s

ReactJS - JSX

正如我们之前所了解的,React JSX 是 JavaScript 的扩展。它允许编写看起来像 HTML 代码的 JavaScript 代码。例如,请考虑以下代码:

const element = <h1>Hello React!</h1>

上面代码中提供的标签称为 JSX。JSX 主要用于提供有关界面外观的信息。但是,它不完全是一种模板语言,而是 JavaScript 的语法扩展。JSX 生成呈现到 DOM 中的元素,以指定输出必须是什么样子。

在 ReactJS 中使用 JSX

JSX 使开发人员能够使用 XML 语法创建虚拟 DOM。它编译为纯 JavaScript(React.createElement 函数调用),因此,它可以在任何有效的 JavaScript 代码中使用。

  • 分配给变量。
var Greeting = <h1>Hello React!</h1>
  • 根据条件分配给变量。
var canGreet = true; 
if(canGreet) { 
   greeting = <h1>Hello React!</h1> 
}
  • 可以用作函数的返回值。
function Greeting() { 
   return <h1>Hello React!</h1> 
   
} 
greeting = Greeting()
  • 可以用作函数的参数。
function Greet(message) { 
   ReactDOM.render(message, document.getElementById('react-app') 
} 
Greet(<h1>Hello React!</h1>)

为什么选择 JSX?

在 React 中使用 JSX 并不是强制性的,因为有多种选项可以实现与 JSX 相同的功能;但它在 JavaScript 代码中使用 UI 时,作为视觉辅助很有用。

  • JSX 在将代码转换为 JavaScript 时执行优化,使其比常规 JavaScript 更快。

  • React 使用在单个文件中包含标记和逻辑的组件,而不是单独的文件。

  • 大多数错误都可以在编译时发现,因为数据流是单向的。

  • 使用 JSX 创建模板变得更容易。

  • 我们可以在条件语句(if−else)和循环语句(for 循环)中使用 JSX,可以将其分配给变量,将其作为参数接受,或从函数返回。

  • 使用 JSX 可以防止跨站点脚本攻击或注入攻击。

表达式JSX

JSX 支持纯 JavaScript 语法中的表达式。表达式必须括在花括号内,{ } 。表达式可以包含定义 JSX 的上下文中可用的所有变量。让我们用表达式创建简单的 JSX。

示例

<script type="text/babel">
   var cTime = new Date().toTimeString();
   ReactDOM.render(
      <div><p>The current time is {cTime}</p></div>, 
      document.getElementById('react-app') );
</script>

输出

此处,JSX 使用表达式中使用了 cTime。上述代码的输出如下,

The Current time is 21:19:56 GMT+0530(India Standard Time)

在 JSX 中使用表达式的积极副作用之一是它可以防止注入攻击,因为它将任何字符串转换为 html 安全字符串。

JSX 中的函数

JSX 支持用户定义的 JavaScript 函数。函数用法类似于表达式。让我们创建一个简单的函数并在 JSX 中使用它。

示例

<script type="text/babel">
   var cTime = new Date().toTimeString();
   ReactDOM.render(
      <div><p>The current time is {cTime}</p></div>, 
      document.getElementById('react-app') 
   );
</script>

输出

此处,getCurrentTime() 用于获取当前时间,输出与下面指定的类似 −

The Current time is 21:19:56 GMT+0530(India Standard Time)

JSX 中的属性

JSX 支持类似 HTML 的属性。所有 HTML 标签及其属性均受支持。属性必须使用 camelCase 约定(并且遵循 JavaScript DOM API)而不是普通的 HTML 属性名称来指定。例如,HTML 中的类属性必须定义为 className。以下是其他几个示例 −

  • htmlFor 而不是 for
  • tabIndex 而不是 tabindex
  • onClick 而不是 onclick

示例

<style>
   .red { color: red }
</style>
<script type="text/babel">
   function getCurrentTime() {
      return new Date().toTimeString();
   }
   ReactDOM.render(
      <div>
         <p>The current time is <span className="red">{getCurrentTime()}</span></p>
      </div>,
      document.getElementById('react-app') 
   );
</script>

输出

输出如下 −

The Current time is 22:36:55 GMT+0530(India Standard Time)

在属性中使用表达式

JSX 支持在属性中指定表达式。在属性中,不应将双引号与表达式一起使用。必须使用表达式或使用双引号的字符串。可以将上述示例更改为在属性中使用表达式。

<style>
   .red { color: red }
</style>

<script type="text/babel">
   function getCurrentTime() {
      return new Date().toTimeString();
   }
   var class_name = "red";
   ReactDOM.render(
      <div>
         <p>The current time is <span className={class_name}>{getCurrentTime()}</span></p>
      </div>, 
      document.getElementById('react-app') 
   );
</script>

JSX 中的嵌套元素

JSX 中的嵌套元素可用作 JSX 子元素。它们在显示嵌套组件时非常有用。您可以将任何类型的元素一起使用,包括标签、文字、函数、表达式等。但 false、null、undefined 和 true 都是 JSX 的有效元素;它们只是不会呈现,因为这些 JSX 表达式都会呈现为同一个内容。在这种情况下,JSX 类似于 HTML。

以下是一段简单的代码,用于展示 JSX 中嵌套元素的用法 −

<div>
   This is a list:
   <ul>
      <li>Element 1</li>
      <li>Element 2</li>
   </ul>
</div>

ReactJS - 组件

React 组件是 React 应用程序的构建块。让我们在本章中学习如何创建新的 React 组件以及 React 组件的功能。

React 组件代表网页中的一小块用户界面。React 组件的主要工作是呈现其用户界面并在其内部状态发生变化时更新它。除了呈现 UI 之外,它还管理属于其用户界面的事件。总而言之,React 组件提供以下功能。

  • 用户界面的初始呈现。
  • 事件的管理和处理。
  • 每当内部状态发生变化时更新用户界面。

React 组件使用三个概念实现这些功能 −

  • 属性 −使组件能够接收输入。

  • 事件 − 使组件能够管理 DOM 事件和最终用户交互。

  • 状态 − 使组件保持状态。状态组件根据其状态更新其 UI。

React 中有两种类型的组件。它们是 −

  • 函数组件

  • 类组件

函数组件

函数组件字面上定义为 JavaScript 函数。此 React 组件接受单个对象参数并返回 React 元素。请注意,React 中的元素不是组件,但组件由多个元素组成。以下是 React 中函数组件的语法:

function function_name(argument_name) {
    function_body;
}

类组件

同样,类组件是由多个函数组成的基本类。React 的所有类组件都是 React.Component 类的子类,因此,类组件必须始终扩展它。以下是基本语法 −

class class_name extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

让我们在接下来的章节中逐一学习所有概念。

创建 React 组件

React 库有两种组件类型。这些类型根据创建方式进行分类。

  • 函数组件 − 使用纯 JavaScript 函数。
  • ES6 类组件 − 使用 ES6 类。

函数和类组件之间的核心区别是 −

  • 函数组件本质上非常小。它的唯一要求是返回一个 React 元素

function Hello() {
    return '<div>Hello</div>'
}

使用 ES6 类组件,只需很少的额外编码即可实现相同的功能。

class ExpenseEntryItem extends React.Component {         
   render() { 
      return ( 
         <div>Hello</div> 
      ); 
   }
}
  • 类组件支持开箱即用的状态管理,而函数组件不支持状态管理。但是,React 为函数组件提供了一个Hooks(钩子) useState() 来维护其状态。

  • 类组件具有生命周期,并通过专用回调 API 访问每个生命周期事件。函数组件没有生命周期。同样,React 为函数组件提供了一个Hooks(钩子) useEffect() 来访问组件的不同阶段。

创建类组件

让我们创建一个新的 React 组件(在我们的Expense Manager(费用管理器)应用中),ExpenseEntryItem 来展示费用条目项。费用条目项由名称、金额、日期和类别组成。费用条目项的对象表示是 −

{ 
   'name': 'Mango juice', 
   'amount': 30.00, 
   'spend_date': '2020-10-10' 
   'category': 'Food', 
}

在您最喜欢的编辑器中打开 expense-manager 应用程序。

接下来,在 src/components 文件夹下创建一个文件 ExpenseEntryItem.css 来设置组件的样式。

接下来,通过扩展 React.Componentsrc/components 文件夹下创建一个文件 ExpenseEntryItem.js

import React from 'react';
import './ExpenseEntryItem.css';
class ExpenseEntryItem extends React.Component {
}

接下来,在 ExpenseEntryItem 类中创建一个方法 render

class ExpenseEntryItem extends React.Component { 
   render() { 
   } 
}

接下来,使用 JSX 创建用户界面并从 render 方法返回它。

class ExpenseEntryItem extends React.Component {
   render() {
      return (
         <div>
            <div><b>Item:</b> <em>Mango Juice</em></div>
            <div><b>Amount:</b> <em>30.00</em></div>
            <div><b>Spend Date:</b> <em>2020-10-10</em></div>
            <div><b>Category:</b> <em>Food</em></div>
         </div>
      );
   }
}

接下来,将该组件指定为默认导出类。

import React from 'react';
import './ExpenseEntryItem.css';

class ExpenseEntryItem extends React.Component {
   render() {
      return (
         <div>
            <div><b>Item:</b> <em>Mango Juice</em></div>
            <div><b>Amount:</b> <em>30.00</em></div>
            <div><b>Spend Date:</b> <em>2020-10-10</em></div>
            <div><b>Category:</b> <em>Food</em></div>
         </div>
      );
   }
}
export default ExpenseEntryItem;

现在,我们成功创建了第一个 React 组件。让我们在 index.js 中使用新创建的组件。

import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItem from './components/ExpenseEntryItem'

ReactDOM.render(
   <React.StrictMode>
      <ExpenseEntryItem />
   </React.StrictMode>,
   document.getElementById('root')
);

示例

可以使用 CDN 在网页中实现相同的功能,如下所示 −

<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <title>React application :: ExpenseEntryItem component</title>
   </head>
   <body>
      <div id="react-app"></div>
       
      <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
      <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
      <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
      <script type="text/babel">
         class ExpenseEntryItem extends React.Component {
            render() {
               return (
                  <div>
                     <div><b>Item:</b> <em>Mango Juice</em></div>
                     <div><b>Amount:</b> <em>30.00</em></div>
                     <div><b>Spend Date:</b> <em>2020-10-10</em></div>
                     <div><b>Category:</b> <em>Food</em></div>
                  </div>
               );
            }
         }
         ReactDOM.render(
            <ExpenseEntryItem />,
            document.getElementById('react-app') );
      </script>
   </body>
</html>

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

npm start

输出

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

Item: Mango Juice
Amount: 30.00
Spend Date: 2020-10-10
Category: Food

创建函数组件

也可以使用纯 JavaScript 函数创建 React 组件,但功能有限。基于函数的 React 组件不支持状态管理和其他高级功能。它可用于快速创建一个简单的组件。

上面的 ExpenseEntryItem 可以在函数中重写,如下所示 −

function ExpenseEntryItem() {
   return (
      <div>
         <div><b>Item:</b> <em>Mango Juice</em></div>
         <div><b>Amount:</b> <em>30.00</em></div>
         <div><b>Spend Date:</b> <em>2020-10-10</em></div>
         <div><b>Category:</b> <em>Food</em></div>
      </div>
   );
}

这里,我们只包含了渲染功能,这足以创建一个简单的 React 组件。

拆分组件

即使 JavaScript 执行起来更简单,但对于一个相对简单的项目来说,由于类或依赖项数量众多,代码也经常变得复杂。代码越多,浏览器中的加载时间就越长。结果,降低了其性能效率。这就是可以使用代码拆分的地方。代码拆分用于将组件或包划分为更小的块以提高性能。

代码拆分将仅加载浏览器当前需要的组件。此过程称为延迟加载。这将大大提高应用程序的性能。必须注意的是,我们并不是想通过这种方式减少代码量,而只是试图通过加载用户可能永远不需要的组件来减轻浏览器的负担。让我们看一个示例代码。

示例

让我们首先查看示例应用程序的捆绑代码以执行任何操作。

// file name = app.js
import { sub } from './math.js';

console.log(sub(23, 14));
// file name = math.js
export function sub(a, b) {
  return a - b;
}

上述应用程序的 Bundle 将如下所示 −

function sub(a, b) {
    return a - b;
}
console.log(sub(23, 14));

现在,在应用程序中引入代码拆分的最佳方法是使用动态 import()。

// 代码拆分之前
import { sub } from './math';

console.log(add(23, 14));

// 代码拆分之后
import("./math").then(math => {
    console.log(math.sub(23, 14));
});

当使用此语法时(在 Webpack 等包中),代码拆分将自动开始。但是,如果您正在使用 Create React App,则代码拆分已为您配置,您可以立即开始使用它。

ReactJS - 嵌套组件

React 中的嵌套组件是与另一个组件相关的组件。您也可以将其视为父组件内的子组件;但它们不是使用继承概念链接在一起的,而是使用组合概念。因此,所有组件都嵌套在一起,以创建一个更大的组件,而不是从父组件继承的较小组件。

React 组件是 React 应用程序的构建块。React 组件由多个单独的组件组成。React 允许将多个组件组合在一起以创建更大的组件。此外,React 组件可以嵌套到任意级别。

嵌套组件将使您的代码更高效、更结构化。但是,如果组件嵌套或组装不正确,您的代码可能会变得更加复杂,从而导致效率降低。让我们在本章中了解如何正确组合 React 组件。

FormattedMoney 组件

让我们创建一个组件 FormattedMoney,在渲染之前将金额格式化为小数点后两位。

步骤 1 − 在您最喜欢的编辑器中打开我们的 expense-manager 应用程序。

src/components 文件夹中创建一个名为 FormattedMoney.js 的文件,然后导入 React 库。

import React from 'react';

步骤 2 −然后通过扩展 React.Component 创建一个类 FormattedMoney

class FormattedMoney extends React.Component {
}

接下来,引入带有参数 props 的构造函数,如下所示 −

constructor(props) {
    super(props);
}

创建方法 format() 来格式化金额。

format(amount) {
    return parseFloat(amount).toFixed(2)
}

创建另一个方法 render() 来发出格式化的金额。

render() {
   return (
      <span>{this.format(this.props.value)}</span> 
   ); 
}

在这里,我们通过 this.props 传递 value 属性来使用 format 方法。

步骤 3 − 接下来,将组件指定为默认导出类。

export default FormattedMoney;

现在,我们已经成功创建了 FormattedMoney React 组件。

import React from 'react';

class FormattedMoney extends React.Component {
   constructor(props) {
      super(props)
   }
   format(amount) {
      return parseFloat(amount).toFixed(2)
   }
   render() {
      return (
         <span>{this.format(this.props.value)}</span>
      );
   }
}
export default FormattedMoney;

FormattedDate 组件

让我们创建另一个组件 FormattedDate 来格式化并显示费用的日期和时间。

步骤 1 − 在您最喜欢的编辑器中打开我们的 expense-manager 应用程序。

src/components 文件夹中创建一个文件 FormattedDate.js 并导入 React 库。

import React from 'react';

步骤 2 −接下来,通过扩展 React.Component 创建一个类。

class FormattedDate extends React.Component {
}

然后引入带有参数 props 的构造函数,如下所示 −

constructor(props) {
    super(props);
}

步骤 3 − 接下来,创建一个方法 format() 来格式化日期。

format(val) {
   const months = ["JAN", "FEB", "MAR","APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
   let parsed_date = new Date(Date.parse(val));
   let formatted_date = parsed_date.getDate() + 
      "-" + months[parsed_date.getMonth()] + 
      "-" + parsed_date.getFullYear()
   return formatted_date;
}

创建另一个方法 render() 来发出格式化的日期。

render() { return ( <span>{this.format(this.props.value)}</span> ); }

在这里,我们通过 this.props 传递 value 属性来使用 format 方法。

步骤 4 − 接下来,将组件指定为默认导出类。

export default FormattedDate;

现在,我们已经成功创建了我们的 FormattedDate React 组件。完整代码如下 −

import React from 'react';
class FormattedDate extends React.Component {
   constructor(props) {
      super(props)
   }
   format(val) {
      const months = ["JAN", "FEB", "MAR","APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
      let parsed_date = new Date(Date.parse(val));
      let formatted_date = parsed_date.getDate() + 
         "-" + months[parsed_date.getMonth()] + 
         "-" + parsed_date.getFullYear()
      return formatted_date;
   }
   render() {
      return (
         <span>{this.format(this.props.value)}</span>
      );
   }
}
export default FormattedDate;

ReactJS - 使用组件

React 组件代表网页中的一小块用户界面。React 组件的主要工作是呈现其用户界面并在其内部状态发生变化时更新它。除了呈现 UI 之外,它还管理属于其用户界面的事件。总而言之,React 组件提供以下功能。

在 ReactJS 中使用组件

在本章中,让我们使用新创建的组件并增强我们的 ExpenseEntryItem 组件。

步骤 1 −在您最喜欢的编辑器中打开我们的 expense-manager 应用程序,然后打开 ExpenseEntryItem.js 文件。

然后,使用以下语句导入 FormattedMoneyFormattedDate

import FormattedMoney from './FormattedMoney' 
import FormattedDate from './FormattedDate'

步骤 2 − 接下来,通过包含 FormattedMoneyFormattedDater 组件来更新 render 方法。

render() {
   return (
      <div>
         <div><b>Item:</b> <em>{this.props.item.name}</em></div>
         <div><b>Amount:</b> 
            <em>
               <FormattedMoney value={this.props.item.amount} />
            </em>
         </div>
         <div><b>Spend Date:</b> 
            <em>
               <FormattedDate value={this.props.item.spendDate} />
            </em>
         </div>
         <div><b>Category:</b> 
            <em>{this.props.item.category}</em></div>
      </div>
   );
}

这里我们通过组件的value属性传递了amount和spendDate。

ExprenseEntryItem组件最终更新后的源代码如下 −

import React from 'react'
import FormattedMoney from './FormattedMoney'
import FormattedDate from './FormattedDate'

class ExpenseEntryItem extends React.Component {
   constructor(props) {
      super(props);
   }
   render() {
      return (
         <div>
            <div><b>Item:</b> <em>{this.props.item.name}</em></div>
            <div><b>Amount:</b> 
               <em>
                  <FormattedMoney value={this.props.item.amount} />
               </em>
            </div>
            <div><b>Spend Date:</b> 
               <em>
                  <FormattedDate value={this.props.item.spendDate} />
               </em>
            </div>
            <div><b>Category:</b> 
               <em>{this.props.item.category}</em></div>
         </div>
      );
   }
}
export default ExpenseEntryItem;

index.js

打开 index.js 并通过传递 item 对象来调用 ExpenseEntryItem 组件。

const item = {
   id: 1, 
   name : "Grape Juice", 
   amount : 30.5, 
   spendDate: new Date("2020-10-10"), 
   category: "Food" 
}
ReactDOM.render(
   <React.StrictMode>
   <ExpenseEntryItem item={item} />
   </React.StrictMode>,
   document.getElementById('root')
);

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

npm start

打开浏览器,在地址栏中输入 http://localhost:3000,然后按 Enter。

Grape Modules

ReactJS - 集合组件

在现代应用程序中,开发人员会遇到很多情况,其中项目列表(例如待办事项、订单、发票等)必须以表格格式或图库格式呈现。React 提供了清晰、直观且简单的技术来创建基于列表的用户界面。React 使用两个现有功能来实现此功能。

  • JavaScript 的内置 map 方法。
  • jsx 中的 React 表达式。

Map 方法

map 函数接受一个集合和一个映射函数。 map 函数将应用于集合中的每个项目,结果用于生成新列表。

例如,声明一个包含 5 个随机数的 JavaScript 数组,如下所示 −

let list = [10, 30, 45, 12, 24]

现在,应用一个匿名函数,将其输入加倍,如下所示 −

result = list.map((input) => input * 2);

然后,结果列表为 −

[20, 60, 90, 24, 48]

要刷新 React 表达式,让我们创建一个新变量并分配一个 React 元素。

var hello = <h1>Hello!</h1>
var final = <div>{helloElement}</div>

现在,React 表达式 hello 将与 final 合并并生成,

<div><h1>Hello!</h1></div>

示例

让我们应用这个概念来创建一个组件,以表格格式显示费用条目集合。

步骤 1 −在您最喜欢的编辑器中打开我们的 expense-manager 应用程序。

src/components 文件夹中创建一个文件 ExpenseEntryItemList.css,以包含组件的样式。

src/components 文件夹中创建另一个文件 ExpenseEntryItemList.js,以创建 ExpenseEntryItemList 组件

步骤 2 − 导入 React 库和样式表。

import React from 'react';
import './ExpenseEntryItemList.css';

步骤 3 − 创建 ExpenseEntryItemList 类并调用构造函数。

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

创建一个 render() 函数。

render() {
}

步骤 4 − 使用 map 方法生成 HTML 表行集合,每行代表列表中的单个费用条目。

render() {
   const lists = this.props.items.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>
      </tr>
   );
}

此处,key 标识每一行,并且它在列表中必须是唯一的。

步骤 5 − 接下来,在 render() 方法中,创建一个 HTML 表并在行部分中包含 lists 表达式。

return (
   <table>
      <thead>
         <tr>
            <th>Item</th>
            <th>Amount</th>
            <th>Date</th>
            <th>Category</th>
         </tr>
      </thead>
      <tbody>
         {lists}
      </tbody>
   </table>
);

最后,导出组件。

export default ExpenseEntryItemList;

现在,我们已经成功创建了将费用项目渲染到 HTML 表中的组件。完整代码如下 −

import React from 'react';
import './ExpenseEntryItemList.css'

class ExpenseEntryItemList extends React.Component {
   constructor(props) {
      super(props);
   }
   render() {
      const lists = this.props.items.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>
         </tr>
      );
      return (
         <table>
            <thead>
               <tr>
                  <th>Item</th>
                  <th>Amount</th>
                  <th>Date</th>
                  <th>Category</th>
               </tr>
            </thead>
            <tbody>
               {lists}
            </tbody>
         </table>
      );
   }
}
export default ExpenseEntryItemList;

index.js:

打开 index.js 并导入我们新创建的 ExpenseEntryItemList 组件。

import ExpenseEntryItemList from './components/ExpenseEntryItemList'

接下来,在 index.js 文件中声明一个列表(费用条目项)并用一些随机值填充它。

const items = [
   { id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
   { id: 1, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
   { id: 1, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
   { id: 1, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
   { id: 1, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
   { id: 1, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
   { id: 1, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
   { id: 1, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
   { id: 1, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
   { id: 1, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]

通过 items 属性传递项目来使用 ExpenseEntryItemList 组件。

ReactDOM.render(
   <React.StrictMode>
      <ExpenseEntryItemList items={items} />
   </React.StrictMode>,
   document.getElementById('root')
);

index.js完整代码如下 −

import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemList from './components/ExpenseEntryItemList'

const items = [
   { id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
   { id: 1, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
   { id: 1, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
   { id: 1, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
   { id: 1, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
   { id: 1, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
   { id: 1, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
   { id: 1, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
   { id: 1, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
   { id: 1, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]
ReactDOM.render(
   <React.StrictMode>
      <ExpenseEntryItem item={item} />
   </React.StrictMode>,
   document.getElementById('root')
);

ExpenseEntryItemList.css:

打开ExpenseEntryItemList.css并为表格添加样式。

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;
}

使用 npm 命令启动应用程序。

npm start

输出

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

Item Amount Date Category
Pizza 80 Sat Oct 10 2020 Food
Grape Juice 30 Man Oct 12 2020 Food
Cinema 210 Fri Oct 16 2020 Entertainment
Java Programming book 242 Thu Oct 15 2020 Academic
Mango Juice 35 Fri Oct 16 2020 Food
Dress 2000 Sun Oct 25 2020 Cloth
Tour 2555 Thu Oct 29 2020 Entertainment
Meals 300 Fri Oct 30 2020 Food
Mobile 3500 Mon Nov 02 2020 Gadgets
Exam Fees 1245 Wed Nov 04 2020 Academic

ReactJS - 样式

一般来说,React 允许通过 className 属性使用 CSS 类对组件进行样式化。由于 React JSX 支持 JavaScript 表达式,因此可以使用许多常见的 CSS 方法。一些顶级选项如下 −

  • CSS 样式表 − 普通 CSS 样式以及 className

  • 内联样式 − CSS 样式作为 JavaScript 对象以及 camelCase 属性。

  • CSS 模块 − 本地范围的 CSS 样式。

  • 样式化组件 − 组件级样式。

  • Sass 样式表 −通过在构建时将样式转换为普通 css 来支持基于 Sass 的 CSS 样式。

  • 后处理样式表 − 通过在构建时将样式转换为普通 css 来支持后处理样式。

在本章中,让我们学习如何应用三种重要方法来设计我们的组件样式。

  • CSS 样式表

  • 内联样式

  • CSS 模块

CSS 样式表

CSS 样式表 是一种常用、常见且久经考验的方法。只需为组件创建一个 CSS 样式表,然后输入该特定组件的所有样式即可。然后,在组件中,使用 className 引用样式。

让我们为 ExpenseEntryItem 组件设置样式。

在您最喜欢的编辑器中打开 expense-manager 应用程序。

接下来,打开 ExpenseEntryItem.css 文件并添加一些样式。

div.itemStyle { 
   color: brown; 
   font-size: 14px; 
}

接下来,打开 ExpenseEntryItem.js 并将 className 添加到主容器。

import React from 'react';
import './ExpenseEntryItem.css';

class ExpenseEntryItem extends React.Component {
   render() {
      return (
         <div className="itemStyle">
            <div><b>Item:</b> <em>Mango Juice</em></div>
            <div><b>Amount:</b> <em>30.00</em></div>
            <div><b>Spend Date:</b> <em>2020-10-10</em></div>
            <div><b>Category:</b> <em>Food</em></div>
         </div>
      );
   }
}
export default ExpenseEntryItem;

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

npm start

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

CSS Stylesheet

CSS 样式表易于理解和使用。但是,当项目大小增加时,CSS 样式也会增加,最终会在类名中产生大量冲突。此外,直接加载 CSS 文件仅在 Webpack 捆绑器中受支持,其他工具可能不支持。

内联样式

内联样式 是设置 React 组件样式的最安全方法之一。它使用基于 DOM 的 css 属性将所有样式声明为 JavaScript 对象,并通过 style 属性将其设置为组件。

让我们在组件中添加内联样式。

在您最喜欢的编辑器中打开 expense-manager 应用程序并修改 src 文件夹中的 ExpenseEntryItem.js 文件。声明一个对象类型的变量并设置样式。

itemStyle = {
   color: 'brown', 
   fontSize: '14px' 
}

此处,fontSize 表示 css 属性 font-size。所有 css 属性都可以通过 camelCase 格式表示来使用。

接下来,使用花括号 {} 在组件中设置 itemStyle 样式 −

render() {
   return (
      <div style={ this.itemStyle }>
         <div><b>Item:</b> <em>Mango Juice</em></div>
         <div><b>Amount:</b> <em>30.00</em></div>
         <div><b>Spend Date:</b> <em>2020-10-10</em></div>
         <div><b>Category:</b> <em>Food</em></div>
      </div>
   );
}

此外,还可以直接在组件内部设置样式 −

render() {
   return (
      <div style={
         {
            color: 'brown',
            fontSize: '14px'
         }         
      }>
         <div><b>Item:</b> <em>Mango Juice</em></div>
         <div><b>Amount:</b> <em>30.00</em></div>
         <div><b>Spend Date:</b> <em>2020-10-10</em></div>
         <div><b>Category:</b> <em>Food</em></div>
      </div>
   );
}

现在,我们已经成功地在应用程序中使用了内联样式。

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

npm start

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

内联样式

CSS 模块

Css 模块 提供了最安全且最简单的定义样式的方法。它使用具有正常语法的正常 css 样式表。在导入样式时,CSS 模块会将所有样式转换为本地范围的样式,这样就不会发生名称冲突。让我们将组件更改为使用 CSS 模块

在您最喜欢的编辑器中打开Expense Manager(费用管理器)应用程序。

接下来,在 src/components 文件夹下创建一个新的样式表 ExpenseEntryItem.module.css 文件,并编写常规 css 样式。

div.itemStyle {
   color: 'brown'; 
   font-size: 14px; 
}

这里文件命名规范很重要,React 工具链会通过 CSS Module 对以 .module.css 结尾的 css 文件进行预处理,否则会被视为普通样式表。

接下来,打开 src/component 文件夹中的 ExpenseEntryItem.js 文件,并导入样式。

import styles from './ExpenseEntryItem.module.css'

接下来,在组件中将样式用作 JavaScript 表达式。

<div className={styles.itemStyle}>

现在,我们已经成功地在应用程序中使用了 CSS 模块。

最终完整的代码是 −

import React from 'react';
import './ExpenseEntryItem.css';
import styles from './ExpenseEntryItem.module.css'

class ExpenseEntryItem extends React.Component {
   render() {
      return (
         <div className={styles.itemStyle} >
            <div><b>Item:</b> <em>Mango Juice</em></div>
            <div><b>Amount:</b> <em>30.00</em></div>
            <div><b>Spend Date:</b> <em>2020-10-10</em></div>
            <div><b>Category:</b> <em>Food</em></div>
         </div>
      );
   }
}
export default ExpenseEntryItem;

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

npm start

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

CSS Modules

ReactJS - 属性 (props)

React 允许开发人员使用属性创建动态和高级组件。每个组件都可以具有类似于 HTML 属性的属性,并且可以使用属性 (props) 在组件内部访问每个属性的值。

例如,具有 name 属性的 Hello 组件可以在组件内部通过 this.props.name 变量进行访问。

<Hello name="React" />
// name 的值将是 "Hello* const name = this.props.name

React 属性支持不同类型的属性值。它们如下,

  • String
  • Number
  • Datetime
  • Array
  • List
  • Objects

使用 Props

当我们在组件中需要不可变数据时,我们只需在 main.js 中的 reactDOM.render() 函数中添加 props,然后在组件中使用它即可。

App.jsx

import React from 'react';

class App extends React.Component {
   render() {
      return (
         <div>
            <h1>{this.props.headerProp}</h1>
            <h2>{this.props.contentProp}</h2>
         </div>
      );
   }
}
export default App;

main.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';

ReactDOM.render(<App headerProp = "Header from props..." contentProp = "Content
   from props..."/>, document.getElementById('app'));

export default App;

这将产生以下结果。

React Props 示例

默认 Props

您还可以直接在组件构造函数上设置默认属性值,而不是将其添加到 reactDom.render() 元素。

App.jsx

import React from 'react';

class App extends React.Component {
   render() {
      return (
         <div>
            <h1>{this.props.headerProp}</h1>
            <h2>{this.props.contentProp}</h2>
         </div>
      );
   }
}
App.defaultProps = {
   headerProp: "Header from props...",
   contentProp:"Content from props..."
}
export default App;

main.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';

ReactDOM.render(<App/>, document.getElementById('app'));

输出与之前相同。

React Props 示例

State 与 Props

以下示例展示了如何在应用中结合使用 state 和 props。我们在父组件中设置状态,并使用 props 将其传递到组件树中。在 render 函数中,我们设置子组件中使用的 headerPropcontentProp

App.jsx

import React from 'react';

class App extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         header: "Header from props...",
         content: "Content from props..."
      }
   }
   render() {
      return (
         <div>
            <Header headerProp = {this.state.header}/>
            <Content contentProp = {this.state.content}/>
         </div>
      );
   }
}
class Header extends React.Component {
   render() {
      return (
         <div>
            <h1>{this.props.headerProp}</h1>
         </div>
      );
   }
}
class Content extends React.Component {
   render() {
      return (
         <div>
            <h2>{this.props.contentProp}</h2>
         </div>
      );
   }
}
export default App;

main.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';

ReactDOM.render(<App/>, document.getElementById('app'));

结果将与前两个示例相同,唯一不同的是我们的数据源,现在它最初来自状态。当我们想要更新它时,我们只需要更新状态,所有子组件都会更新。有关此内容的更多信息,请参阅事件章节。

React Props 示例

让我们在本章中逐一学习以下概念。

ReactJS - 使用属性创建组件

正如我们之前在本教程中了解到的,React 是一个非常灵活的库,有时具有可弯曲的规则,但它严格遵循一条规则:如果组件被定义为函数或类,则它必须像纯函数一样对待其属性。React 中的纯函数被定义为其输入不能更改的函数,因此它不会改变其结果。

简而言之,传递给组件的 Props 是只读的。但由于应用程序 UI 是动态的,并且会随时间改变其输入,我们使用"状态"概念来处理它。

状态概念允许 React 组件根据不断变化的用户操作、网络响应等改变其结果,而不会违反此规则。

如何使用属性创建组件?

在本章中,让我们看看使用属性创建组件的步骤 −

我们将首先修改我们的 ExpenseEntryItem 组件并尝试使用属性。

步骤 1 − 在您最喜欢的编辑器中打开我们的 expense-manager 应用程序。

src/components 文件夹中打开 ExpenseEntryItem 文件。

步骤 2 −引入带有参数 props 的构造函数。

constructor(props) {
    super(props);
}

接下来,更改渲染方法并从 props 中填充值。

render() {
   return (
      <div>
         <div><b>Item:</b> <em>{this.props.name}</em></div>
         <div><b>Amount:</b> <em>{this.props.amount}</em></div>
         <div><b>Spend date:</b> 
            <em>{this.props.spenddate.tostring()}</em></div>
         <div><b>Category:</b> <em>{this.props.category}</em></div>
      </div>
   );
}

此处,

  • name 表示商品名称,类型为 String

  • amount 表示商品金额,类型为 number

  • spendDate 表示商品消费日期,类型为 date

  • category 表示商品类别,类型为 String

现在,我们已成功使用属性更新了组件。

import React from 'react'
import './ExpenseEntryItem.css';
import styles from './ExpenseEntryItem.module.css'

class ExpenseEntryItem extends React.Component {
   constructor(props) {
      super(props);
   }
   render() {
      return (
         <div>
            <div><b>Item:</b> <em>{this.props.name}</em></div>
            <div><b>Amount:</b> <em>{this.props.amount}</em></div>
            <div><b>Spend Date:</b> 
               <em>{this.props.spendDate.toString()}</em></div>
            <div><b>Category:</b> <em>{this.props.category}</em></div>
         </div>
      );
   }
}
export default ExpenseEntryItem;

index.js

现在,我们可以通过 index.js 中的属性传递所有属性来使用该组件。

import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItem from './components/ExpenseEntryItem'

const name = "Grape Juice"
const amount = 30.00
const spendDate = new Date("2020-10-10")
const category = "Food"

ReactDOM.render(
   <React.StrictMode>
      <ExpenseEntryItem
         name={name}
         amount={amount}
         spendDate={spendDate}
         category={category} />
   </React.StrictMode>,
   document.getElementById('root')
);

使用 npm 命令为应用程序提供服务。

npm start

打开浏览器,在地址栏中输入 http://localhost:3000,然后按回车键。

Grape Modules

在网页中使用 CDN 的完整代码如下−

<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <title>React based application</title>
   </head>
   <body>
      <div id="react-app"></div>
      
      <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
      <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
      <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
      <script type="text/babel">
         class ExpenseEntryItem extends React.Component {
            constructor(props) {
               super(props);
            }
            render() {
               return (
                  <div>
                     <div><b>Item:</b> <em>{this.props.name}</em></div>
                     <div><b>Amount:</b> <em>{this.props.amount}</em></div>
                     <div><b>Spend Date:</b> <em>{this.props.spendDate.toString()}</em></div>
                     <div><b>Category:</b> <em>{this.props.category}</em></div>
                  </div>
               );
            }
         }
         const name = "Grape Juice"
         const amount = 30.00
         const spendDate = new Date("2020-10-10")
         const category = "Food"

         ReactDOM.render(
            <ExpenseEntryItem 
               name={name} 
               amount={amount} 
               spendDate={spendDate} 
               category={category} />,
            document.getElementById('react-app') );
      </script>
   </body>
</html>

对象作为属性

在本章中,让我们学习如何使用 JavaScript 对象作为属性。

步骤 1 − 在您最喜欢的编辑器中打开我们的 expense-manager 应用程序。

打开 ExpenseEntryItem.js 文件。

步骤 2 − 接下来,更改 render() 方法并通过 this.props.item 属性访问输入对象项。

render() {
   return (
      <div>
         <div><b>Item:</b> <em>{this.props.item.name}</em></div>
         <div><b>Amount:</b> <em>{this.props.item.amount}</em></div>
         <div><b>Spend Date:</b> 
            <em>{this.props.item.spendDate.toString()}</em></div>
         <div><b>Category:</b> <em>{this.props.item.category}</em></div>
      </div>
   );
}

打开 index.js 并在 JavaScript 对象中表示费用条目。

const item = { 
   id: 1, 
   name : "Grape Juice", 
   amount : 30.5, 
   spendDate: new Date("2020-10-10"), 
   category: "Food" 
}

在组件属性中使用花括号 ({}) 语法将对象传递给组件。

<ExpenseEntryItem item={item} />

index.js

index.js 的完整代码如下 −

import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItem from './components/ExpenseEntryItem'

const item = {
   id: 1, 
   name : "Grape Juice", 
   amount : 30.5, 
   spendDate: new Date("2020-10-10"), 
   category: "Food" 
}
ReactDOM.render(
   <React.StrictMode>
   <ExpenseEntryItem item={item} />
   </React.StrictMode>,
   document.getElementById('root')
);

使用 npm 命令为应用程序提供服务。

npm start

打开浏览器,在地址栏中输入 http://localhost:3000,然后按回车键。

Grape Modules

在网页中使用 CDN 的完整代码如下 −

<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <title>React based application</title>
   </head>
   <body>
      <div id="react-app"></div>
      
      <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
      <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
      <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
      <script type="text/babel">
         class ExpenseEntryItem extends React.Component {
            constructor(props) {
               super(props);
            }
            render() {
               return (
                  <div>
                     <div><b>Item:</b> 
                        <em>{this.props.item.name}</em></div>
                     <div><b>Amount:</b> 
                        <em>{this.props.item.amount}</em></div>
                     <div><b>Spend Date:</b> 
                        <em>{this.props.item.spendDate.toString()}</em>
                     </div>
                     <div><b>Category:</b> 
                        <em>{this.props.item.category}</em>
                     </div>
                  </div>
               );
            }
         }
         const item = {
            id: 1, 
            name : "Grape Juice", 
            amount : 30.5, 
            spendDate: new Date("2020-10-10"), 
            category: "Food" 
         }
         ReactDOM.render(
            <ExpenseEntryItem item={item} />,
            document.getElementById('react-app') 
         );
      </script>
   </body>
</html>

ReactJS - props 验证

程序中耗时的过程之一是查找错误的根本原因。在 React 中,props 被广泛使用。组件的 props 有不同的来源。一些组件有静态 props,而一些组件有来自直接父组件的动态 props。错误的来源之一是 props 的值与开发人员设计的 props 类型不匹配。这种不匹配会产生很多错误。React 提供了很多选项来修复这个问题,其中一个功能就是 PropTypes 及其验证。

在本章中,我们将学习什么是 PropTypes 以及如何使用它来创建无错误的 React 应用程序。

PropTypes

React 社区提供了一个特殊的包 prop-types 来解决属性类型不匹配问题。 prop-types 允许通过组件内部的自定义设置 (propTypes) 指定组件属性的类型。例如,可以使用 PropTypes.number 选项指定数字类型的属性,如下所示。

Sum.propTypes = {
   num1: PropTypes.number,
   num2: PropTypes.number
}

一旦指定了属性的类型,React 将在应用程序的开发阶段发出警告。

让我们在应用程序中包含 propTypes,看看它如何帮助捕获属性类型不匹配问题。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,使用节点包管理器 (npm) 安装 prop-types 包,如下所示 −

npm i prop-types --save

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。然后,创建一个简单的组件 Sum (src/Components/Sum.js),如下所示 −

import React from 'react'
import PropTypes from 'prop-types'
class Sum extends React.Component {
   render() {
      return <p>The sum of {this.props.num1} and {this.props.num2}
      is {parseInt(this.props.num1) + parseInt(this.props.num2)}</p>
   }
}
Sum.propTypes = {
   num1: PropTypes.number,
   num2: PropTypes.number
}
export default Sum

这里,

  • 该组件的目的是找到给定 props(num1 和 num2)的总值并将其显示在前端。

  • 使用 propTypesnum1num2 的数据类型设置为数字(PropTypes.number)。

接下来,打开 App 组件(src/App.js),导入 bootstrap css 并呈现日期选择器,如下所示 −

import './App.css'
import Sum from './Components/Sum'
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <Sum num1="10" num2="John" />
            </div>
         </div>
      </div>
   );
}
export default App;

在这里,我们用 10 和 John 作为 props 渲染了 Sum 组件

最后,在您最喜欢的浏览器中打开应用程序,并通过开发人员工具打开 JavaScript 控制台。JavaScript 会发出警告,提示提供了意外类型,如下所示。

Props Validation

propTypes 仅在开发阶段起作用,以消除由于额外检查 props 类型而导致的应用程序性能下降。这不会影响应用程序在生产/实时设置中的性能。

可用的验证器

prop-types 提供了大量现成的验证器。它们如下 −

  • PropTypes.array

  • PropTypes.bigint

  • PropTypes.bool

  • PropTypes.func

  • PropTypes.number

  • PropTypes.object

  • PropTypes.string

  • PropTypes.symbol

  • PropTypes.node - 任何可以渲染的内容

  • PropTypes.element - React 组件

  • PropTypes.elementType - React 组件的类型

  • PropTypes.instanceOf() - 实例指定的类

  • propTypes.oneOf(['Value1', 'valueN']) - Value 和 ValueN 之一

  • PropTypes.oneOfType([]) - 示例,PropTypes.oneOfType([PropTypes.number, PropTypes.bigint])

  • PropTypes.arrayOf() - 示例,PropTypes.arrayOf(PropTypes.number)

  • PropTypes.objectOf() - 示例, PropTypes.objectOf(PropTypes.number)

  • PropTypes.func.isRequired

  • propTypes.element.isRequired

  • PropTypes.any.isRequired

自定义验证器

还可以创建自定义验证器并用于验证属性的值。假设组件具有电子邮件属性,其值应为有效的电子邮件地址。然后,可以编写验证函数并将其附加到电子邮件属性,如下所示 −

Sum.propTypes = {
   num1: PropTypes.number,
   num2: PropTypes.number,
   email: function(myProps, myPropName, myComponentName) {
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(myProps[myPropName])) {
         return new Error(
            'Invalid prop value `' + myProps[myPropName] + '` supplied to' +
            ' `' + myComponentName + '/' + myPropName + '`. Validation failed.'
         );
      }
   }
}

这里,

  • /^[^\s@]+@[^\s@]+\.[^\s@]+$/ 是一个简单的正则表达式电子邮件模式。

  • myProps 表示所有属性。

  • myPropName 表示当前正在验证的属性。

  • myComponentName 表示正在验证的组件的名称。

同样,可以使用以下函数签名 − 创建自定义验证器并将其用于数组和对象属性

PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) { ... })

这里,

  • propValue 表示数组/对象值。

  • key 表示当前项的键。

  • componentName 表示组件的名称。

  • propFullName 表示正在验证的属性的名称。

总结

Props 类型是开发人员编写无错误软件的好工具之一。它肯定会帮助开发人员更快、更安全地编写代码。

ReactJS - 构造函数

一般来说,类中的构造函数方法用于设置新创建对象的初始值。React 也使用构造函数()来实现相同的初始化目的。然而在 React 中,构造函数也用于状态初始化和事件绑定目的。

让我们在本章中学习如何在 React 组件中使用构造函数。

props 的初始化

众所周知,每个 React 组件都会有 props 和状态。应该使用 super 关键字在构造函数中初始化 props。如果在基于类的 React 组件中未正确初始化 props,则 this.props 将无法正常工作并引入错误。让我们使用适当的构造函数方法创建一个简单的组件。

class Welcome extends React.Component {
   constructor(props) {
      super(props);
   }
   render() {
      return (
         <div><h3>Welcome {this.props.name}</h3></div>
      )
   }
}

此处,

  • super(props) 将初始化 Welcome 组件中的 props。

  • this.props.* 将提供对 props 详细信息的访问。

  • 该组件的使用方法如下所示 −

function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <Welcome name={'John'} />
            </div>
         </div>
      </div>
   );
}

组件将呈现如下所示的欢迎消息 −

Props 的初始化

状态的初始化

与 props 初始化类似,状态的初始化非常重要,可以在构造函数中完成。通常,React 提供了不同的方法来设置和获取组件中的状态信息。它们如下 −

使用 this.state = obj

这用于使用对象初始化状态

this.state = {
    pageSize: 10
}

使用 this.state.*

这用于访问状态信息。 (this.state.pageSize)

使用 this.setState()

它是一个接受对象或 lambda 函数的函数。用于设置状态信息

this.setState({
   pageSize: 20
})
this.setState((state, props) => ({
   pageSize: state.pageSize + 1
}))

让我们创建一个具有适当状态初始化的简单组件

class Welcome extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         welcomeMessage: "Hello"
      }
   }
   render() {
      return (
         <div><h3>{this.state.welcomeMessage}, {this.props.name}</h3></div>
      )
   }
}

此处,this.state 用于设置欢迎消息的默认(初始)值。该组件的使用方式如下图所示 −

function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <Welcome name={'John'} />
            </div>
         </div>
      </div>
   );
}

组件将呈现如下所示的欢迎消息 −

Initialization State

事件绑定

与 props 和 state 初始化类似,事件处理程序必须正确绑定,以便在事件处理程序中正确访问 this。让我们在 Welcome 组件中创建新按钮来更改欢迎消息,并添加事件处理程序来处理按钮的 onClick 事件,如下所示 −

import React from "react";
class Welcome extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         welcomeMessage: "Hello"
      }
      this.changeMessageHandler = this.changeMessageHandler.bind(this)
   }
   changeMessageHandler() {
      this.setState(prevState => ({
         welcomeMessage: prevState.welcomeMessage == "Hello" ? "Welcome" : "Hello"
      }));
   }
   render() {
      return (
         <div>
            <div><h3>{this.state.welcomeMessage}, {this.props.name}</h3></div>
            <div><button onClick={this.changeMessageHandler}>Change welcome message</button></div>
         </div>
      )
   }          
}
export default Welcome;

这里,

步骤 1 − 添加一个带有 onClick 事件的按钮

<div><button onClick={this.changeMes​​sageHandler}>更改欢迎消息</button></div>

步骤 2 − 将 this.changeMes​​sageHandler 方法设置为 onClick 事件处理程序

步骤 3 − 在构造函数中绑定事件处理程序 this.changeMes​​sageHandler

this.changeMes​​sageHandler = this.changeMes​​sageHandler.bind(this)

步骤 4 −添加事件处理程序并使用 this.setState

更新状态。
changeMessageHandler() {
   this.setState(prevState => ({
      welcomeMessage: prevState.welcomeMessage == "Hello" ? "Welcome" : "Hello"
   }));
}

该组件的使用方法如下 −

function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <Welcome name={'John'} />
            </div>
         </div>
      </div>
   );
}

组件将呈现如下所示的欢迎消息 −

Event Binding

摘要

构造函数在基于类的 React 组件中非常重要。它的主要工作是以正确配置 props、状态和事件的方式设置组件,并准备好通过组件事件和渲染方法进行访问。

ReactJS - 组件生命周期

在 React 中,组件的生命周期表示组件存在期间的不同阶段。React 提供回调函数来在 React 生命周期的每个阶段附加功能。让我们在本章中学习 React 组件的生命周期(和相关 API)。

生命周期 API

每个 React 组件都有三个不同的阶段。

  • 挂载 − 挂载表示在给定的 DOM 节点中渲染 React 组件。

  • 更新 − 更新表示在状态更改/更新期间在给定的 DOM 节点中重新渲染 React 组件。

  • 卸载 −卸载表示移除 React 组件。

React 提供了一组生命周期事件(或回调 API)来附加功能,这些功能将在组件的各个阶段执行。生命周期的可视化以及生命周期事件(API)的调用顺序如下所示。

Life Cycle

constructor() − 在 React 组件的初始构造阶段调用。用于设置组件的初始状态和属性。

render() − 在组件构造完成后调用。它在虚拟 DOM 实例中渲染组件。这被指定为在 DOM 树中安装组件。

componentDidMount() −在 DOM 树中初始安装组件后调用。这是调用 API 端点和执行网络请求的最佳位置。在我们的时钟组件中,可以在此处设置 setInterval 函数以每秒更新状态(当前日期和时间)。

componentDidMount() { 
   this.timeFn = setInterval( () => this.setTime(), 1000); 
}

componentDidUpdate()与 ComponentDidMount() 类似,但在更新阶段调用。此阶段可以进行网络请求,但前提是组件的当前属性和先前属性存在差异。

API 的签名如下 −

componentDidUpdate(prevProps, prevState, snap)
  • prevProps − 组件的先前属性。

  • prevState − 组件的先前状态。

  • snapshot − 当前渲染的内容。

componentWillUnmount() −组件从 DOM 卸载后调用。这是清理对象的好地方。在我们的时钟示例中,我们可以在此阶段停止更新日期和时间。

componentDidMount() { 
   this.timeFn = setInterval( () => this.setTime(), 1000); 
}

shouldComponentUpdate() − 在更新阶段调用。用于指定组件是否应更新。如果返回 false,则不会发生更新。

签名如下 −

shouldComponentUpdate(nextProps, nextState)
  • nextProps − 组件的即将出现的属性

  • nextState − 组件的即将出现的状态

getDerivedStateFromProps − 在初始和更新阶段以及 render() 方法之前调用。它返回新的状态对象。在属性更改导致状态更改的情况下很少使用它。它主要用于动画上下文中,其中需要组件的各种状态来实现流畅的动画。

API 的签名如下 −

static getDerivedStateFromProps(props, state)
  • props − 组件的当前属性

  • state − 组件的当前状态

这是一个静态方法,无法访问 this 对象。

getSnapshotBeforeUpdate − 在渲染内容提交到 DOM 树之前调用。它主要用于获取有关新内容的一些信息。此方法返回的数据将传递给 ComponentDidUpdate() 方法。例如,它用于维护用户在新生成的内容中的滚动位置。它返回用户的滚动位置。此滚动位置由componentDidUpdate()用于设置输出在实际DOM中的滚动位置。

API的签名如下 −

getSnapshotBeforeUpdate(prevProps, prevState)
  • prevProps − 组件的先前属性。

  • prevState − 组件的先前状态。

生命周期API的工作示例

让我们在我们的react-clock-app应用程序中使用生命周期API。

步骤1 −在您最喜欢的编辑器中打开 react-clock-hook-app

打开 src/components/Clock.js 文件并开始编辑。

步骤 2 − 从构造函数中删除 setInterval() 方法。

constructor(props) { 
   super(props); 
   this.state = { 
      date: new Date() 
   } 
}

步骤 3 − 添加 componentDidMount() 方法并调用 setInterval() 每秒更新日期和时间。此外,存储引用以便稍后停止更新日期和时间。

componentDidMount() {
    this.setTimeRef = setInterval(() => this.setTime(), 1000);
}

添加 componentWillUnmount() 方法并调用 clearInterval() 以停止日期和时间更新调用。

componentWillUnmount() {
    clearInterval(this.setTimeRef)
}

现在,我们已经更新了 Clock 组件,该组件的完整源代码如下所示−

import React from 'react';

class Clock extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         date: new Date()
      }      
   }
   componentDidMount() {
      this.setTimeRef = setInterval(() => this.setTime(), 1000); 
   }
   componentWillUnmount() {
      clearInterval(this.setTimeRef)
   }
   setTime() {
      this.setState((state, props) => {
         console.log(state.date);
         return {
            date: new Date()
         }
      })
   }
   render() {
      return (
         <div>
            <p>The current time is {this.state.date.toString()}</p>
         </div>
      );
   }
}
export default Clock;

接下来,打开 index.js 并使用 setTimeout 在 5 秒后从 DOM 中删除时钟。

import React from 'react';
import ReactDOM from 'react-dom';
import Clock from './components/Clock';

ReactDOM.render(
   <React.StrictMode>
      <Clock />
   </React.StrictMode>,
   document.getElementById('root')
);
setTimeout(() => {
   ReactDOM.render(
      <React.StrictMode>
         <div><p>Clock is removed from the DOM.</p></div>
      </React.StrictMode>,
      document.getElementById('root')
   );
}, 5000);

使用 npm 命令为应用程序提供服务。

npm start

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

时钟将显示 5 秒,然后从 DOM 中删除。通过检查控制台日志,我们可以发现清理代码已正确执行。

Interface DOM

Expense Manager(费用管理器)应用程序中的生命周期 API

让我们在Expense Manager(费用管理器)中添加生命周期 API,并在调用该 API 时记录它。这将提供有关组件生命周期的见解。

步骤 1 −在您最喜欢的编辑器中打开 expense-manager 应用程序。

接下来,使用以下方法更新 ExpenseEntryItemList 组件。

componentDidMount() {
   console.log("ExpenseEntryItemList :: Initialize :: componentDidMount :: Component mounted");
}
shouldComponentUpdate(nextProps, nextState) {
   console.log("ExpenseEntryItemList :: Update :: shouldComponentUpdate invoked :: Before update");
   return true;
}
static getDerivedStateFromProps(props, state) {
   console.log("ExpenseEntryItemList :: Initialize / Update :: getDerivedStateFromProps :: Before update");
   return null;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
   console.log("ExpenseEntryItemList :: Update :: getSnapshotBeforeUpdate :: Before update");
   return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
   console.log("ExpenseEntryItemList :: Update :: componentDidUpdate :: Component updated");
}
componentWillUnmount() {
   console.log("ExpenseEntryItemList :: Remove :: componentWillUnmount :: Component unmounted");
}

步骤 2 − 使用 npm 命令为应用程序提供服务。

npm start

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

接下来,检查控制台日志。它将显示初始化阶段的生命周期 api,如下所示。

ExpenseEntryItemList :: Initialize / Update :: getDerivedStateFromProps :: Before update 
ExpenseEntryItemList :: Initialize :: componentDidMount :: Component mounted

删除一个项目,然后检查控制台日志。它将显示更新阶段的生命周期 api,如下所示。

ExpenseEntryItemList :: Initialize / Update :: getDerivedStateFromProps :: Before update 
ExpenseEntryItemList.js:109 ExpenseEntryItemList :: Update :: shouldComponentUpdate invoked :: Before update 
ExpenseEntryItemList.js:121 ExpenseEntryItemList :: Update :: getSnapshotBeforeUpdate :: Before update 
ExpenseEntryItemList.js:127 ExpenseEntryItemList :: Update :: componentDidUpdate :: Component updated

最后,删除所有生命周期 API,因为它们可能会影响应用程序性能。只有在情况需要时才应使用生命周期 API。

ReactJS - 事件管理

事件只是用户与任何应用程序交互时执行的一些操作。它们可以是最小的操作,例如将鼠标指针悬停在触发下拉菜单的元素上、调整应用程序窗口的大小或拖放元素以上传它们等。React 中的事件分为三类:

  • 鼠标事件 − onClick、onDrag、onDoubleClick

  • 键盘事件 − onKeyDown、onKeyPress、onKeyUp

  • 焦点事件 − onFocus、onBlur

对于这些事件中的每一个,JavaScript 都会提供响应。因此,每次用户执行事件时,通常都需要应用程序做出某种类型的反应;这些反应被定义为一些函数或代码块,称为事件处理程序。使用事件处理程序处理事件的整个过程称为事件管理

ReactJS 中的事件管理

事件管理是 Web 应用程序中的重要功能之一。它使用户能够与应用程序交互。React 支持 Web 应用程序中可用的所有事件。React 事件处理与 DOM 事件非常相似,只有很小的变化。以下是在基于 React 的网站中可以观察到的一些常见事件 −

  • 单击组件。

  • 滚动当前页面。

  • 将鼠标悬停在当前页面的元素上。

  • 提交表单。

  • 重定向到另一个网页。

  • 加载图像。

合成 React 事件

在 JavaScript 中,当指定事件时,您将处理称为合成事件的反应事件类型,而不是常规 DOM 事件。SyntheticEvent 是一个简单的跨浏览器包装器,用于本机事件实例,使事件在所有浏览器中的工作方式相同。所有事件处理程序都必须作为此包装器的实例传递。但是,由于创建的每个合成事件都需要进行垃圾收集,因此 CPU 资源消耗较大。每个合成事件对象都具有以下属性:

  • boolean bubbles

  • boolean cancelable

  • DOMEventTarget currentTarget

  • boolean defaultPrevented

  • number eventPhase

  • boolean isTrusted

  • DOMEvent nativeEvent

  • void preventDefault()

  • boolean isDefaultPrevented()

  • void stopPropagation()

  • boolean isPropagationStopped()

  • void persist()

  • DOMEventTarget target

  • number timeStamp

  • string type

由于合成事件使用大量资源,因此通常会重复使用它们,并且在调用事件回调后其所有属性都将被取消,以优化其在浏览器中的性能。 SyntheticEvent 具有与本机事件相同的接口。由于合成事件由文档节点授权,因此会先触发本机事件,然后再触发合成事件。

添加事件

正如我们已经看到的,React 具有与 HTML 相同的事件:单击、更改、鼠标悬停等。但是,React 事件使用驼峰式命名法定义,而反应则写在花括号内。在功能组件和类组件中添加事件的语法有所不同。

以下是在 React 的功能组件中添加 onClick 事件的语法:

onClick = {要执行的操作}

以下是在 React 的类组件中添加 onClick 事件的语法:

onClick = {this.action_to_be_performed}

处理事件

现在让我们通过以下分步过程学习如何在 React 应用程序中处理这些事件。

  • 定义事件处理程序方法来处理给定的事件。

log() {
    console.log("Event is fired");
}

React 提供了一种使用 lambda 函数定义事件处理程序的替代语法。lambda 语法是 −

log = () => { 
   console.log("Event is fired"); 
}

将参数传递给事件处理程序

有两种方法可以将参数传递给事件处理程序:

  • 箭头方法

  • 绑定方法

箭头方法

如果您想知道事件的目标,请在处理程序方法中添加参数e。React 会将事件目标详细信息发送到处理程序方法。

log(e) { 
   console.log("Event is fired"); 
   console.log(e.target); 
}

替代的 lambda 语法是 −

log = (e) => {
    console.log("Event is fired");
    console.log(e.target);
}

如果您想在事件期间发送额外详细信息,请将额外详细信息添加为初始参数,然后为事件目标添加参数 (e)

log(extra, e) { 
   console.log("Event is fired"); 
   console.log(e.target); 
   console.log(extra); 
   console.log(this); 
}

替代的 lambda 语法如下 −

log = (extra, e) => { 
   console.log("Event is fired"); 
   console.log(e.target); 
   console.log(extra); 
   console.log(this); 
}

绑定方法

我们还可以在组件的构造函数中绑定事件处理程序方法。这将确保事件处理程序方法中 this 的可用性。

constructor(props) {
    super(props);
    this.logContent = this.logContent.bind(this);
}

如果事件处理程序是在备用 lambda 语法中定义的,则不需要绑定。this 关键字将自动绑定到事件处理程序方法。

为特定事件设置事件处理程序方法,如下所示 −

<div onClick={this.log}> ... </div>

要设置额外参数,请绑定事件处理程序方法,然后将额外信息作为第二个参数传递。

<div onClick={this.log.bind(this, extra)}> ... </div>

替代 lambda 语法如下 −

<div onClick={this.log(extra, e)}> ... </div>

这里,

ReactJS - 创建事件感知组件

事件管理是 Web 应用程序中的重要功能之一。它使用户能够与应用程序交互。React 支持 Web 应用程序中可用的所有事件。React 事件处理与 DOM 事件非常相似,但变化不大。例如,单击组件是基于 React 的网站中可以观察到的常见事件之一。

React 中的事件感知组件只不过是一个包含事件处理程序方法的组件。该组件可以是类组件,也可以是功能组件。在本章中,我们将学习如何使用 React 创建此类事件感知组件。

如何创建事件感知组件?

以下是创建新事件感知组件的步骤 −

让我们创建一个新组件 MessageWithEvent 并在组件中处理事件,以更好地理解 React 应用程序中的事件管理。

步骤 1 −在您最喜欢的编辑器中打开 expense-manager 应用程序。

接下来,在 src/components 文件夹中创建一个文件 MessageWithEvent.js,以创建 MessageWithEvent 组件。

导入 React 库。

import React from 'react';

步骤 2 − 创建一个类 MessageWithEvent 并使用 props 调用构造函数。

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

步骤 3 − 创建一个事件处理程序方法 logEventToConsole,它将把事件详细信息记录到控制台。

logEventToConsole(e) {
    console.log(e.target.innerHTML);
}

步骤 4 − 创建一个 render 函数。

render() {
}

在 render() 函数中,创建一条问候消息并返回它。

render() {
   return (
      <div>
         <p>Hello {this.props.name}!</p>
      </div>
   );
}

步骤 5 − 然后,将 logEventToConsole 方法设置为根容器(div)的点击事件的事件处理程序。

render() {
   return (
      <div onClick={this.logEventToConsole}>
         <p>Hello {this.props.name}!</p>
      </div>
   );
}

步骤 6 − 通过绑定事件处理程序来更新构造函数。

class MessageWithEvent extends React.Component { 
   constructor(props) { 
      super(props); 
      this.logEventToConsole = this.logEventToConsole.bind(); 
   } 
}

最后,导出组件。

export default MessageWithEvent;

MessageWithEvent 组件的完整代码如下 −

import React from 'react';

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

      this.logEventToConsole = this.logEventToConsole.bind();
   }
   logEventToConsole(e) {
      console.log(e.target.innerHTML);
   }
   render() {
      return (
         <div onClick={this.logEventToConsole}>
            <p>Hello {this.props.name}!</p>
         </div>
      );
   }
}
export default MessageWithEvent;

index.js

接下来,打开 index.js 并导入 MessageWithEvent

import MessageWithEvent from './components/MessageWithEvent'

使用 MessageWithEvent 组件 构建应用程序的用户界面。

import React from 'react';
import ReactDOM from 'react-dom';
import MessageWithEvent from './components/MessageWithEvent'

ReactDOM.render(
   <React.StrictMode>
       <div>
            <MessageWithEvent name="React" />
            <MessageWithEvent name="React developer" />
      </div>
   </React.StrictMode>,
   document.getElementById('root')
);

使用 npm 命令为应用程序提供服务。

npm start

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

现在,单击 MessageWithEvent 组件,应用程序将在控制台中发出消息,如下所示。

React Modules

将额外信息传递给事件处理程序

让我们尝试将额外信息(例如 msgid)传递给事件处理程序。

步骤 1 −首先,更新 logEventToConsole 以接受额外参数 msgid

logEventToConsole(msgid, e) {
    console.log(e.target.innerHTML);
    console.log(msgid);
}

步骤 2 − 接下来,通过在 render 方法中绑定消息 ID,将消息 ID 传递给事件处理程序。

render() {
   return (
      <div onClick={this.logEventToConsole.bind(this, Math.floor(Math.random() * 10))}>
         <p>Hello {this.props.name}!</p>
      </div>
   );
}

步骤 3 − 完整更新后的代码如下−

import React from 'react';

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

      this.logEventToConsole = this.logEventToConsole.bind();
   }
   logEventToConsole(msgid, e) {
      console.log(e.target.innerHTML);
      console.log(msgid);
   }
   render() {
      return (
         >div onClick={this.logEventToConsole.bind(this, Math.floor(Math.random() * 10))}>
            >p>Hello {this.props.name}!>/p>
         >/div>
      );
   }
}
export default MessageWithEvent;

运行应用程序,你会发现事件在控制台中发出消息 id。

React Module

在 Expense Manager(费用管理器)应用程序中引入事件

在前面的章节中,我们了解到事件只是用户与任何应用程序交互时执行的一些操作。它们可以是最小的操作,例如将鼠标指针悬停在触发下拉菜单的元素上、调整应用程序窗口的大小或拖放元素以上传它们等。

对于这些事件中的每一个,JavaScript 都提供响应。因此,每次用户执行事件时,通常都需要应用程序做出某种类型的反应;这些反应被定义为一些函数或代码块,称为事件处理程序

为了更好地理解事件处理,让我们尝试将事件引入示例应用程序( Expense Manager(费用管理器)应用程序)。在本章中,我们尝试处理 Expense Manager(费用管理器)应用程序中的鼠标事件。鼠标事件只是用户使用鼠标执行的一些操作。这些包括悬停、单击、拖动或任何可以使用鼠标在应用程序上执行的操作。

处理 Expense Manager(费用管理器)应用程序中的事件

让我们在费用应用程序中进行一些事件管理。当用户将光标移到费用条目上时,我们可以尝试突出显示表中的费用条目。

步骤 1 − 在您最喜欢的编辑器中打开 expense-manager 应用程序。

打开 ExpenseEntryItemList.js 文件并添加方法 handleMouseEnter 来处理当用户将鼠标指针移动到费用项目(td - 表格单元格)时触发的事件(onMouseEnter)。

handleMouseEnter(e) { 
   e.target.parentNode.classList.add("highlight"); 
}

此处,

  • 事件处理程序尝试使用 parentNode 方法查找事件目标 (td) 节点的父节点 (tr)parentNode 方法是查找当前节点的直接父节点的标准 DOM 方法。

  • 找到父节点后,事件处理程序访问附加到父节点的 css 类列表,并使用 add 方法添加"highlight"类。classList 是获取附加到节点的类列表的标准 DOM 属性,可用于从 DOM 节点添加/删除类。

步骤 2 −接下来,添加一个方法 handleMouseLeave() 来处理用户移出费用项目时触发的事件。

handleMouseLeave(e) {
    e.target.parentNode.classList.remove("highlight");
}

在这里,事件处理程序从 DOM 中删除 highlight 类。

添加另一个方法 handleMouseOver() 来检查鼠标当前的位置。在 DOM 中找到鼠标指针的位置是可选的。

handleMouseOver(e) {
    console.log("The mouse is at (" + e.clientX + ", " + e.clientY + ")"); 
}

步骤 3 − 在组件的构造函数中绑定所有事件处理程序。

this.handleMouseEnter = this.handleMouseEnter.bind();
this.handleMouseLeave = this.handleMouseLeave.bind();
this.handleMouseOver = this.handleMouseOver.bind();

步骤 4 − 在 render 方法中将事件处理程序附加到相应的标记。

render() {
   const lists = this.props.items.map((item) =>
      <tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
         <td>{item.name}</td>
         <td>{item.amount}</td>
         <td>{new Date(item.spendDate).toDateString()}</td>
         <td>{item.category}</td>
      </tr>
   );
   return (
      <table onMouseOver={this.handleMouseOver}>
         <thead>
            <tr>
               <th>Item</th>
               <th>Amount</th>
               <th>Date</th>
               <th>Category</th>
            </tr>
         </thead>
         <tbody>
            {lists}
         </tbody>
      </table>
   );
}

ExpenseEntryItemList 的最终完整代码如下 −

import React from 'react';
import './ExpenseEntryItemList.css';

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

      this.handleMouseEnter = this.handleMouseEnter.bind();
      this.handleMouseLeave = this.handleMouseLeave.bind();
      this.handleMouseOver = this.handleMouseOver.bind();
   }
   handleMouseEnter(e) {
      e.target.parentNode.classList.add("highlight");
   }
   handleMouseLeave(e) {
      e.target.parentNode.classList.remove("highlight");
   }
   handleMouseOver(e) {
      console.log("The mouse is at (" + e.clientX + ", " + e.clientY + ")");
   }
   render() {
      const lists = this.props.items.map((item) =>
         <tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
            <td>{item.name}</td>
            <td>{item.amount}</td>
            <td>{new Date(item.spendDate).toDateString()}</td>
            <td>{item.category}</td>
         </tr>
      );
      return (
         <table onMouseOver={this.handleMouseOver}>
            <thead>
               <tr>
                  <th>Item</th>
                  <th>Amount</th>
                  <th>Date</th>
                  <th>Category</th>
               </tr>
            </thead>
            <tbody>
               {lists}
            </tbody>
         </table>
      );
   }
}
export default ExpenseEntryItemList;

ExpenseEntryItemList.css:

接下来,打开 css 文件 ExpenseEntryItemList.css 并添加 css 类 highlight

tr.highlight td {
    background-color: #a6a8bd;
}

index.js

打开 index.js 并使用 ExpenseEntryItemList 组件。

import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemList from './components/ExpenseEntryItemList'

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" }
]
ReactDOM.render(
   <React.StrictMode>
      <ExpenseEntryItemList items={items} />
   </React.StrictMode>,
   document.getElementById('root')
);

使用 npm 命令为应用程序提供服务。

npm start

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

应用程序将响应鼠标事件并突出显示当前选定的行。

Item Amount Date Category
Pizza 80 Sat Oct 10 2020 Food
Grape Juice 30 Man Oct 12 2020 Food
Cinema 210 Fri Oct 16 2020 Entertainment
Java Programming book 242 Thu Oct 15 2020 Academic
Mango Juice 35 Fri Oct 16 2020 Food
Dress 2000 Sun Oct 25 2020 Cloth
Tour 2555 Thu Oct 29 2020 Entertainment
Meals 300 Fri Oct 30 2020 Food
Mobile 3500 Mon Nov 02 2020 Gadgets
Exam Fees 1245 Wed Nov 04 2020 Academic

ReactJS - 状态管理

状态管理是任何动态应用程序的重要且不可避免的功能之一。React 提供了一个简单而灵活的 API 来支持 React 组件中的状态管理。本章让我们了解如何在 React 应用程序中维护状态。

什么是状态?

状态 表示给定实例中 React 组件的动态属性的值。React 为每个组件提供动态数据存储。内部数据表示 React 组件的状态,可以使用组件的 this.state 成员变量进行访问。每当组件的状态发生变化时,组件都会通过调用 render() 方法以及新状态重新渲染自身。

一个可以更好地理解状态管理的简单示例是分析实时时钟组件。时钟组件的主要工作是在给定实例中显示位置的日期和时间。由于当前时间每秒都会变化,因此时钟组件应在其状态中维护当前日期和时间。由于时钟组件的状态每秒都会变化,因此时钟的 render() 方法将每秒调用一次,并且 render() 方法使用其当前状态显示当前时间。

状态的简单表示如下 −

{ 
   date: '2020-10-10 10:10:10' 
}

让我们在无状态组件一章中创建一个新的Clock组件。

定义状态

React 中的状态可以与功能组件和类组件一起使用。要使用组件中的状态,必须有一个起点,即初始状态。组件的初始状态必须在组件类的构造函数中定义。以下是定义任何类的状态的语法 −

state = {attribute: "value"};

让我们看一个具有初始状态的类组件的示例代码 −

Class SampleClass extends React.Component
{
    constructor(props)
    {
        super(props);
        this.state = { name : "John Doe" };
    }
}

创建状态对象

React 组件具有内置状态对象。状态对象用于存储属于定义此状态的组件的所有属性值。当状态对象发生更改时,组件会重新渲染。

让我们看一个示例代码来演示如何在 React 中创建状态对象。

Class BookClass extends React.Component
{
   constructor(props)
   {
      super(props);
      this.state = { name : "John Doe" };
   }
   render() {
      return (
      <div>
         <h1>Name of the Author</h1>
      </div>
      );
   }
}

为了更好地理解状态管理,请查看以下章节。

ReactJS - 状态管理 API

正如我们之前所了解的,React 组件通过组件的 this.state 维护和公开其状态。React 提供了一个 API 来维护组件中的状态。该 API 是 this.setState()。它接受 JavaScript 对象或返回 JavaScript 对象的函数。

setState() 用于更新组件的状态对象。这是通过安排对该组件的状态对象的更新来完成的。因此,当状态发生变化时,此组件会通过重新渲染来响应。

setState API 的签名如下 −

this.setState( { ... object ...} );

设置/更新名称的一个简单示例如下 −

this.setState( { name: 'John' } )

setState() with Function

setState with function 的签名如下 −

this.setState( (state, props) => 
   ... function returning JavaScript object ... );

此处,

  • state 指的是 React 组件的当前状态

  • props 指的是 React 组件的当前属性。

React 建议使用带函数的 setState API,因为它在异步环境中可以正常工作。除了 lambda 函数,还可以使用普通的 JavaScript 函数。

this.setState( function(state, props) {
    return ... JavaScript object ...
}

示例

使用函数更新金额的简单示例如下 −

this.setState( (state, props) => ({ 
   amount: this.state.amount + this.props.additionaAmount 
})

不应直接通过 this.state 成员变量修改 React 状态,并且通过成员变量更新状态不会重新渲染组件。

React State API 的特殊功能

React State API 的一个特殊功能是它将与现有状态合并,而不是替换状态。例如,我们可以一次更新任何一个状态字段,而不是更新整个对象。此功能使开发人员能够灵活地轻松处理状态数据。

例如,让我们考虑内部状态包含一个学生记录。

{
    name: 'John', age: 16
}

我们可以使用 setState API 仅更新年龄,这将自动将新对象与现有的学生记录对象合并。

this.setState( (state, props) => ({ 
   age: 18 
});

ReactJS - 无状态组件

具有内部状态的 React 组件称为有状态组件,没有任何内部状态管理的 React 组件称为无状态组件。React 建议尽可能多地创建和使用无状态组件,仅在绝对必要时才创建有状态组件。此外,React 不与子组件共享状态。数据需要通过子组件的属性传递给子组件。

将日期传递给 FormattedDate 组件的示例如下 −

<FormattedDate value={this.state.item.spend_date} />

总体思路是不要让应用程序逻辑过于复杂,只在必要时使用高级功能。

创建有状态组件

让我们创建一个 React 应用程序来显示当前日期和时间。

步骤 1 − 首先,按照创建 React 应用程序一章中的说明,使用 Create React AppRollup 捆绑器创建一个新的 React 应用程序 react-clock-app

在您最喜欢的编辑器中打开该应用程序。

步骤 2 −在应用程序的根目录下创建 src 文件夹。

在 src 文件夹下创建 components 文件夹。

src/components 文件夹下创建文件 Clock.js 并开始编辑。

导入 React 库。

import React from 'react';

接下来,创建 Clock 组件。

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

步骤 3 − 使用当前日期和时间初始化状态。

constructor(props) { 
   super(props); 
   this.state = { 
      date: new Date() 
   } 
}

步骤 4 − 添加方法 setTime() 来更新当前时间 −

setTime() { 
   console.log(this.state.date); 
   this.setState((state, props) => (
      {
         date: new Date() 
      } 
   )) 
}

步骤 5 − 使用 JavaScript 方法 setInterval 并每秒调用 setTime() 方法,以确保组件的状态每秒更新一次。

constructor(props) { 
   super(props); 
   this.state = { 
      date: new Date() 
   } 
   setInterval( () => this.setTime(), 1000); 
}

步骤 6 − 创建一个 render 函数。

render() {
}
Next, update the render() method to show the current time.
render() {
   return (
      <div><p>The current time is {this.state.date.toString()}</p></div>
   );
}

最后,导出组件。

export default Clock;

Clock组件的完整源代码如下 −

import React from 'react';

class Clock extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         date: new Date()
      }      
      setInterval( () => this.setTime(), 1000);
   }
   setTime() {
      console.log(this.state.date);
      this.setState((state, props) => (
         {
            date: new Date()
         }
      ))
   }
   render() {
      return (
         <div>
            <p>The current time is {this.state.date.toString()}</p>
         </div>
      );
   }
}
export default Clock;

index.js

接下来,在 src 文件夹下创建一个文件 index.js 并使用 Clock 组件。

import React from 'react';
import ReactDOM from 'react-dom';
import Clock from './components/Clock';

ReactDOM.render(
   <React.StrictMode>
      <Clock />
   </React.StrictMode>,
   document.getElementById('root')
);

index.html

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

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

使用 npm 命令为应用程序提供服务。

npm start

打开浏览器,在地址栏中输入 http://localhost:3000,然后按回车键。应用程序将显示时间并每秒更新一次。

The current time is Wed Nov 11 2020 10:10:18 GMT+0530(Indian Standard Time)

上述应用程序运行正常但在控制台中抛出错误。

Can't call setState on a component that is not yet mounted.

错误消息表明只有在安装组件后才需要调用 setState。

什么是安装?

React 组件具有生命周期,安装是生命周期中的一个阶段。让我们在接下来的章节中了解有关生命周期的更多信息。

在Expense Manager(费用管理器)应用中引入状态

让我们通过添加一个简单的功能来删除费用项目,在Expense Manager(费用管理器)应用中引入状态管理。

步骤 1 − 在您最喜欢的编辑器中打开 expense-manager 应用程序。

打开 ExpenseEntryItemList.js 文件。

使用通过属性传递到组件中的费用项目初始化组件的状态。

this.state = { 
   items: this.props.items 
}

步骤 2 − 在 render() 方法中添加 Remove 标签。

<thead>
   <tr>
      <th>Item</th>
      <th>Amount</th>
      <th>Date</th>
      <th>Category</th>
      <th>Remove</th>
   </tr>
</thead>

步骤 3 − 更新 render() 方法中的列表以包含删除链接。此外,使用状态 (this.state.items) 中的项目,而不是属性 (this.props.items) 中的项目。

const lists = this.state.items.map((item) =>
   <tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
      <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>
);

步骤 4 − 实现 handleDelete 方法,该方法将从状态中删除相关费用项目。

handleDelete = (id, e) => {
   e.preventDefault();
   console.log(id);

   this.setState((state, props) => {
      let items = [];

      state.items.forEach((item, idx) => {
         if(item.id != id)
            items.push(item)
      })
      let newState = {
         items: items
      }
      return newState;
   })
}

此处,

    从组件的当前状态获取费用项目。

    循环遍历当前费用项目,使用项目的 ID 查找用户引用的项目。

    创建一个新的项目列表,其中包含除用户引用的费用项目之外的所有费用项目

步骤 5 − 添加新行以显示总费用金额。

<tr>
   <td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
   <td colSpan="4" style={{ textAlign: "left" }}>
      {this.getTotal()}
   </td> 
</tr>

步骤 6 − 实现 getTotal() 方法计算总支出金额。

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

render()方法完整代码如下 −

render() {
   const lists = this.state.items.map((item) =>
      <tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
         <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 (
      <table onMouseOver={this.handleMouseOver}>
         <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>
   );
}

最后,ExpenseEntryItemList更新后的代码如下 −

import React from 'react';
import './ExpenseEntryItemList.css';

class ExpenseEntryItemList extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         items: this.props.items
      }
      this.handleMouseEnter = this.handleMouseEnter.bind();
      this.handleMouseLeave = this.handleMouseLeave.bind();
      this.handleMouseOver = this.handleMouseOver.bind();
   }
   handleMouseEnter(e) {
      e.target.parentNode.classList.add("highlight");
   }
   handleMouseLeave(e) {
      e.target.parentNode.classList.remove("highlight");
   }
   handleMouseOver(e) {
      console.log("The mouse is at (" + e.clientX + ", " + e.clientY + ")");
   }
   handleDelete = (id, e) => {
      e.preventDefault();
      console.log(id);
      this.setState((state, props) => {
         let items = [];
         state.items.forEach((item, idx) => {
            if(item.id != id)
               items.push(item)
         })
         let newState = {
            items: items
         }
         return newState;
      })
   }
   getTotal() {
      let total = 0;
      for(var i = 0; i < this.state.items.length; i++) {
         total += this.state.items[i].amount
      }
      return total;
   }
   render() {
      const lists = this.state.items.map((item) =>
         <tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
            <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 (
         <table onMouseOver={this.handleMouseOver}>
            <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>
      );
   }
}
export default ExpenseEntryItemList;

index.js

更新 index.js 并包含 ExpenseEntyItemList 组件。

import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemList from './components/ExpenseEntryItemList'

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" }
]
ReactDOM.render(
   <React.StrictMode>
      <ExpenseEntryItemList items={items} />
   </React.StrictMode>,
   document.getElementById('root')
);

使用 npm 命令为应用程序提供服务。

npm start

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

最后,要删除费用项目,请单击相应的删除链接。它将删除相应的项目并刷新用户界面,如动画 gif 所示。

Interface

ReactJS - 使用 React Hooks 进行状态管理

React 从 React 16.8 开始引入了一个全新的概念,称为 React Hooks。尽管这是一个相对较新的概念,但它使 React 功能组件能够拥有自己的状态和生命周期。此外,React Hooks 使功能组件能够使用许多以前不可用的功能。让我们在本章中了解如何使用 React Hooks 在功能组件中进行状态管理。

什么是 React Hooks?

React Hooks 是 React 提供的特殊函数,用于处理 React 功能组件内的特定功能。React 为每个支持的功能提供了一个 Hook 函数。例如,React 提供 useState() 函数来管理功能组件中的状态。当 React 函数式组件使用 React Hooks 时,React Hooks 会将自身附加到组件中并提供附加功能。

useState() Hook 的通用签名如下 −

const [<state variable>, <state update function>] = useState(<initial value>);

例如,使用 Hooks 进行时钟组件中的状态管理可以按如下方式进行 −

const [currentDateTime, setCurrentDateTime] = useState(new Date());
setInterval(() => setCurrentDateTime(new Date()), 1000);

此处,

  • currentDateTime − 用于保存当前日期和时间的变量(由 setState() 返回)
  • setCurrentDate() − 用于设置当前日期和时间的函数(由 setState() 返回)

创建有状态组件

让我们在本章中使用 Hooks 重新创建时钟组件。

步骤 1 − 首先,按照创建 React 应用程序一章中的说明,使用 Create React AppRollup bundler 创建一个新的 React 应用程序 react-clock-hook-app

步骤 2 −在您最喜欢的编辑器中打开应用程序。

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

src 文件夹下创建 components 文件夹。

src/components 文件夹下创建文件 Clock.js 并开始编辑。

导入 React 库 和 React 状态 Hook,setState

import React, { useState } from 'react';

步骤 2 −创建 Clock 组件。

function Clock() {
}

创建状态Hooks(钩子)来维护日期和时间。

const [currentDateTime, setCurrentDateTime] = useState(new Date());

设置每秒的日期和时间。

setInterval(() => setCurrentDateTime(new Date()), 1000);

使用 currentDateTime 创建用户界面以显示当前日期和时间并返回它。

return ( <div><p>当前时间为 {currentDateTime.toString()}</p></div> );

步骤 3 − 最后,使用代码片段 − 导出组件

export default Clock;

Clock 组件的完整源代码如下 −

import React, { useState } from 'react';

function Clock(props) {
   const [currentDateTime, setCurrentDateTime] = useState(new Date());
   setInterval(() => setCurrentDateTime(new Date()), 1000);
   return (
      <div><p>The current time is {currentDateTime.toString()}</p></div>
   );
}
export default Clock;

index.js:

接下来,在 src 文件夹下创建一个文件 index.js,并使用 Clock 组件

import React from 'react';
import ReactDOM from 'react-dom';
import Clock from './components/Clock';

ReactDOM.render(
   <React.StrictMode>
      <Clock />
   </React.StrictMode>,
   document.getElementById('root')
);

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

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

然后,使用 npm 命令为应用程序提供服务。

npm start

打开浏览器,在地址栏中输入 http://localhost:3000,然后按回车键。应用程序将显示时间并每秒更新一次。

The current time is Wed Nov 11 2020 10:10:18 GMT+0530 (India Standard Time)

上述应用程序运行良好。但是,设置为每秒执行的 setCurrentDateTime() 必须在应用程序结束时删除。我们可以使用 React 提供的另一个 Hook,useEffect 来做到这一点。我们将在下一章(组件生命周期)中学习它。

在Expense Manager(费用管理器)应用程序中引入状态

让我们通过在本章中添加一个简单的功能来使用 Hooks 删除费用项目,从而在Expense Manager(费用管理器)应用程序中引入状态管理。

步骤 1 −在您最喜欢的编辑器中打开 expense-manager 应用程序。

src/components 文件夹下创建一个新文件 ExpenseEntryItemListFn.js 并开始编辑。

导入 React 库和 React 状态 Hook,setState

import React, { useState } from 'react';

导入 css,ExpenseEntryItem.css

import './ExpenseEntryItemList.css'

步骤 2 −创建 ExpenseEntryItemListFn 组件。

function ExpenseEntryItemListFn(props) { }

使用通过属性传递到组件中的费用项目初始化组件的状态挂钩。

const [items, setItems] = useState(props.items);

步骤 3 − 创建事件处理程序以突出显示行。

function handleMouseEnter(e) {
   e.target.parentNode.classList.add("highlight");
}
function handleMouseLeave(e) {
   e.target.parentNode.classList.remove("highlight");
}
function handleMouseOver(e) {
   console.log("The mouse is at (" + e.clientX + ", " + e.clientY + ")");
}

步骤 4 − 使用 items 和 setItems() 创建事件处理程序以删除选定的项目。

function handleDelete(id, e) {
   e.preventDefault();
   console.log(id);
   let newItems = [];
   items.forEach((item, idx) => {
      if (item.id != id)
         newItems.push(item)
   })
   setItems(newItems);
}

步骤 5 − 创建 getTotal() 方法来计算总金额。

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

步骤 6 − 通过循环遍历项目来创建用户界面以显示费用。

const lists = items.map((item) =>
   <tr key={item.id} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
      <td>{item.name}</td>
      <td>{item.amount}</td>
      <td>{new Date(item.spendDate).toDateString()}</td>
      <td>{item.category}</td>
      <td><a href="#" onClick={(e) => handleDelete(item.id, e)}>Remove</a></td>
   </tr>
);

步骤 7 − 创建完整的 UI 来显示费用并返回。

return (
   <table onMouseOver={handleMouseOver}>
      <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" }}>
               {getTotal()}
            </td>
         </tr>
      </tbody>
   </table>
);

最后,导出函数如下 −

export default ExpenseEntryItemListFn;

ExpenseEntryItemListFn 的完整代码如下 −

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

function ExpenseEntryItemListFn(props) {
   const [items, setItems] = useState(props.items);

   function handleMouseEnter(e) {
      e.target.parentNode.classList.add("highlight");
   }
   function handleMouseLeave(e) {
      e.target.parentNode.classList.remove("highlight");
   }
   function handleMouseOver(e) {
      console.log("The mouse is at (" + e.clientX + ", " + e.clientY + ")");
   }
   function handleDelete(id, e) {
      e.preventDefault();
      console.log(id);
      let newItems = [];
      items.forEach((item, idx) => {
         if (item.id != id)
            newItems.push(item)
      })
      setItems(newItems);
   }
   function getTotal() {
      let total = 0;
      for (var i = 0; i < items.length; i++) {
         total += items[i].amount
      }
      return total;
   }
   const lists = items.map((item) =>
      <tr key={item.id} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
         <td>{item.name}</td>
         <td>{item.amount}</td>
         <td>{new Date(item.spendDate).toDateString()}</td>
         <td>{item.category}</td>
         <td><a href="#"
            onClick={(e) => handleDelete(item.id, e)}>Remove</a></td>
      </tr>
   );
   return (
      <table onMouseOver={handleMouseOver}>
         <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" }}>
                  {getTotal()}
               </td>
            </tr>
         </tbody>
      </table>
   );
}
export default ExpenseEntryItemListFn;

index.js

更新 index.js 并包含 ExpenseEntyItemListFn 组件 −

import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemListFn from './components/ExpenseEntryItemListFn'

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" }
]
ReactDOM.render(
   <React.StrictMode>
      <ExpenseEntryItemListFn items={items} />
   </React.StrictMode>,
   document.getElementById('root')
);

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

npm start

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

最后,要删除费用项目,请单击相应的删除链接。它将删除相应的项目并刷新用户界面,如动画 gif 所示。

Interface

ReactJS - 使用 React Hooks 的组件生命周期

React Hooks 提供了一个特殊的 Hook,useEffect(),用于在组件的生命周期中执行某些功能。useEffect()componentDidMount、componentDidUpdatecomponentWillUnmount 生命周期合并为一个 api。

useEffect() api 的签名如下 −

useEffect(
   <executeFn>, 
   <values>
);

此处,

  • executeFn − 当效果发生时执行的函数,带有可选的返回函数。当需要清理时,将执行返回函数(类似于 componentWillUnmount)。

  • values − 效果所依赖的值数组。React Hooks 仅在值发生变化时执行 executeFn。这将减少对 executeFn 的不必要调用。

让我们在 react-clock-hook-app 应用程序中添加 useEffect()Hooks。

在您最喜欢的编辑器中打开 react-clock-hook-app

接下来,打开 src/components/Clock.js 文件并开始编辑。

接下来,导入 useEffect api

import React, { useState, useEffect } from 'react';

接下来,使用 setInterval 函数调用 useEffect 每秒设置日期和时间,并返回一个函数,使用 clearInterval 停止更新日期和时间。

useEffect(
   () => {
      let setTime = () => {
         console.log("setTime is called");
         setCurrentDateTime(new Date());
      }
      let interval = setInterval(setTime, 1000);
      return () => {
         clearInterval(interval);
      }
   },
   []
);

这里,

  • 创建一个函数 setTime,将当前时间设置为组件的状态。

  • 调用 setInterval JavaScript api 每秒执行 setTime,并将 setInterval 的引用存储在 interval 变量中。

  • 创建一个返回函数,该函数通过传递 interval 引用 来调用 clearInterval api 以停止每秒执行 setTime

现在,我们已经更新了 Clock 组件,该组件的完整源代码如下 −

import React, { useState, useEffect } from 'react';

function Clock() {
   const [currentDateTime, setCurrentDateTime] = useState(new Date());
   useEffect(
      () => {
         let setTime = () => {
            console.log("setTime is called");
            setCurrentDateTime(new Date());
         }
         let interval = setInterval(setTime, 1000);
         return () => {
            clearInterval(interval);
         }
      },
      []
   );
   return (
      <div>
         <p>The current time is {currentDateTime.toString()}</p>
      </div>
   );
}
export default Clock;

接下来,打开 index.js 并使用 setTimeout 在 5 秒后从 DOM 中删除时钟。

import React from 'react';
import ReactDOM from 'react-dom';
import Clock from './components/Clock';

ReactDOM.render(
   <React.StrictMode>
      <Clock />
   </React.StrictMode>,
   document.getElementById('root')
);
setTimeout(() => {
   ReactDOM.render(
      <React.StrictMode>
         <div><p>Clock is removed from the DOM.</p></div>
      </React.StrictMode>,
      document.getElementById('root')
   );
}, 5000);

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

npm start

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

时钟将显示 5 秒,然后从 DOM 中删除。通过检查控制台日志,我们可以发现清理代码已正确执行。

清理代码

React children 属性又名 Containment

React 允许将任意子用户界面内容包含在组件内。可以通过 this.props.children 访问组件的子项。在组件内添加子项称为 containment。 包含用于组件的某些部分本质上是动态的情况。

例如,富文本消息框可能直到被调用时才知道其内容。让我们创建 RichTextMessage 组件来展示本章中 React children 属性的功能。

首先,按照 创建 React 应用程序 一章中的说明,使用 Create React AppRollup bundler 创建一个新的 React 应用程序 react-message-app

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

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

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

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

接下来,导入 React 库。

import React from 'react';

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

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

接下来,添加 render() 方法并显示组件及其子组件的用户界面

render() { 
   return ( 
      <div>{this.props.children}</div> 
   ) 
}

这里,

  • props.children 返回组件的子项。

  • 将子项包装在 div 标签内。

最后,导出组件。

export default RichTextMessage;

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

import React from 'react';

class RichTextMessage extends React.Component {
   constructor(props) {
      super(props);
   }
   render() {
      return (
         <div>{this.props.children}</div>
      )
   }
}
export default RichTextMessage;

接下来,在 src 文件夹下创建一个文件 index.js,并使用 RichTextMessage 组件。

import React from 'react';
import ReactDOM from 'react-dom';
import RichTextMessage from './components/RichTextMessage';

ReactDOM.render(
   <React.StrictMode>
      <RichTextMessage>
         <h1>Containment is really a cool feature.</h1>
      </RichTextMessage>
   </React.StrictMode>,
   document.getElementById('root')
);

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

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="utf-8">
      <title>React 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。

Cleanup Codes

浏览器发出包裹在 div 标签中的组件子项,如下所示 −

<div id="root">
   <div>
      <div>
         <h1>Containment is really a cool feature.</h1>
      </div>
   </div>
</div>

接下来,更改 index.js 中 RichTextMessage 组件的 child 属性。

import React from 'react';
import ReactDOM from 'react-dom';
import Clock from './components/Clock';

ReactDOM.render(
  <React.StrictMode>
         <RichTextMessage>
            <h1>Containment is really an excellent feature.</h1>
         </RichTextMessage>
   </React.StrictMode>,
   document.getElementById('root')
);

现在,浏览器更新组件的子内容并发出如下所示的内容 −

<div id="root">
    <div>
        <div>
            <h1>Containment is really an excellent feature.</h1>
        </div>
    </div>
</div>

简而言之,包含是将任意用户界面内容传递给组件的绝佳特性。

ReactJS - 组件中的布局

React 的高级功能之一是它允许使用属性将任意用户界面 (UI) 内容传递到组件中。与 React 的特殊 children 属性(仅允许将单个用户界面内容传递到组件中)相比,此选项允许将多个 UI 内容传递到组件中。此选项可视为 children 属性的扩展。该选项的一个用例是布局组件的用户界面。

例如,具有可自定义页眉和页脚的组件可以使用此选项通过属性获取自定义页眉和页脚并布局内容。

示例

下面给出了一个包含两个属性 header 和 footer 的快速简单示例

<Layout header={<h1>Header</h1>} footer={<p>footer</p>} />

布局渲染逻辑如下 −

return (<div>
   <div> 
      {props.header} 
   </div> 
   <div> 
      Component user interface 
   </div> 
   <div> 
      {props.footer} 
   </div> 
</div>)

让我们为费用条目列表 (ExpenseEntryItemList) 组件添加一个简单的页眉和页脚。

在您最喜欢的编辑器中打开 expense-manager 应用程序。

接下来,打开 src/components 文件夹中的文件 ExpenseEntryItemList.js

接下来,在 render() 方法中使用 header 和 footer 属性。

return (
   <div> 
      <div>{this.props.header}</div> 
         ... existing code ... 
      <div>{this.props.footer}</div> 
   </div> 
);

接下来,打开 index.js 并在使用 ExpenseEntryItemList 组件时包含 headerfooter 属性。

ReactDOM.render(
   <React.StrictMode>
      <ExpenseEntryItemList items={items}
         header={
            <div><h1> Expense Manager </h1></div>
         }
         footer={
            <div style={{ textAlign: "left" }}>
               <p style={{ fontSize: 12 }}>Sample application</p>
            </div>
         } 
      />
   </React.StrictMode>,
   document.getElementById('root')
);

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

npm start

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

Interface

在组件中共享逻辑,又称渲染道具

渲染道具 是一个用于在 React 组件之间共享逻辑的高级概念。正如我们之前所了解的,组件可以通过属性接收任意 UI 内容或 React 元素(对象)。通常,组件会按原样渲染它接收到的 React 元素以及它自己的用户界面,正如我们在 children 和布局概念中看到的那样。它们之间不共享任何逻辑。

更进一步,React 允许组件通过属性采用返回用户界面而不是普通用户界面对象的函数。该函数的唯一目的是渲染 UI。然后,组件将进行高级计算,并调用传入的函数以及计算值来渲染 UI。

简而言之,组件的属性接受渲染用户界面的 JavaScript 函数,称为 Render Props。接收 Render Props 的组件将执行高级逻辑并将其与 Render Props 共享,后者将使用共享逻辑渲染用户界面。

许多高级第三方库都基于 Render Props。一些使用 Render Props 的库是 −

  • React Router
  • Formik
  • Downshift

例如,Formik 库组件将执行表单验证和提交,并将表单设计传递给调用函数,即 Render Props。类似地,React Router 执行路由逻辑,同时使用 Render Props 将 UI 设计委托给其他组件。

ReactJS - 分页

React 通过第三方 UI 组件库提供分页组件。React 社区提供了大量的 UI / UX 组件,很难选择适合我们需求的库。Bootstrap UI 库是开发人员的热门选择之一,被广泛使用。 React Bootstrap (https://react-bootstrap.github.io/) 已移植几乎所有 bootstrap UI 组件,并且对 Pagination 组件也提供了最佳支持。

在本章中,我们将学习如何使用 react-bootstrap 库中的 Pagination 组件。

Pagination 组件

Pagination 组件允许开发人员在 Web 应用程序中使用 bootstrap 设计创建简单的分页。分页组件接受以下组件。

  • Pagination.Item

  • Pagination.First

  • Pagination.Last

  • Pagination.Previous

  • Pagination.Next

Pagination 组件接受一小组 props 来自定义分页组件,它们如下 −

  • size (sm | lg)

  • 设置分页按钮的大小

  • bsPrefix (string)

  • 允许更改底层组件CSS

Pagination.Item 组件接受一小组 props 来自定义分页组件,它们如下 −

  • active (boolean)

  • 将项目设置为活动项目,不呈现标签。

  • activeLabel (string)

  • 用于指示分页项目状态的标签

  • disabled (boolean)

  • 禁用分页项目

  • href (string)

  • 分页项目的链接

  • onClick (function)

  • 当 onClick 事件发生时要调用的回调函数fired

应用 Pagination 组件

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,使用以下命令安装 bootstrap 库,

npm install --save bootstrap react-bootstrap

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。

// 删除 css

接下来,创建一个简单的分页组件,SimplePagination (src/Components/SimplePagination.js)如下所示 −

import React from 'react';
import { Table, Pagination } from 'react-bootstrap';
class SimplePagination extends React.Component {
   render() {
      <div>Pagination component</div>
   }
}
export default SimplePagination;

接下来,在 public 文件夹下创建一个文件 users.json,并填充以下用户信息,

[
   {
      "id":1,
      "name":"Fowler",
      "age":18
   },
   {
      "id":2,
      "name":"Donnell",
      "age":24
   },
   {
      "id":3,
      "name":"Pall",
      "age":26
   },
   {
      "id":4,
      "name":"Christos",
      "age":19
   },
   {
      "id":5,
      "name":"Dud",
      "age":29
   },
   {
      "id":6,
      "name":"Rayner",
      "age":22
   },
   {
      "id":7,
      "name":"Somerset",
      "age":31
   },
   {
      "id":8,
      "name":"Stavros",
      "age":32
   },
   {
      "id":9,
      "name":"Cody",
      "age":19
   },
   {
      "id":10,
      "name":"Sharai",
      "age":19
   },
   {
      "id":11,
      "name":"Kristo",
      "age":28
   },
   {
      "id":12,
      "name":"Harvey",
      "age":27
   },
   {
      "id":13,
      "name":"Christen",
      "age":27
   },
   {
      "id":14,
      "name":"Hillard",
      "age":19
   },
   {
      "id":15,
      "name":"Jaine",
      "age":32
   },
   {
      "id":16,
      "name":"Annabel",
      "age":29
   },
   {
      "id":17,
      "name":"Hildagarde",
      "age":29
   },
   {
      "id":18,
      "name":"Cherlyn",
      "age":18
   },
   {
      "id":19,
      "name":"Herold",
      "age":32
   },
   {
      "id":20,
      "name":"Gabriella",
      "age":32
   },
   {
      "id":21,
      "name":"Jessalyn",
      "age":32
   },
   {
      "id":22,
      "name":"Opal",
      "age":31
   },
   {
      "id":23,
      "name":"Westbrooke",
      "age":27
   },
   {
      "id":24,
      "name":"Morey",
      "age":22
   },
   {
      "id":25,
      "name":"Carleton",
      "age":26
   },
   {
      "id":26,
      "name":"Cosimo",
      "age":22
   },
   {
      "id":27,
      "name":"Petronia",
      "age":23
   },
   {
      "id":28,
      "name":"Justino",
      "age":32
   },
   {
      "id":29,
      "name":"Verla",
      "age":20
   },
   {
      "id":30,
      "name":"Lanita",
      "age":18
   },
   {
      "id":31,
      "name":"Karlik",
      "age":23
   },
   {
      "id":32,
      "name":"Emmett",
      "age":22
   },
   {
      "id":33,
      "name":"Abran",
      "age":26
   },
   {
      "id":34,
      "name":"Holly",
      "age":23
   },
   {
      "id":35,
      "name":"Beverie",
      "age":23
   },
   {
      "id":36,
      "name":"Ingelbert",
      "age":27
   },
   {
      "id":37,
      "name":"Kailey",
      "age":30
   },
   {
      "id":38,
      "name":"Ralina",
      "age":26
   },
   {
      "id":39,
      "name":"Stella",
      "age":29
   },
   {
      "id":40,
      "name":"Ronnica",
      "age":20
   },
   {
      "id":41,
      "name":"Brucie",
      "age":20
   },
   {
      "id":42,
      "name":"Ryan",
      "age":22
   },
   {
      "id":43,
      "name":"Fredek",
      "age":20
   },
   {
      "id":44,
      "name":"Corliss",
      "age":28
   },
   {
      "id":45,
      "name":"Kary",
      "age":32
   },
   {
      "id":46,
      "name":"Kaylee",
      "age":21
   },
   {
      "id":47,
      "name":"Haskell",
      "age":25
   },
   {
      "id":48,
      "name":"Jere",
      "age":29
   },
   {
      "id":49,
      "name":"Kathryne",
      "age":31
   },
   {
      "id":50,
      "name":"Linnea",
      "age":21
   },
   {
      "id":51,
      "name":"Theresina",
      "age":24
   },
   {
      "id":52,
      "name":"Arabela",
      "age":32
   },
   {
      "id":53,
      "name":"Howie",
      "age":22
   },
   {
      "id":54,
      "name":"Merci",
      "age":21
   },
   {
      "id":55,
      "name":"Mitchel",
      "age":30
   },
   {
      "id":56,
      "name":"Clari",
      "age":18
   },
   {
      "id":57,
      "name":"Laurena",
      "age":19
   },
   {
      "id":58,
      "name":"Odessa",
      "age":30
   },
   {
      "id":59,
      "name":"Pippy",
      "age":25
   },
   {
      "id":60,
      "name":"Wilmar",
      "age":23
   },
   {
      "id":61,
      "name":"Cherianne",
      "age":24
   },
   {
      "id":62,
      "name":"Huberto",
      "age":25
   },
   {
      "id":63,
      "name":"Ariella",
      "age":26
   },
   {
      "id":64,
      "name":"Lorant",
      "age":30
   },
   {
      "id":65,
      "name":"Francesca",
      "age":25
   },
   {
      "id":66,
      "name":"Ingamar",
      "age":28
   },
   {
      "id":67,
      "name":"Myrta",
      "age":27
   },
   {
      "id":68,
      "name":"Nicolette",
      "age":26
   },
   {
      "id":69,
      "name":"Petra",
      "age":22
   },
   {
      "id":70,
      "name":"Cyrill",
      "age":27
   },
   {
      "id":71,
      "name":"Ad",
      "age":23
   },
   {
      "id":72,
      "name":"Denys",
      "age":22
   },
   {
      "id":73,
      "name":"Karilynn",
      "age":23
   },
   {
      "id":74,
      "name":"Gunner",
      "age":30
   },
   {
      "id":75,
      "name":"Falkner",
      "age":20
   },
   {
      "id":76,
      "name":"Thurston",
      "age":19
   },
   {
      "id":77,
      "name":"Codi",
      "age":30
   },
   {
      "id":78,
      "name":"Jacob",
      "age":31
   },
   {
      "id":79,
      "name":"Gasparo",
      "age":26
   },
   {
      "id":80,
      "name":"Mitzi",
      "age":29
   },
   {
      "id":81,
      "name":"Rubetta",
      "age":21
   },
   {
      "id":82,
      "name":"Clary",
      "age":20
   },
   {
      "id":83,
      "name":"Oliviero",
      "age":24
   },
   {
      "id":84,
      "name":"Ranique",
      "age":21
   },
   {
      "id":85,
      "name":"Shae",
      "age":24
   },
   {
      "id":86,
      "name":"Woodrow",
      "age":20
   },
   {
      "id":87,
      "name":"Junia",
      "age":31
   },
   {
      "id":88,
      "name":"Athene",
      "age":26
   },
   {
      "id":89,
      "name":"Veriee",
      "age":18
   },
   {
      "id":90,
      "name":"Rickie",
      "age":30
   },
   {
      "id":91,
      "name":"Carly",
      "age":23
   },
   {
      "id":92,
      "name":"Vern",
      "age":19
   },
   {
      "id":93,
      "name":"Trix",
      "age":26
   },
   {
      "id":94,
      "name":"Lenore",
      "age":20
   },
   {
      "id":95,
      "name":"Hanna",
      "age":30
   },
   {
      "id":96,
      "name":"Dominique",
      "age":21
   },
   {
      "id":97,
      "name":"Karlotta",
      "age":22
   },
   {
      "id":98,
      "name":"Levey",
      "age":20
   },
   {
      "id":99,
      "name":"Dalila",
      "age":18
   },
   {
      "id":100,
      "name":"Launce",
      "age":21
   }
]

接下来,在 SimplePagination 组件中添加一个构造函数并设置初始状态,如下所示 −

constructor(props) {
   super(props);
   this.state = {
      users: [],
      usersToBeShown: [],
      currentPage: 1
   }
};

接下来,添加 componentDidMount 生命周期事件并添加以下代码来获取和处理用户信息。

componentDidMount() {
   fetch('users.json')
      .then((response) => response.json())
      .then((data) => {
         // console.log(data);
         this.setState({
            users: data,
            pageSize: 3,
            usersToBeShown: [],
            pageArray: []
         });
         this.calculatePaginationDetails(1)
   });
}

接下来,在 calculatePaginationDetails 方法中实现分页逻辑,如下所示 −

calculatePaginationDetails = (page) => {
   console.log(page)
   let users = this.state.users;
   let total = users.length;
   let pages = Math.floor((users.length / this.state.pageSize) + 1);
   let firstPage = 1;
   let lastPage = pages;
   let pageArray = []
   let usersToBeShown = []
   let currentPage = 1;
   if(page.toString().toLowerCase().indexOf('previous') > 0) {
      currentPage = this.state.currentPage - 1;
      if(currentPage < 1) {
         currentPage = 1
      }
   } else if(page.toString().toLowerCase().indexOf('next') > 0) {
      currentPage = this.state.currentPage + 1;
      if(currentPage > pages) {
         currentPage = pages;
      }
   } else if(page.toString().toLowerCase().indexOf('first') > 0) {
      currentPage = 1
   } else if(page.toString().toLowerCase().indexOf('last') > 0) {
      currentPage = pages;
   } else {
      currentPage = parseInt(page);
   }
   console.log(parseInt(page))
   console.log(currentPage)
   for(let i = currentPage; i <= currentPage + 4; i++) {
      if(i <= pages)
      pageArray.push(i)
   }
   let currentItemIndex = (currentPage - 1) * this.state.pageSize;
   for(let i = currentItemIndex; i < currentItemIndex + 3 && i <= (total - 1); i++) {
      usersToBeShown.push(users[i])
   }
   let updatedState = {
      usersToBeShown: usersToBeShown,
      pageArray: pageArray,
      firstPage: firstPage,
      lastPage: lastPage,
      currentPage: currentPage
   }
   console.log(updatedState)
   this.setState({
      usersToBeShown: usersToBeShown,
      pageArray: pageArray,
      firstPage: firstPage,
      lastPage: lastPage,
      currentPage: currentPage
   });
}

接下来,添加一个事件处理程序来处理分页并根据用户的页面选择设置数据,如下所示 −

handlePagination = (e) => {
   e.preventDefault();
   console.log(e.target);
   if(e.target.text != undefined) {
      this.calculatePaginationDetails(e.target.text);
   }
}

接下来,使用 Table 组件渲染数据,并使用 Pagination 组件进行分页。

render() {
   return (
      <>
         <Table bordered hover striped>
            <thead>
               <tr>
                  <th>#</th>
                  <th>Name</th>
                  <th>Age</th>
                  <th>Email</th>
               </tr>
            </thead>
            <tbody>{
               this.state.usersToBeShown && this.state.usersToBeShown.length &&
               this.state.usersToBeShown.map(
                  (item) => (
                     <tr key={item.id}>
                        <td>{item.id}</td>
                        <td>{item.name}</td>
                        <td>{item.age}</td>
                        <td>{item.name.toLowerCase()}.example@tutorialspoint.com</td>
                     </tr>
                  )
               )
            }
            </tbody>
         </Table>
         <Pagination>
            <Pagination.First onClick={(e) => this.handlePagination(e)} />
            <Pagination.Prev onClick={(e) => this.handlePagination(e)} />{
               this.state.pageArray && this.state.pageArray.length &&
               this.state.pageArray.map(
                  (item) => (
                     <Pagination.Item key={item} onClick={(e) => this.handlePagination(e)}
                     active={this.state.currentPage == item}>{item}</Pagination.Item>
                  )
               )
            }
            <Pagination.Next onClick={(e) => this.handlePagination(e)} />
            <Pagination.Last onClick={(e) => this.handlePagination(e)} />
         </Pagination>
      </>
   );
}

接下来,打开 App 组件 (src/App.js),导入 bootstrap css 并使用 SimplePagination 组件更新内容,如下所示 −

import './App.css'
import "bootstrap/dist/css/bootstrap.min.css";
import SimplePagination from './Components/SimplePagination'
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimplePagination />
            </div>
         </div>
      </div>
   );
}
export default App;

最后,在浏览器中打开应用程序并检查分页是否正常工作,如下所示 −

Pagination

摘要

React Bootstrap 分页组件提供了必要的组件来呈现简单和复杂的分页设计。

ReactJS - Material UI

React 社区提供了大量高级 UI 组件框架。Material UI 是流行的 React UI 框架之一。让我们在本章中学习如何使用 Material UI 库。

安装

可以使用 npm 包安装 Material UI。

npm install @material-ui/core

Material UI 建议使用 roboto 字体作为 UI。要使用 Roboto 字体,请使用 Gooogleapi 链接将其包含在内。

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />

要使用字体图标,请使用来自 googleapis 的图标链接 −

<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />

要使用 SVG 图标,请安装 @material-ui/icons 包 −

npm install @material-ui/icons

工作示例

让我们重新创建费用列表应用程序并使用 Material UI 组件代替 html 表。

步骤 1 −首先,按照创建 React 应用程序一章中的说明,使用 Create React App 或 Rollup bundler 创建一个新的 React 应用程序 react-materialui-app

步骤 2 −安装 React Transition 组库 −

cd /go/to/project npm install @material-ui/core @material-ui/icons --save

在您最喜欢的编辑器中打开应用程序。

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

src 文件夹下创建 components 文件夹。

src/components 文件夹中创建一个文件 ExpenseEntryItemList.js 以创建 ExpenseEntryItemList 组件

导入 React 库和样式表。

import React from 'react';

步骤 2 − 接下来,导入 Material-UI 库。

import { withStyles } from '@material-ui/core/styles'; 
import Table from '@material-ui/core/Table'; 
import TableBody from '@material-ui/core/TableBody'; 
import TableCell from '@material-ui/core/TableCell'; 
import TableContainer from '@material-ui/core/TableContainer'; 
import TableHead from '@material-ui/core/TableHead'; 
import TableRow from '@material-ui/core/TableRow'; 
import Paper from '@material-ui/core/Paper';

创建 ExpenseEntryItemList 类并调用构造函数。

class ExpenseEntryItemList extends React.Component {
   constructor(props) {
      super(props);
   }
}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />

创建一个 render() 函数。

render() {
}

render 方法中为表格行和表格单元格应用样式。

const StyledTableCell = withStyles((theme) => ({
   head: {
      backgroundColor: theme.palette.common.black,
      color: theme.palette.common.white,
   },
   body: {
      fontSize: 14,
   },
}))(TableCell);
const StyledTableRow = withStyles((theme) => ({
   root: {
      '&:nth-of-type(odd)': {
         backgroundColor: theme.palette.action.hover,
      },
   },
}))(TableRow);

使用 map 方法生成 Material UI StyledTableRow 集合,每个集合代表列表中的单个费用条目。

const lists = this.props.items.map((item) =>
   <StyledTableRow key={item.id}>
      <StyledTableCell component="th" scope="row">
         {item.name}
      </StyledTableCell>
      <StyledTableCell align="right">{item.amount}</StyledTableCell>
      <StyledTableCell align="right">
         {new Date(item.spendDate).toDateString()}
      </StyledTableCell>
      <StyledTableCell align="right">{item.category}</StyledTableCell>
   </StyledTableRow>
);

此处,key 标识每一行,并且它在列表中必须是唯一的。

步骤 3 − 在 render() 方法中,创建一个 Material UI 表并在行部分中包含列表表达式并返回它。

return (
<TableContainer component={Paper}>
   <Table aria-label="customized table">
      <TableHead>
         <TableRow>
            <StyledTableCell>Title</StyledTableCell>
            <StyledTableCell align="right">Amount</StyledTableCell>
            <StyledTableCell align="right">Spend date</StyledTableCell>
            <StyledTableCell align="right">Category</StyledTableCell>
         </TableRow>
      </TableHead>
      <TableBody>
         {lists}
      </TableBody>
   </Table>
</TableContainer> );

最后,导出组件。

export default ExpenseEntryItemList;

现在,我们已经成功创建了使用material ui组件渲染费用项目的组件。

组件的完整源代码如下所示 −

import React from 'react';

import { withStyles } from '@material-ui/core/styles';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';

class ExpenseEntryItemList extends React.Component {
   constructor(props) {
      super(props);
   }
   render() {
      const StyledTableCell = withStyles((theme) => ({
         head: {
            backgroundColor: theme.palette.common.black,
            color: theme.palette.common.white,
         },
         body: {
            fontSize: 14,
         },
      }))(TableCell);
      const StyledTableRow = withStyles((theme) => ({
         root: {
            '&:nth-of-type(odd)': {
               backgroundColor: theme.palette.action.hover,
            },
         },
      }))(TableRow);
      const lists = this.props.items.map((item) =>
         <StyledTableRow key={item.id}>
            <StyledTableCell component="th" scope="row">
               {item.name}
            </StyledTableCell>
            <StyledTableCell align="right">{item.amount}</StyledTableCell>
            <StyledTableCell align="right">{new Date(item.spendDate).toDateString()}</StyledTableCell>
            <StyledTableCell align="right">{item.category}</StyledTableCell>
         </StyledTableRow>
      );
      return (
      <TableContainer component={Paper}>
         <Table aria-label="customized table">
            <TableHead>
               <TableRow>
                  <StyledTableCell>Title</StyledTableCell>
                  <StyledTableCell align="right">Amount</StyledTableCell>
                  <StyledTableCell align="right">Spend date</StyledTableCell>
                  <StyledTableCell align="right">Category</StyledTableCell>
               </TableRow>
            </TableHead>
            <TableBody>
               {lists}
            </TableBody>
         </Table>
      </TableContainer> );
   }
}
export default ExpenseEntryItemList;

index.js:

打开 index.js 并导入 react 库和我们新创建的 ExpenseEntryItemList 组件。

import React from 'react'; 
import ReactDOM from 'react-dom'; 
import ExpenseEntryItemList from './components/ExpenseEntryItemList';

index.js 文件中声明一个列表(费用条目项目)并用一些随机值填充它。

const items = [
   { id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
   { id: 1, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
   { id: 1, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
   { id: 1, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
   { id: 1, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
   { id: 1, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
   { id: 1, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
   { id: 1, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
   { id: 1, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
   { id: 1, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]

通过项目属性传递项目来使用 ExpenseEntryItemList 组件。

ReactDOM.render(
   <React.StrictMode>
      <ExpenseEntryItemList items={items} />
   </React.StrictMode>,
   document.getElementById('root')
);

index.js完整代码如下 −

import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemList from './components/ExpenseEntryItemList';

const items = [
   { id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
   { id: 1, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
   { id: 1, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
   { id: 1, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
   { id: 1, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
   { id: 1, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
   { id: 1, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
   { id: 1, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
   { id: 1, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
   { id: 1, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]
ReactDOM.render(
   <React.StrictMode>
      <ExpenseEntryItemList items={items} />
   </React.StrictMode>,
   document.getElementById('root')
);

使用 npm 命令为应用程序提供服务。

npm start

index.html

在公共文件夹中打开 index.html 文件并包含 Material UI 字体和图标。

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="utf-8">
      <title>Material UI App</title>
      <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
      <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
   </head>
   <body>
      <div id="root"></div>
      <script type="text/JavaScript" src="./index.js"></script>
   </body>
</html>

打开浏览器,在地址栏中输入http://localhost:3000并按回车键。

material UI

ReactJS - Http 客户端编程

Http 客户端编程使应用程序能够通过 JavaScript 连接并从 http 服务器获取数据。它减少了客户端和服务器之间的数据传输,因为它只获取所需的数据而不是整个设计,从而提高了网络速度。它提高了用户体验,成为每个现代 Web 应用程序不可或缺的功能。

如今,许多服务器端应用程序通过 REST API(HTTP 协议上的功能)公开其功能,并允许任何客户端应用程序使用该功能。

React 不提供自己的 http 编程 api,但它支持浏览器的内置 fetch() api 以及第三方客户端库(如 axios)来进行客户端编程。让我们在本章中学习如何在 React 应用程序中进行 http 编程。开发人员应该具备 Http 编程的基本知识才能理解本章。

Expense Rest API 服务器

进行 Http 编程的先决条件是具备 Http 协议和 REST API 技术的基本知识。Http 编程涉及两个部分,服务器和客户端。React 提供创建客户端应用程序的支持。 Express 是一个流行的 Web 框架,它支持创建服务器端应用程序。

首先,让我们使用 express 框架创建一个 Expense Rest Api 服务器,然后使用浏览器内置的 fetch api 从我们的 ExpenseManager 应用程序访问它。

打开命令提示符并创建一个新文件夹 express-rest-api

cd /go/to/workspace
mkdir apiserver
cd apiserver

使用以下命令初始化新的节点应用程序 −

npm init

npm init 将提示并要求我们输入基本项目详细信息。让我们输入 apiserver 作为项目名称,输入 server.js 作为入口点。其他配置保留默认选项。

This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (apiserver)
version: (1.0.0)
description: Rest api for Expense Application
entry point: (index.js) server.js
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to \path	o\workspace\expense-rest-api\package.json:
{
   "name": "expense-rest-api",
   "version": "1.0.0",
   "description": "Rest api for Expense Application",
   "main": "server.js",
   "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
   },
   "author": "",
   "license": "ISC"
}
Is this OK? (yes) yes

接下来,使用以下命令安装 express、nedb 和 cors 模块 −

npm install express nedb cors
  • express 用于创建服务器端应用程序。

  • nedb 是用于存储费用数据的数据存储。

  • corsexpress 框架的中间件,用于配置客户端访问详细信息。

接下来,让我们创建一个文件 data.csv 并用初始费用数据填充它以进行测试。文件的结构是每行包含一个费用条目。

Pizza,80,2020-10-10,Food
Grape Juice,30,2020-10-12,Food
Cinema,210,2020-10-16,Entertainment
Java Programming book,242,2020-10-15,Academic
Mango Juice,35,2020-10-16,Food
Dress,2000,2020-10-25,Cloth
Tour,2555,2020-10-29,Entertainment
Meals,300,2020-10-30,Food
Mobile,3500,2020-11-02,Gadgets
Exam Fees,1245,2020-11-04,Academic

接下来,创建一个文件 expensedb.js,并包含将初始费用数据加载到数据存储中的代码。代码检查数据存储中的初始数据,并且仅在数据在存储中不可用时才加载。

var store = require("nedb")
var fs = require('fs');
var expenses = new store({ filename: "expense.db", autoload: true })
expenses.find({}, function (err, docs) {
   if (docs.length == 0) {
      loadExpenses();
   }
})
function loadExpenses() {
   readCsv("data.csv", function (data) {
      console.log(data);

      data.forEach(function (rec, idx) {
         item = {}
         item.name = rec[0];
         item.amount = parseFloat(rec[1]);
         item.spend_date = new Date(rec[2]);
         item.category = rec[3];

         expenses.insert(item, function (err, doc) {
            console.log('Inserted', doc.item_name, 'with ID', doc._id);
         })
      })
   })
}
function readCsv(file, callback) {
   fs.readFile(file, 'utf-8', function (err, data) {
      if (err) throw err;
      var lines = data.split('
');
      var result = lines.map(function (line) {
         return line.split(',');
      });
      callback(result);
   });
}
module.exports = expenses

接下来,创建一个文件 server.js,并包含列出、添加、更新和删除费用条目的实际代码。

var express = require("express")
var cors = require('cors')
var expenseStore = require("./expensedb.js")
var app = express()
app.use(cors());
var bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
var HTTP_PORT = 8000
app.listen(HTTP_PORT, () => {
   console.log("Server running on port %PORT%".replace("%PORT%", HTTP_PORT))
});
app.get("/", (req, res, next) => {
   res.json({ "message": "Ok" })
});
app.get("/api/expenses", (req, res, next) => {
   expenseStore.find({}, function (err, docs) {
      res.json(docs);
   });
});
app.get("/api/expense/:id", (req, res, next) => {
   var id = req.params.id;
   expenseStore.find({ _id: id }, function (err, docs) {
      res.json(docs);
   })
});
app.post("/api/expense/", (req, res, next) => {
   var errors = []
   if (!req.body.item) {
      errors.push("No item specified");
   }
   var data = {
      name: req.body.name,
      amount: req.body.amount,
      category: req.body.category,
      spend_date: req.body.spend_date,
   }
   expenseStore.insert(data, function (err, docs) {
      return res.json(docs);
   });
})
app.put("/api/expense/:id", (req, res, next) => {
   var id = req.params.id;
   var errors = []
   if (!req.body.item) {
      errors.push("No item specified");
   }
   var data = {
      _id: id,
      name: req.body.name,
      amount: req.body.amount,
      category: req.body.category,
      spend_date: req.body.spend_date,
   }
   expenseStore.update( { _id: id }, data, function (err, docs) {
      return res.json(data);
   });
})
app.delete("/api/expense/:id", (req, res, next) => {
   var id = req.params.id;
   expenseStore.remove({ _id: id }, function (err, numDeleted) {
      res.json({ "message": "deleted" })
   });
})
app.use(function (req, res) {
   res.status(404);
});

现在,是时候运行应用程序了。

npm run start

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

{ 
   "message": "Ok" 
}

这证实我们的应用程序运行正常。

最后,将 url 更改为 http://localhost:8000/api/expense 并按 Enter。浏览器将以 JSON 格式显示初始费用条目。

[
   ...
   {
      "name": "Pizza",
      "amount": 80,
      "spend_date": "2020-10-10T00:00:00.000Z",
      "category": "Food",
      "_id": "5H8rK8lLGJPVZ3gD"
   },
   ...
]

让我们在下一节中通过 fetch() api 在我们的 Expense Manager(费用管理器)应用程序中使用我们新创建的费用服务器。

fetch() API

让我们创建一个新的应用程序来展示 React 中的客户端编程。

首先,按照创建 React 应用程序一章中的说明,使用 Create React AppRollup 捆绑器创建一个新的 React 应用程序 react-http-app

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

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

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

接下来,在 下创建一个文件 ExpenseEntryItemList.css src/components 文件夹并包含通用表格样式。

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 库。

import React from 'react';

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

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

接下来,在构造函数中使用空列表初始化状态。

this.state = {
   isLoaded: false,
   items: []
}

接下来,创建一个方法 setItems 来格式化从远程服务器接收的项目,然后将其设置到组件的状态中。

setItems(remoteItems) {
   var items = [];
   remoteItems.forEach((item) => {
      let newItem = {
         id: item._id,
         name: item.name,
         amount: item.amount,
         spendDate: item.spend_date,
         category: item.category
      }
      items.push(newItem)
   });
   this.setState({
      isLoaded: true,
      items: items
   });
}

接下来,添加一个方法 fetchRemoteItems 来从服务器获取项目。

fetchRemoteItems() {
   fetch("http://localhost:8000/api/expenses")
      .then(res => res.json())
      .then(
         (result) => {
            this.setItems(result);
         },
         (error) => {
            this.setState({
               isLoaded: false,
               error
            });
         }
      )
}

这里,

  • fetch api 用于从远程服务器获取项目。

  • setItems 用于格式化和存储状态中的项目。

接下来,添加一个方法,deleteRemoteItem 以从远程服务器删除该项目。

deleteRemoteItem(id) {
   fetch('http://localhost:8000/api/expense/' + id, { method: 'DELETE' })
      .then(res => res.json())
      .then(
         () => {
            this.fetchRemoteItems()
         }
      )
}

这里,

  • fetch api 用于从远程服务器删除和获取项目。

  • setItems 再次用于格式化和存储状态中的项目。

接下来,调用 componentDidMount 生命周期 api 在组件的安装阶段将项目加载到组件中。

componentDidMount() { 
   this.fetchRemoteItems(); 
}

接下来,编写一个事件处理程序以从列表中删除该项目。

handleDelete = (id, e) => { 
   e.preventDefault(); 
   console.log(id); 

   this.deleteRemoteItem(id); 
}

接下来编写render方法。

render() {
   let lists = [];
   if (this.state.isLoaded) {
      lists = this.state.items.map((item) =>
         <tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
            <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 onMouseOver={this.handleMouseOver}>
            <thead>
               <tr>
                  <th>Item</th>
                  <th>Amount</th>
                  <th>Date</th>
                  <th>Category</th>
                  <th>Remove</th>
               </tr>
            </thead>
            <tbody>
               {lists}
            </tbody>
         </table>
      </div>
   );
}

最后,导出组件。

export default ExpenseEntryItemList;

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

import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemList from './components/ExpenseEntryItemList';

ReactDOM.render(
   <React.StrictMode>
         <ExpenseEntryItemList />
   </React.StrictMode>,
   document.getElementById('root')
);

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

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

接下来,打开一个新的终端窗口并启动我们的服务器应用程序。

cd /go/to/server/application
npm start

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

npm start

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

Material

尝试通过单击删除链接来删除该项目。

Materials

ReactJS - 表单编程

表单是 Web 应用程序的常见部分,主要用于允许用户与应用程序交互。这些表单可能包含在网页上,用于收集用户信息、让用户搜索网站、付款等。表单可以包含的基本元素是输入字段、按钮、检查表、下拉菜单等。从这些表单获取的数据通常由 React 中的组件处理。

要了解如何在 React 中使用表单,让我们看一些示例

表单编程

与 HTML 不同,HTML 中的表单会根据用户输入数据进行更新,而 React 会借助其状态来更新表单。可变状态通常在组件的状态属性中指定,并且仅使用 setState() 进行更新。

表单编程的本质需要维护状态。因为,输入字段信息会随着用户与表单交互而改变。但正如我们之前所了解的,React 库本身不存储或维护任何状态信息,组件必须使用状态管理 API 来管理状态。考虑到这一点,React 提供了两种类型的组件来支持表单编程。

  • 受控组件 − 在受控组件中,React 为所有输入元素提供了一个特殊的属性 value,并控制输入元素。value 属性可用于获取和设置输入元素的值。它必须与组件的状态同步。

  • 非受控组件 − 在非受控组件中,React 对表单编程提供了最低限度的支持。它必须使用 Ref 概念(另一个 React 概念,用于在运行时获取 React 组件中的 DOM 元素)来进行表单编程。

让我们在本章中学习使用受控和非受控组件进行表单编程。

ReactJS - 受控组件

在受控组件中,React 为所有输入元素提供了一个特殊属性 value,并控制输入元素。value 属性可用于获取和设置输入元素的值。它必须与组件的状态同步。

换句话说,呈现表单的 React 组件还控制后续用户输入时该表单中发生的情况。以这种方式由 React 控制值的输入表单元素称为"受控组件"。

受控组件必须遵循特定的流程才能进行表单编程。

具有单个输入的受控组件

让我们检查单个输入元素要遵循的分步流程。

步骤 1 −创建表单元素。

<input type="text" name="username" />

步骤 2 − 为输入元素创建状态。

this.state = { 
   username: '' 
}

步骤 3 − 添加一个值属性并从状态分配值。

<input type="text" name="username" value={this.state.username} />

步骤 4 − 添加一个 onChange 属性 并分配一个处理程序方法。

<input type="text" name="username" value={this.state.username} onChange={this.handleUsernameChange} />

步骤 5 − 编写处理程序方法并在触发事件时更新状态。

handleUsernameChange(e) {
   this.setState({
      username = e.target.value
   });
}

步骤 6 − 在组件的构造函数中绑定事件处理程序。

this.handleUsernameChange = this.handleUsernameChange.bind(this)

最后,在验证和提交期间,使用 usernamethis.state 获取输入值。

handleSubmit(e) {
   e.preventDefault();
   alert(this.state.username);
}

创建一个简单的表单

让我们在本章中使用控制器组件创建一个简单的表单来添加费用条目。

步骤 1 − 首先,按照创建 React 应用程序一章中的说明,使用 Create React App 或 Rollup 捆绑器创建一个新的 React 应用程序 react-form-app

步骤 2 − 在您最喜欢的编辑器中打开该应用程序。

在下一步中,在应用程序的根目录下创建 src 文件夹。

继续上述过程,在 src 文件夹下创建 components 文件夹。

步骤 3 −在 src 文件夹下创建一个文件 ExpenseForm.css,用于设置组件样式。

input[type=text], input[type=number], input[type=date], select {
   width: 100%;
   padding: 12px 20px;
   margin: 8px 0;
   display: inline-block;
   border: 1px solid #ccc;
   border-radius: 4px;
   box-sizing: border-box;
}
input[type=submit] {
   width: 100%;
   background-color: #4CAF50;
   color: white;
   padding: 14px 20px;
   margin: 8px 0;
   border: none;
   border-radius: 4px;
   cursor: pointer;
}
input[type=submit]:hover {
   background-color: #45a049;
}
input:focus {
   border: 1px solid #d9d5e0;
}
#expenseForm div {
   border-radius: 5px;
   background-color: #f2f2f2;
   padding: 20px;
}

步骤 4 − 在 src/components 文件夹下创建一个文件 ExpenseForm.js 并开始编辑。

步骤 5 − 导入 React 库。

import React from 'react';

导入 ExpenseForm.css 文件。

import './ExpenseForm.css'

步骤 6 − 创建一个类 ExpenseForm 并使用 props 调用构造函数。

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

初始化组件的状态。

this.state = { 
   item: {} 
}

创建 render() 方法并添加一个带有输入字段的表单以添加费用项目。

render() {
   return (
      <div id="expenseForm">
         <form>
            <label for="name">Title</label>
            <input type="text" id="name" name="name" placeholder="Enter expense title" />
            <label for="amount">Amount</label>
            <input type="number" id="amount" name="amount" placeholder="Enter expense amount" />
            <label for="date">Spend Date</label>
            <input type="date" id="date" name="date" placeholder="Enter date" />
            <label for="category">Category</label>
            <select id="category" name="category" 
              <option value="">Select</option>
              <option value="Food">Food</option>
              <option value="Entertainment">Entertainment</option>
              <option value="Academic">Academic</option>
            </select>
            <input type="submit" value="Submit" />
         </form>
      </div>
   )
}

为所有输入字段创建事件处理程序,以更新状态中的费用详细信息。

handleNameChange(e) {
   this.setState( (state, props) => {
      let item = state.item
      item.name = e.target.value;
      return { item: item }
   });
}
handleAmountChange(e) {
   this.setState( (state, props) => {
      let item = state.item
      item.amount = e.target.value;
      return { item: item }
   });
}
handleDateChange(e) {
   this.setState( (state, props) => {
      let item = state.item
      item.date = e.target.value;
      return { item: item }
   });
}
handleCategoryChange(e) {
   this.setState( (state, props) => {
      let item = state.item
      item.category = e.target.value;
      return { item: item }
   });
}

在构造函数中绑定事件处理程序。

this.handleNameChange = this.handleNameChange.bind(this);
this.handleAmountChange = this.handleAmountChange.bind(this);
this.handleDateChange = this.handleDateChange.bind(this);
this.handleCategoryChange = this.handleCategoryChange.bind(this);

接下来,为提交操作添加事件处理程序。

onSubmit = (e) => {
   e.preventDefault();
   alert(JSON.stringify(this.state.item));
}

将事件处理程序附加到表单。

render() {
   return (
      <div id="expenseForm">
         <form onSubmit={(e) => this.onSubmit(e)}>
            <label for="name">Title</label>
            <input type="text" id="name" name="name" placeholder="Enter expense title" 
               value={this.state.item.name}
               onChange={this.handleNameChange} />

            <label for="amount">Amount</label>
            <input type="number" id="amount" name="amount" placeholder="Enter expense amount"
               value={this.state.item.amount}
               onChange={this.handleAmountChange} />

            <label for="date">Spend Date</label>
            <input type="date" id="date" name="date" placeholder="Enter date" 
               value={this.state.item.date}
               onChange={this.handleDateChange} />

            <label for="category">Category</label>
            <select id="category" name="category"
               value={this.state.item.category}
               onChange={this.handleCategoryChange} >
              <option value="">Select</option>
              <option value="Food">Food</option>
              <option value="Entertainment">Entertainment</option>
              <option value="Academic">Academic</option>
            </select>

            <input type="submit" value="Submit" />
         </form>
      </div>
   )
}

最后,导出组件。

export default ExpenseForm

ExpenseForm 组件的完整代码如下 −

import React from 'react';
import './ExpenseForm.css'

class ExpenseForm extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         item: {}
      }
      this.handleNameChange = this.handleNameChange.bind(this);
      this.handleAmountChange = this.handleAmountChange.bind(this);
      this.handleDateChange = this.handleDateChange.bind(this);
      this.handleCategoryChange = this.handleCategoryChange.bind(this);
   }
   handleNameChange(e) {
      this.setState( (state, props) => {
         let item = state.item
         item.name = e.target.value;
         return { item: item }
      });
   }
   handleAmountChange(e) {
      this.setState( (state, props) => {
         let item = state.item
         item.amount = e.target.value;
         return { item: item }
      });
   }
   handleDateChange(e) {
      this.setState( (state, props) => {
         let item = state.item
         item.date = e.target.value;
         return { item: item }
      });
   }
   handleCategoryChange(e) {
      this.setState( (state, props) => {
         let item = state.item
         item.category = e.target.value;
         return { item: item }
      });
   }
   onSubmit = (e) => {
      e.preventDefault();
      alert(JSON.stringify(this.state.item));
   }
   render() {
      return (
         <div id="expenseForm">
           <form onSubmit={(e) => this.onSubmit(e)}>
            <label for="name">Title</label>
            <input type="text" id="name" name="name" placeholder="Enter expense title" 
               value={this.state.item.name}
               onChange={this.handleNameChange} />

            <label for="amount">Amount</label>
            <input type="number" id="amount" name="amount" placeholder="Enter expense amount"
               value={this.state.item.amount}
               onChange={this.handleAmountChange} />

            <label for="date">Spend Date</label>
            <input type="date" id="date" name="date" placeholder="Enter date" 
               value={this.state.item.date}
               onChange={this.handleDateChange} />

            <label for="category">Category</label>
            <select id="category" name="category"
               value={this.state.item.category}
               onChange={this.handleCategoryChange} >
              <option value="">Select</option>
              <option value="Food">Food</option>
              <option value="Entertainment">Entertainment</option>
              <option value="Academic">Academic</option>
            </select>
           
            <input type="submit" value="Submit" />
           </form>
         </div>
      )
   }
}
export default ExpenseForm;

index.js −

接下来,在 src 文件夹下创建一个文件 index.js,并使用 ExpenseForm 组件。

import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseForm from './components/ExpenseForm'

ReactDOM.render(
   <React.StrictMode>
      <ExpenseForm />
   </React.StrictMode>,
   document.getElementById('root')
);

index.html −

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

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

使用 npm 命令启动应用。

npm start

打开浏览器,在地址栏中输入 http://localhost:3000,然后按回车键。

地址栏

最后,输入示例费用明细,然后单击提交。提交的数据将被收集并显示在弹出消息框中。

地址栏

ReactJS - 非受控组件

正如我们之前所了解的,非受控组件不支持基于 React 的表单编程。如果不使用 React api,则无法获取 React DOM 元素(表单元素)的值。获取 React 组件内容的一种方法是使用 React ref 功能。

React 为其所有 DOM 元素提供 ref 属性,并提供相应的 api React.createRef() 来创建新引用 (this.ref)。新创建的引用可以附加到表单元素,并且可以在必要时(在验证和提交期间)使用 this.ref.current.value 访问附加的表单元素的值。

非受控组件中的表单编程

让我们看看在非受控组件中进行表单编程的分步过程。

步骤 1 −创建引用。

this.inputRef = React.createRef();

步骤 2 − 创建表单元素。

<input type="text" name="username" />

步骤 3 − 在表单元素中附加已创建的引用。

<input type="text" name="username" ref={this.inputRef} />

要设置输入元素的默认值,请使用 defaultValue 属性,而不是 value 属性。如果使用值,它将在组件的渲染阶段更新。

<input type="text" name="username" ref={this.inputRef} defaultValue="default value" />

最后,在验证和提交期间使用 this.inputRef.current.value 获取输入值。

handleSubmit(e) {
   e.preventDefault();

   alert(this.inputRef.current.value);
}

创建简单表单

在本章中,让我们使用不受控制的组件创建一个简单的表单来添加费用条目。

步骤 1 − 首先,按照创建 React 应用程序一章中的说明,使用 Create React App 或 Rollup bundler 创建一个新的 React 应用程序 react-form-uncontrolled-app

步骤 2 − 在您最喜欢的编辑器中打开该应用程序。

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

src 文件夹下创建 components 文件夹。

步骤 3 − 在 src 文件夹下创建一个文件 ExpenseForm.css 来设置组件的样式。

input[type=text], input[type=number], input[type=date], select {
   width: 100%;
   padding: 12px 20px;
   margin: 8px 0;
   display: inline-block;
   border: 1px solid #ccc;
   border-radius: 4px;
   box-sizing: border-box;
}

input[type=submit] {
   width: 100%;
   background-color: #4CAF50;
   color: white;
   padding: 14px 20px;
   margin: 8px 0;
   border: none;
   border-radius: 4px;
   cursor: pointer;
}

input[type=submit]:hover {
   background-color: #45a049;
}

input:focus {
   border: 1px solid #d9d5e0;
}

#expenseForm div {
   border-radius: 5px;
   background-color: #f2f2f2;
   padding: 20px;
}

步骤 4 − 在 src/components 文件夹下创建一个文件 ExpenseForm.js 并开始编辑。

步骤 5 − 导入 React 库。

import React from 'react';

导入 ExpenseForm.css 文件。

import './ExpenseForm.css'

创建一个类 ExpenseForm 并使用 props 调用构造函数。

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

为所有输入字段创建 React 引用。

this.nameInputRef = React.createRef();
this.amountInputRef = React.createRef();
this.dateInputRef = React.createRef();
this.categoryInputRef = React.createRef();

创建 render() 方法并添加带有输入字段的表单以添加费用项目。

render() {
   return (
      <div id="expenseForm">
         <form>
            <label for="name">Title</label>
            <input type="text" id="name" name="name" placeholder="Enter expense title" />

            <label for="amount">Amount</label>
            <input type="number" id="amount" name="amount" placeholder="Enter expense amount" />

            <label for="date">Spend Date</label>
            <input type="date" id="date" name="date" placeholder="Enter date" />

            <label for="category">Category</label>
            <select id="category" name="category" >
               <option value="">Select</option>
               <option value="Food">Food</option>
               <option value="Entertainment">Entertainment</option>
               <option value="Academic">Academic</option>
            </select>

            <input type="submit" value="Submit" />
         </form>
      </div>
   )
}

为提交操作添加事件处理程序。

onSubmit = (e) => {
   e.preventDefault();

   let item = {};

   item.name = this.nameInputRef.current.value;
   item.amount = this.amountInputRef.current.value;
   item.date = this.dateInputRef.current.value;
   item.category = this.categoryInputRef.current.value;

   alert(JSON.stringify(item));
}

将事件处理程序附加到表单。

render() {
   return (
      <div id="expenseForm">
         <form onSubmit={(e) => this.onSubmit(e)}>
            <label for="name">Title</label>
            <input type="text" id="name" name="name" placeholder="Enter expense title" 
               ref={this.nameInputRef} />

            <label for="amount">Amount</label>
            <input type="number" id="amount" name="amount" placeholder="Enter expense amount" 
               ref={this.amountInputRef} />      

            <label for="date">Spend Date</label>
            <input type="date" id="date" name="date" placeholder="Enter date" 
               ref={this.dateInputRef} />

            <label for="category">Category</label>
            <select id="category" name="category" 
               ref={this.categoryInputRef} >
               <option value="">Select</option>
               <option value="Food">Food</option>
               <option value="Entertainment">Entertainment</option>
               <option value="Academic">Academic</option>
            </select>

            <input type="submit" value="Submit" />
         </form>
      </div>
   )
}

最后,导出组件。

export default ExpenseForm

下面给出了ExpenseForm组件的完整代码

import React from 'react';
import './ExpenseForm.css'

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

      this.nameInputRef = React.createRef();
      this.amountInputRef = React.createRef();
      this.dateInputRef = React.createRef();
      this.categoryInputRef = React.createRef();
   }
   onSubmit = (e) => {
      e.preventDefault();
      let item = {};
      item.name = this.nameInputRef.current.value;
      item.amount = this.amountInputRef.current.value;
      item.date = this.dateInputRef.current.value;
      item.category = this.categoryInputRef.current.value;

      alert(JSON.stringify(item));
   }
   render() {
      return (
         <div id="expenseForm">
            <form onSubmit={(e) => this.onSubmit(e)}>
               <label for="name">Title</label>
               <input type="text" id="name" name="name" placeholder="Enter expense title" 
                  ref={this.nameInputRef} />

               <label for="amount">Amount</label>
               <input type="number" id="amount" name="amount" placeholder="Enter expense amount" 
                  ref={this.amountInputRef} />   

               <label for="date">Spend Date</label>
               <input type="date" id="date" name="date" placeholder="Enter date" 
                  ref={this.dateInputRef} />

               <label for="category">Category</label>
               <select id="category" name="category" 
                  ref={this.categoryInputRef} >
                 <option value="">Select</option>
                 <option value="Food">Food</option>
                 <option value="Entertainment">Entertainment</option>
                 <option value="Academic">Academic</option>
               </select>
              
               <input type="submit" value="Submit" />
           </form>
         </div>
      )
   }
}
export default ExpenseForm;

index.js

接下来,在 src 文件夹下创建一个文件 index.js 并使用 ExpenseForm 组件。

import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseForm from './components/ExpenseForm'

ReactDOM.render(
   <React.StrictMode>
      <ExpenseForm />
   </React.StrictMode>,
   document.getElementById('root')
);

index.html

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

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="utf-8">
      <title>React 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。

root folder

最后,输入示例费用明细,然后单击提交。提交的数据将被收集并显示在弹出消息框中。

root folders

ReactJS - Formik

Formik 是第三方 React 表单库。它提供基本的表单编程和验证。它基于受控组件,大大减少了进行表单编程的时间。

表单编程的本质需要维护状态。因为,当用户与表单交互时,输入字段信息会发生变化。但正如我们之前了解到的,React 库本身不存储或维护任何状态信息,组件必须使用状态管理 API 来管理状态。考虑到这一点,React 提供了两种类型的组件来支持表单编程。

使用 Formik 的费用表单

在本章中,让我们使用 Formik 库重新创建费用表单。

步骤 1 −首先,按照创建 React 应用程序一章中的说明,使用 Create React AppRollup 捆绑器创建一个新的 React 应用程序 react-formik-app

步骤 2 − 安装 Formik 库。

cd /go/to/workspace npm install formik --save

步骤 3 − 在您最喜欢的编辑器中打开应用程序。

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

在 src 文件夹下创建 components 文件夹。

步骤 4 −在 src 文件夹下创建一个文件 ExpenseForm.css,用于设置组件样式。

input[type=text], input[type=number], input[type=date], select {
   width: 100%;
   padding: 12px 20px;
   margin: 8px 0;
   display: inline-block;
   border: 1px solid #ccc;
   border-radius: 4px;
   box-sizing: border-box;
}
input[type=submit] {
   width: 100%;
   background-color: #4CAF50;
   color: white;
   padding: 14px 20px;
   margin: 8px 0;
   border: none;
   border-radius: 4px;
   cursor: pointer;
}
input[type=submit]:hover {
   background-color: #45a049;
}
input:focus {
   border: 1px solid #d9d5e0;
}
#expenseForm div {
   border-radius: 5px;
   background-color: #f2f2f2;
   padding: 20px;
}
#expenseForm span {
   color: red;
}

步骤 5 − 在 src/components 文件夹下创建另一个文件 ExpenseForm.js 并开始编辑。

导入 ReactFormik 库。

import React from 'react';
import { Formik } from 'formik';

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

import './ExpenseForm.css'

接下来,创建 ExpenseForm 类。

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

在构造函数中设置费用项目的初始值。

this.initialValues = { name: '', amount: '', date: '', category: '' }

接下来,创建一个验证方法。Formik 将发送用户输入的当前值。

validate = (values) => {
   const errors = {};
   if (!values.name) {
      errors.name = 'Required';
   }
   if (!values.amount) {
      errors.amount = 'Required';
   }
   if (!values.date) {
      errors.date = 'Required';
   }
   if (!values.category) {
      errors.category = 'Required';
   }
   return errors;
}

创建一个方法来提交表单。Formik 将发送用户输入的当前值。

handleSubmit = (values, setSubmitting) => {
   setTimeout(() => {
      alert(JSON.stringify(values, null, 2));
      setSubmitting(false);
   }, 400);
}

创建 render() 方法。使用 Formik 提供的 handleChange、handleBlur 和 handleSubmit 方法作为输入元素事件处理程序。

render() {
   return (
      <div id="expenseForm">
         <Formik
            initialValues={this.initialValues}
            validate={values => this.validate(values)}
            onSubmit={(values, { setSubmitting }) => this.handleSubmit(values, setSubmitting)} >{
               ({
                  values,
                  errors,
                  touched,
                  handleChange,
                  handleBlur,
                  handleSubmit,
                  isSubmitting,
                  /* and other goodies */
               }) 
               => (
                  <form onSubmit={handleSubmit}>
                     <label for="name">Title <span>{errors.name && touched.name && errors.name}</span></label>
                     <input type="text" id="name" name="name" placeholder="Enter expense title"
                        onChange={handleChange}
                        onBlur={handleBlur}
                        value={values.name} />

                     <label for="amount">Amount <span>{errors.amount && touched.amount && errors.amount}</span></label>
                     <input type="number" id="amount" name="amount" placeholder="Enter expense amount"
                        onChange={handleChange}
                        onBlur={handleBlur}
                        value={values.amount} />

                     <label for="date">Spend Date <span>{errors.date && touched.date && errors.date}</span></label>
                     <input type="date" id="date" name="date" placeholder="Enter date"
                        onChange={handleChange}
                        onBlur={handleBlur}
                        value={values.date} />

                     <label for="category">Category <span>{errors.category && touched.category && errors.category}</span></label>
                     <select id="category" name="category"
                        onChange={handleChange}
                        onBlur={handleBlur}
                        value={values.category}>
                        <option value="">Select</option>
                        <option value="Food">Food</option>
                        <option value="Entertainment">Entertainment</option>
                        <option value="Academic">Academic</option>
                     </select>

                     <input type="submit" value="Submit" disabled={isSubmitting} />
                  </form>
               )
            }
         </Formik>
      </div>
   )
}

最后,导出组件。

export default ExpenseForm

下面给出了ExpenseForm组件的完整代码。

import React from 'react';
import './ExpenseForm.css'
import { Formik } from 'formik';

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

      this.initialValues = { name: '', amount: '', date: '', category: '' }
   }
   validate = (values) => {
      const errors = {};
      if (!values.name) {
         errors.name = 'Required';
      }
      if (!values.amount) {
         errors.amount = 'Required';
      }
      if (!values.date) {
         errors.date = 'Required';
      }
      if (!values.category) {
         errors.category = 'Required';
      }
      return errors;
   }
   handleSubmit = (values, setSubmitting) => {
      setTimeout(() => {
         alert(JSON.stringify(values, null, 2));
         setSubmitting(false);
      }, 400);
   }
   render() {
      return (
         <div id="expenseForm">
            <Formik
               initialValues={this.initialValues}
               validate={values => this.validate(values)}
               onSubmit={(values, { setSubmitting }) => this.handleSubmit(values, setSubmitting)} > 
               {
                  ({
                     values,
                     errors,
                     touched,
                     handleChange,
                     handleBlur,
                     handleSubmit,
                     isSubmitting,
                     /* and other goodies */
                  }) => 
                  (
                     <form onSubmit={handleSubmit}>
                        <label for="name">Title <span>{errors.name && touched.name && errors.name}</span></label>
                        <input type="text" id="name" name="name" placeholder="Enter expense title"
                           onChange={handleChange}
                           onBlur={handleBlur}
                           value={values.name} />

                        <label for="amount">Amount <span>{errors.amount && touched.amount && errors.amount}</span></label>
                        <input type="number" id="amount" name="amount" placeholder="Enter expense amount"
                           onChange={handleChange}
                           onBlur={handleBlur}
                           value={values.amount} />

                        <label for="date">Spend Date <span>{errors.date && touched.date && errors.date}</span></label>
                        <input type="date" id="date" name="date" placeholder="Enter date"
                           onChange={handleChange}
                           onBlur={handleBlur}
                           value={values.date} />

                        <label for="category">Category <span>{errors.category && touched.category && errors.category}</span></label>
                        <select id="category" name="category"
                           onChange={handleChange}
                           onBlur={handleBlur}
                           value={values.category}>
                           <option value="">Select</option>
                           <option value="Food">Food</option>
                           <option value="Entertainment">Entertainment</option>
                           <option value="Academic">Academic</option>
                        </select>

                        <input type="submit" value="Submit" disabled={isSubmitting} />
                     </form>
                  )
               }
            </Formik>
         </div>
      )
   }
}
export default ExpenseForm;

index.js

在 src 文件夹下创建一个文件 index.js 并使用 ExpenseForm 组件。

import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseForm from './components/ExpenseForm'

ReactDOM.render(
   <React.StrictMode>
      <ExpenseForm />
   </React.StrictMode>,
   document.getElementById('root')
);

index.html

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

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

使用 npm 命令为应用程序提供服务。

npm start

打开浏览器,在地址栏中输入 http://localhost:3000,然后按回车键。

Root Folder

最后,输入示例费用明细并单击提交。提交的数据将被收集并显示在弹出消息框中。

Root Folders

表单的交互式版本如下 −

Root Folder

ReactJS - 条件渲染

React 中的条件渲染

条件渲染用于根据情况向用户显示/隐藏 UI 的某个部分。例如,如果用户未登录 Web 应用程序,Web 应用程序将显示登录按钮。当用户登录 Web 应用程序时,相同的链接将被欢迎消息替换。

让我们了解 React 提供的支持条件渲染的选项。

条件渲染的方法

React 提供了多种在 Web 应用程序中进行条件渲染的方法。它们如下 −

  • 条件语句

  • JSX / UI 变量

  • 逻辑 && JSX 中的运算符

  • JSX 中的条件运算符

  • null 返回值

条件语句

条件语句是一种简单而直接的选项,可根据条件呈现 UI。我们假设要求编写一个组件,该组件将根据用户的登录状态显示登录链接或欢迎消息。以下是使用条件渲染的组件的实现,

function Welcome(props) {
   if(props.isLoggedIn) {
      return <div>Welcome, {props.userName}</div>
   } else {
      return <div><a href="/login">Login</a></div>
   }
}

这里,

  • 使用 isLoggedIn 属性来检查用户是否已登录。

  • 如果用户已登录,则返回欢迎消息。

  • 如果用户未登录,则返回登录链接。

该组件的使用方法如下所示 C−

function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <Welcome isLoggedIn={true} userName={'John'} />
            </div>
         </div>
      </div>
   );
}

组件将呈现如下所示的欢迎消息 −

Conditional Rendering React

JSX / UI 变量

React 允许将 JSX 元素存储到变量中并在必要时使用它。开发人员可以通过条件语句创建必要的 JSX 并将其存储在变量中。将 UI 存储在变量中后,即可呈现,如下所示 −

function Welcome(props) {
   let output = null;
   if(props.isLoggedIn) {
      output = <div>Welcome, {props.userName}</div>
   } else {
      output = <div><a href="/login">Login</a></div>
   }
   return output
}

在这里,我们使用变量 output 来保存 UI。该组件的使用方法如下所示 −

function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <Welcome isLoggedIn={true} userName={'John'} />
            </div>
         </div>
      </div>
   );
}

组件将呈现如下所示的欢迎消息 −

JSX UI Variable

逻辑 && 运算符

React 允许在 JSX 代码中使用任何表达式。在 Javascript 中,条件从左到右应用。如果最左边的条件为假,则不会处理下一个条件。开发人员可以利用此功能并在 JSX 本身中输出消息,如下所示 −

function ShowUsers(props) {
   return (
      <div>
         <ul>
            {props.users && props.users.length > 0 &&
               props.users.map((item) => 
                  (
                     <li>{item}</li>
                  )
               )}
         </ul>
      </div>
   );
}
export default ShowUsers;

这里,

  • 首先,将检查 props.users 是否可用。如果 props.users 为空,则不会进一步处理条件

  • 一旦 props.users 可用,将检查数组的长度,只有长度大于零时,才会进一步处理条件

  • 最后,props.users 将通过 map 函数循环,并将用户信息呈现为无序列表。

该组件可以按如下所示使用 −

function App() {
   const users = ['John', 'Peter']
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <ShowUsers users={users} />
            </div>
         </div>
      </div>
   );
}

组件将呈现用户,如下所示 −

逻辑运算符

JSX 中的条件运算符

由于 React 允许在 JSX 中使用任何 javascript 表达式,因此开发人员可以在 JSX 中使用条件运算符 (a =b ? x : y),并仅呈现必要的 UI 元素,如下所示−

function Welcome(props) {
   if(props.isLoggedIn) {
      return props.isLoggedIn ?
      <div>Welcome, {props.userName}</div> : <div><a href="/login">Login</a></div>
   }
}

在这里,我们使用了条件运算符来显示欢迎消息或登录链接。该组件的使用方法如下所示 −

function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <Welcome isLoggedIn={true} userName={'John'} />
            </div>
         </div>
      </div>
   );
}

组件将呈现如下所示的欢迎消息 −

Conditional Operator JSX

返回值 null

仅当组件返回 UI 元素时,React 才会呈现组件。否则,它会默默跳过渲染而不显示任何错误消息。开发人员可以利用此功能,仅在满足条件时呈现特定的 UI。

function Welcome(props) {
    return props.isLoggedIn ? <div>Welcome, {props.userName}</div> : null
}

这里,

  • 我们使用了条件运算符来显示/隐藏欢迎消息

  • null不呈现任何 UI

该组件可以按如下所示使用−

function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <div>Welcome component will not output any content</div>
                  <Welcome isLoggedIn={false} />
               </div>
            </div>
         </div>
      </div>
   );
}

组件将呈现如下所示的欢迎消息 −

The Null Return Value

摘要

React 提供了多种有条件地呈现 UI 元素的方法。开发人员必须根据情况选择方法

ReactJS - 列表

列表和 For

React 中最常见的模式是将项目集合转换为 React 元素。JavaScript 有很多选项可以操作集合。让我们在本章中了解如何使用 for 循环使用集合。

for 循环

简单易用的解决方案是使用经过时间测试的 for 循环遍历集合并使用 JSX 表达式创建最终的 React 元素。让我们创建一个 React 应用并尝试应用 for 循环。

使用 create-react-app 创建一个新应用并启动该应用。

create-react-app myapp
cd myapp
npm start

接下来,在 components 文件夹下创建一个组件 ExpenseListUsingForLoop (src/components/ExpenseListUsingForLoop.js)

import React from 'react'
class ExpenseListUsingForLoop extends React.Component {
   render() {
      return <table>
         <thead>
            <tr>
               <th>Item</th>
               <th>Amount</th>
            </tr>
         </thead>
         <tbody>
         
         </tbody>
         <tfoot>
            <tr>
               <th>Sum</th>
               <th></th>
            </tr>
         </tfoot>
      </table>
   }
}
export default ExpenseListUsingForLoop

在这里,我们创建了一个带有页眉和页脚的基本表格结构。

接下来,创建一个函数来查找总费用金额。我们稍后会在 render() 方法中使用它。

getTotalExpenses() {
   var items = this.props['expenses'];
   var total = 0;
   for(let i = 0; i < items.length; i++) {
      total += parseInt(items[i]);
   }
   return total;
}

这里 getTotalExpenses 循环遍历费用 props,汇总总费用。然后在 render 方法中添加费用项目和总金额。

render() {
   var items = this.props['expenses'];
   var expenses = []
   for(let i = 0; i < items.length; i++) {
      expenses.push(<tr><td>item {i + 1}</td><td>{items[i]}</td></tr>)
   }
   var total = this.getTotalExpenses();
   return <table>
      <thead>
         <tr>
            <th>Item</th>
            <th>Amount</th>
         </tr>
      </thead>
      <tbody>
         {expenses}
      </tbody>
      <tfoot>
         <tr>
            <th>Sum</th>
            <th>{total}</th>
         </tr>
      </tfoot>
   </table>
}

这里,

  • 使用 for 循环导航费用数组中的每个项目,使用 JSX 生成表格行 (tr),最后将其推送到费用数组中。

  • 我们在 JSX 表达式中使用了 expenses 数组来包含生成的行。

  • getTotalExpenses 方法查找总费用金额并将其添加到 render 方法中。

ExpenseListUsingForLoop 组件的完整源代码如下 −

import React from 'react'
class ExpenseListUsingForLoop extends React.Component {
   getTotalExpenses() {
      var items = this.props['expenses'];
      var total = 0;
      
      for(let i = 0; i < items.length; i++) {
         total += parseInt(items[i]);
      }
      return total;
   }
   render() {
      var items = this.props['expenses'];
      var expenses = []
      for(let i = 0; i < items.length; i++) {
         expenses.push(<tr><td>item {i + 1}</td><td>{items[i]}</td></tr>)
      }
      var total = this.getTotalExpenses();
      
      return <table>
         <thead>
            <tr>
               <th>Item</th>
               <th>Amount</th>
            </tr>
         </thead>
         <tbody>
            {expenses}
         </tbody>
         <tfoot>
            <tr>
               <th>Sum</th>
               <th>{total}</th>
            </tr>
         </tfoot>
      </table>
   }
}
export default ExpenseListUsingForLoop

接下来,使用 ExpenseListUsingForLoop 组件更新 App 组件 (App.js)。

import ExpenseListUsingForLoop from './components/ExpenseListUsingForLoop';
import './App.css';
function App() {
   var expenses = [100, 200, 300]
   return (
      <div>
         <ExpenseListUsingForLoop expenses={expenses} />
      </div>
   );
}
export default App;

接下来,在 App.css 中添加基本样式

/* Center tables for demo */
table {
   margin: 0 auto;
}
div {
   padding: 5px;
}
/* Default Table Style */
table {
   color: #333;
   background: white;
   border: 1px solid grey;
   font-size: 12pt;
   border-collapse: collapse;
}
table thead th,
table tfoot th {
   color: #777;
   background: rgba(0,0,0,.1);
   text-align: left;
}
table caption {
   padding:.5em;
}
table th,
table td {
   padding: .5em;
   border: 1px solid lightgrey;
}

最后,在浏览器中检查应用程序。它将显示如下所示的费用 −

List and For

ECMASCript 6 for 循环

让我们更改应用程序并使用 ECMAScript 6 中引入的 for .. of 循环。

for(const [idx, item] of items.entries()) {
   expenses.push(<tr><td>item {idx + 1}</td><td>{item}</td></tr>)
}

这里,

  • idx 表示数组的索引。

  • item 表示数组中每个位置上的支出项。

  • entries 方法解析数组并将其作为键值对数组返回。key 表示数组的索引,value 表示相应键中数组的值。

如果不需要索引,那么我们可以跳过 entries() 方法,如下所示 −

for(const item of items) {
   expenses.push(<tr><td></td><td>{item}</td></tr>)
}

ReactJS - Key 键

列表和键

我们在前面的章节中学习了如何使用 for 循环和 map 函数在 React 中使用集合。如果我们运行应用程序,它会按预期输出。如果我们在浏览器中打开开发者控制台,那么它将显示如下所示的警告 −

Warning: Each child in a list should have a unique "key" prop.
Check the render method of `ExpenseListUsingForLoop`. See https://reactjs.org/link/warning-keys for more information.
tr
ExpenseListUsingForLoop@
div
App

那么,这意味着什么,以及它如何影响我们的 React 应用程序。众所周知,React 尝试通过各种机制仅渲染 DOM 中更新的值。当 React 渲染集合时,它会尝试通过仅更新列表中更新的项目来优化渲染。

但是,React 没有任何提示来查找哪些项目是新的、更新的或删除的。为了获取信息,React 允许所有组件都有一个关键属性。唯一的要求是键的值在当前集合中应该是唯一的。

让我们重新创建我们之前的一个应用程序并应用关键属性。

create-react-app myapp
cd myapp
npm start

接下来,在组件文件夹 (src/components/ExpenseListUsingForLoop.js) 下创建一个组件 ExpenseListUsingForLoop

import React from 'react'
class ExpenseListUsingForLoop extends React.Component {
   render() {
      return <table>
         <thead>
            <tr>
               <th>Item</th>
               <th>Amount</th>
            </tr>
         </thead>
         <tbody>
         </tbody>
         <tfoot>
            <tr>
               <th>Sum</th>
               <th></th>
            </tr>
         </tfoot>
      </table>
   }
}
export default ExpenseListUsingForLoop

在这里,我们创建了一个带有页眉和页脚的基本表格结构。然后,创建一个函数来查找总费用金额。我们稍后会在 render 方法中使用它。

getTotalExpenses() {
   var items = this.props['expenses'];
   var total = 0;
   for(let i = 0; i < items.length; i++) {
      total += parseInt(items[i]);
   }
   return total;
}

这里,getTotalExpenses 循环遍历费用 props 并汇总总费用。然后,在 render 方法中添加费用项目和总金额。

render() {
   var items = this.props['expenses'];
   var expenses = []
   expenses = items.map((item, idx) => <tr key={idx}><td>item {idx + 1}</td><td>{item}</td></tr>)
   var total = this.getTotalExpenses();
   return <table>
      <thead>
         <tr>
            <th>Item</th>
            <th>Amount</th>
         </tr>
      </thead>
      <tbody>
         {expenses}
      </tbody>
      <tfoot>
         <tr>
            <th>Sum</th>
            <th>{total}</th>
         </tr>
      </tfoot>
   </table>
}

这里,

  • 使用 map 函数导航费用数组中的每个项目,使用 transform 函数为每个条目创建表行 (tr),最后在 expenses 变量中设置返回的数组。

  • 使用项目的索引值设置每行的关键属性。

  • 在 JSX 表达式中使用 expenses 数组来包含生成的行。

  • 使用 getTotalExpenses 方法查找总费用金额并将其添加到 render 方法中。

ExpenseListUsingForLoop 组件的完整源代码如下 −

import React from 'react'
class ExpenseListUsingForLoop extends React.Component {
   getTotalExpenses() {
      var items = this.props['expenses'];
      var total = 0;
      for(let i = 0; i < items.length; i++) {
         total += parseInt(items[i]);
      }
      return total;
   }
   render() {
      var items = this.props['expenses'];
      var expenses = []
      expenses = items.map(
         (item, idx) => <tr key={idx}><td>item {idx + 1}</td><td>{item}</td></tr>)
      var total = this.getTotalExpenses();
      return <table>
         <thead>
            <tr>
               <th>Item</th>
               <th>Amount</th>
            </tr>
         </thead>
         <tbody>
            {expenses}
         </tbody>
         <tfoot>
            <tr>
               <th>Sum</th>
               <th>{total}</th>
            </tr>
         </tfoot>
      </table>
   }
}
export default ExpenseListUsingForLoop

接下来,使用 ExpenseListUsingForLoop 组件更新 App 组件 (App.js)。

import ExpenseListUsingForLoop from './components/ExpenseListUsingForLoop';
import './App.css';
function App() {
   var expenses = [100, 200, 300]
   return (
      <div>
         <ExpenseListUsingForLoop expenses={expenses} />
      </div>
   );
}
export default App;

接下来,在 App.css 中添加包含基本样式。

/* 演示的中心表格 */
table {
    margin: 0 auto;
}
div {
    padding: 5px;
}
/* 默认表格样式 */
table {
   color: #333;
   background: white;
   border: 1px solid grey;
   font-size: 12pt;
   border-collapse: collapse;
}
table thead th,
table tfoot th {
   color: #777;
   background: rgba(0,0,0,.1);
   text-align: left;
}
table caption {
   padding:.5em;
}
table th,
table td {
   padding: .5em;
   border: 1px solid lightgrey;
}

接下来,在浏览器中检查应用程序。它将显示费用,如下所示 −

List and Keys

最后,打开开发者控制台,发现没有显示关于 key 的警告。

Key 和 index

我们了解到 key 应该是唯一的,以优化组件的渲染。我们使用了 index 值,错误消失了。为列表提供值仍然是正确的方法吗?答案是肯定和否定。设置索引键在大多数情况下都有效,但如果我们在应用程序中使用不受控制的组件,它将以意想不到的方式运行。

让我们更新我们的应用程序并添加两个新功能,如下所示 −

  • 在费用金额旁边的每一行添加一个输入元素。

  • 添加一个按钮以删除列表中的第一个元素。

首先,添加一个构造函数并设置应用程序的初始状态。由于我们将在应用程序运行时删除某些项目,因此我们应该使用状态而不是道具。

constructor(props) {
   super(props)
   this.state = {
      expenses: this.props['expenses']
   }
}

接下来,添加一个函数来删除列表的第一个元素。

remove() {
   var itemToRemove = this.state['expenses'][0]
   this.setState((previousState) => ({
      expenses: previousState['expenses'].filter((item) => item != itemToRemove)
   }))
}

接下来,在构造函数中绑定 remove 函数,如下所示 −

constructor(props) {
   super(props)
   this.state = {
      expenses: this.props['expenses']
   }
   this.remove = this.remove.bind(this)
}

接下来,在表格下方添加一个按钮,并在其 onClick 动作中设置删除函数。

render() {
   var items = this.state['expenses'];
   var expenses = []
   expenses = items.map(
      (item, idx) => <tr key={idx}><td>item {idx + 1}</td><td>{item}</td></tr>)
   var total = this.getTotalExpenses();
   return (
      <div>
         <table>
            <thead>
               <tr>
                  <th>Item</th>
                  <th>Amount</th>
               </tr>
            </thead>
            <tbody>
               {expenses}
            </tbody>
            <tfoot>
               <tr>
                  <th>Sum</th>
                  <th>{total}</th>
               </tr>
            </tfoot>
         </table>
         <div>
            <button onClick={this.remove}>Remove first item</button>
         </div>
      </div>
   )
}

接下来,在与金额相邻的所有行中添加一个输入元素,如下所示 −

expenses = items.map((item, idx) => <tr key={idx}><td>item {idx + 1}</td><td>{item} <input /></td></tr>)

接下来,在浏览器中打开应用程序。应用程序将呈现如下图所示 − Key and Index

接下来,在第一个输入框中输入金额(例如 100),然后单击"删除第一个项目"按钮。这将删除第一个元素,但输入的金额将填充在第二个元素旁边的输入中(金额:200),如下所示 −

Key and Index

要解决此问题,我们应该删除将索引用作键。相反,我们可以使用唯一 id 来表示项目。让我们将项目从数字数组更改为对象数组,如下所示(App.js),

import ExpenseListUsingForLoop from './components/ExpenseListUsingForLoop';
import './App.css';
function App() {
   var expenses = [
      {
         id: 1,
         amount: 100
      },
      {
         id: 2,
         amount: 200
      },
      {
         id: 3,
         amount: 300
      }
   ]
   return (
      <div>
      <ExpenseListUsingForLoop expenses={expenses} />
      </div>
   );
}
export default App;

接下来,通过将 item.id 设置为 key 来更新渲染逻辑,如下所示 −

expenses = items.map((item, idx) => <tr key={item.id}><td>{item.id}</td><td>{item.amount} <input /></td></tr>)

接下来,更新 getTotalExpenses 逻辑,如下所示 −

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

在这里,我们更改了从对象获取金额的逻辑(item[i].amount)。最后,在浏览器中打开应用程序并尝试复制之前的问题。在第一个输入框中添加一个数字(例如 100),然后单击"删除第一个项目"。现在,第一个元素被删除,并且输入的输入值不会保留在下一个输入框中,如下所示 &minuss;

键和索引

ReactJS - 路由

在 Web 应用程序中,路由是将 Web URL 绑定到 Web 应用程序中特定资源的过程。在 React 中,它将 URL 绑定到组件。React 本身不支持路由,因为它基本上是一个用户界面库。React 社区提供了许多第三方组件来处理 React 应用程序中的路由。让我们学习 React Router,它是 React 应用程序的首选路由库

安装 React Router

让我们学习如何在我们的 Expense Manager 应用程序中安装 React Router 组件。

打开命令提示符并转到我们应用程序的根文件夹。

cd /go/to/expense/manager

使用以下命令安装 react router。

npm install react-router-dom --save

React Router

React router 提供四个组件来管理 React 应用程序中的导航。

Router − Router 是顶级组件。它封装了整个应用程序。

Link − 类似于 html 中的锚标记。它设置目标 url 以及参考文本。

<Link to="/">Home</Link>

此处,to 属性用于设置目标 url。

Route − 将目标 url 映射到组件。

嵌套路由

React router 也支持嵌套路由。让我们使用以下示例创建应用程序来了解嵌套路由 −

Home.jsx

import React from "react";
function Home() {
  return (
    <div className="Home">
      <h1>This is Home</h1>
    </div>
  );
}
export default Home;

About.jsx

import React from "react";
function About() {
  return (
    <div className="About">
      <h1>AboutUs</h1>
      <p>tutorialspoint India</p>
    </div>
  );
}
export default About;

Contact.jsx

import React from "react";
function Contact() {
  return (
    <div className="Contact">
      <h1>Contact-Us</h1>
      <p>
        Tutorials Point India Private Limited, 4th Floor, Incor9 Building, Plot
        No: 283/A, Kavuri Hills, Madhapur, Hyderabad, Telangana, INDIA-500081
      </p>
    </div>
  );
}
export default Contact;

创建导航

让我们介绍一下我们上面创建的组件之间的导航。应用程序的最小屏幕如下所示 −

  • Home screen − 应用程序的着陆或初始屏幕

  • About − 显示应用程序的描述

  • Contact − 包含联系信息

以下 Navigate.jsx 文件的完整代码将包含从一个组件到另一个组件的链接。它将建立从着陆页到其他组件的链接。

Navigate.jsx

import React from "react";
import { Outlet, Link } from "react-router-dom";

function Navigate() {
  return (
    <div>
      <ul style={{ listStyle: "none" }}>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About-Us</Link>
        </li>
        <li>
          <Link to="/contact">Contact-Us</Link>
        </li>
      </ul>

      <Outlet />
    </div>
  );
}
export default Navigate;

接下来,在 src/components 文件夹下创建一个文件 App.js 并开始编辑。App 组件的目的是在一个组件中处理所有屏幕。它将配置路由并启用到所有其他组件的导航。

我们将 React 库和应用程序的其他组件导入到 App.jsx。在最新版本的 React 中,我们只使用 <Route> 标签,而不是 Switch。这是嵌套路由发生的地方。

App.jsx

import { Route, Routes, BrowserRouter } from "react-router-dom";
import "./App.css"
import Home from "./Router/Home";
import About from "./Router/About";
import Contact from "./Router/Contact";
import Navigate from "./Router/Navigate";
function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Navigate />}>
            <Route index element={<Home />} />

            <Route path="About" element={<About />} />

            <Route path="Contact" element={<Contact />} />
          </Route>
        </Routes>
      </BrowserRouter>
    </div>
  );
}
export default App;

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

npm start

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

尝试导航链接并确认路由正常工作。

Navigate Links

React Router 的优势

以下是 React Routing 的优势列表 −

  • 如果渲染的数据量较少,组件之间的路由会变得更快。

  • 在不同组件之间切换时实现动画和过渡变得更加容易。这提供了更好的用户体验。

  • 允许导航而无需刷新页面,因为它允许单页网络或移动应用程序。

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 AppRollup 捆绑器创建一个新的 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

ReactJS - 动画

动画是现代 Web 应用程序的一个令人兴奋的功能。它给应用程序带来了耳目一新的感觉。React 社区提供了许多基于 React 的优秀动画库,如 React Motion、React Reveal、react-animations 等,React 本身提供了一个动画库 React Transition Group,作为早期的附加选项。它是一个独立的库,增强了早期版本的库。让我们在本章中学习 React Transition Group 动画库。

React Transition Group

React Transition Group 库是动画的简单实现。它不执行任何开箱即用的动画。相反,它公开了核心动画相关信息。每个动画基本上都是元素从一个状态到另一个状态的过渡。该库公开了每个元素的最小可能状态,如下所示 −

  • Entering
  • Entered
  • Exiting
  • Exited

该库提供了设置每个状态的 CSS 样式的选项,并在元素从一个状态移动到另一个状态时根据样式为元素设置动画。该库提供了 in props 来设置元素的当前状态。如果 in props 值为 true,则表示元素正在从 entering 状态移动到 exiting 状态。如果 props 值为 false,则表示元素正在从 exiting 移动到 exited。

安装

要安装此 React Transition Group 库,请使用以下任一命令 −

# npm
npm install react-transition-group --save

# yarn
yarn add react-transition-group

Transition

TransitionReact Transition Group 提供的用于为元素设置动画的基本组件。让我们创建一个简单的应用程序,并尝试使用 Transition 元素 淡入/淡出元素。

首先,按照创建 React 应用程序一章中的说明,使用 Create React AppRollup 捆绑器创建一个新的 React 应用程序 react-animation-app

接下来,安装 React Transition Group 库。

cd /go/to/project
npm install react-transition-group --save

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

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

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

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

接下来,导入 React 和动画库。

import React from 'react'; 
import { Transition } from 'react-transition-group'

接下来,创建 HelloWorld 组件。

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

接下来,在构造函数中将过渡相关样式定义为 JavaScript 对象。

this.duration = 2000;
this.defaultStyle = {
   transition: `opacity ${this.duration}ms ease-in-out`,
   opacity: 0,
}
this.transitionStyles = {
   entering: { opacity: 1 },
   entered: { opacity: 1 },
   exiting: { opacity: 0 },
   exited: { opacity: 0 },
};

这里,

  • defaultStyles设置过渡动画

  • transitionStyles设置各种状态的样式

接下来,在构造函数中设置元素的初始状态。

this.state = {
    inProp: true
}

接下来,通过每 3 秒更改一次 inProp 值来模拟动画。

setInterval(() => {
   this.setState((state, props) => {
      let newState = {
         inProp: !state.inProp
      };
      return newState;
   })
}, 3000);

接下来,创建一个 render 函数。

render() { 
   return ( 
   ); 
}

接下来,添加 Transition 组件。使用 this.state.inProp 作为 in 属性,使用 this.duration 作为 timeout 属性。Transition 组件需要一个函数,该函数返回用户界面。它基本上是一个 Render props

render() {
   return (
      <Transition in={this.state.inProp} timeout={this.duration}>
         {state => ({
            ... component's user interface.
         })
      </Transition>
   );
}

接下来,在容器内编写组件用户界面,并为容器设置 defaultStyletransitionStyles

render() {
   return (
      <Transition in={this.state.inProp} timeout={this.duration}>
         {state => (
            <div style={{
               ...this.defaultStyle,
               ...this.transitionStyles[state]
            }}>
               <h1>Hello World!</h1>
            </div>
         )}
      </Transition>
   );
}

最后,暴露组件。

export default HelloWorld

组件完整源代码如下 −

import React from "react";
import { Transition } from 'react-transition-group';

class HelloWorld extends React.Component {
   constructor(props) {
      super(props);
      this.duration = 2000;
      this.defaultStyle = {
         transition: `opacity ${this.duration}ms ease-in-out`,
         opacity: 0,
      }
      this.transitionStyles = {
         entering: { opacity: 1 },
         entered: { opacity: 1 },
         exiting: { opacity: 0 },
         exited: { opacity: 0 },
      };
      this.state = {
         inProp: true
      }
      setInterval(() => {
         this.setState((state, props) => {
            let newState = {
               inProp: !state.inProp
            };
            return newState;
         })
      }, 3000);
   }
   render() {
      return (
         <Transition in={this.state.inProp} timeout={this.duration}>
            {state => (
               <div style={{
                  ...this.defaultStyle,
                  ...this.transitionStyles[state]
               }}>
                  <h1>Hello World!</h1>
               </div>
            )}
         </Transition>
      );
   }
}
export default HelloWorld;

接下来,在 src 文件夹下创建一个文件 index.js,并使用 HelloWorld 组件。

import React from 'react';
import ReactDOM from 'react-dom';
import HelloWorld from './components/HelloWorld';

ReactDOM.render(
   <React.StrictMode   
      <HelloWorld /   
   </React.StrictMode   ,
   document.getElementById('root')
);

最后,在根文件夹下创建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 商店中删除该项目。

Animation

要了解有关使用 React Transition Group 为元素设置动画的更多信息,请单击此处

CSSTransition

CSSTransition 建立在 Transition 组件之上,它通过引入 classNames 属性改进了 Transition 组件。 classNames prop 指的是元素各种状态所用的 css 类名。

例如,classNames=hello prop 指的是下面的 css 类。

.hello-enter {
   opacity: 0;
}
.hello-enter-active {
   opacity: 1;
   transition: opacity 200ms;
}
.hello-exit {
   opacity: 1;
}
.hello-exit-active {
   opacity: 0;
   transition: opacity 200ms;
}

让我们使用 CSSTransition 组件创建一个新组件 HelloWorldCSSTransition

首先,在您最喜欢的编辑器中打开我们的 react-animation-app 应用程序

接下来,在 src/components 文件夹下创建一个新文件 HelloWorldCSSTransition.css,并输入过渡类。

.hello-enter {
   opacity: 1;
   transition: opacity 2000ms ease-in-out;
}
.hello-enter-active {
   opacity: 1;
   transition: opacity 2000ms ease-in-out;
}
.hello-exit {
   opacity: 0;
   transition: opacity 2000ms ease-in-out;
}
.hello-exit-active {
   opacity: 0;
   transition: opacity 2000ms ease-in-out;
}

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

接下来,导入 React 和动画库。

import React from 'react'; 
import { CSSTransition } from 'react-transition-group'

接下来,导入 HelloWorldCSSTransition.css

import './HelloWorldCSSTransition.css'

接下来,创建 HelloWorld 组件。

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

接下来,在构造函数中定义过渡的持续时间。

this.duration = 2000;

接下来,在构造函数中设置元素的初始状态。

this.state = { 
   inProp: true 
}

接下来,通过每 3 秒更改一次 inProp 值来模拟动画。

setInterval(() => {
   this.setState((state, props) => {
      let newState = {
         inProp: !state.inProp
      };
      return newState;
   })
}, 3000);

接下来,创建一个 render 函数。

render() { 
   return (
   ); 
}

接下来,添加 CSSTransition 组件。使用 this.state.inProp 作为 in 属性,使用 this.duration 作为 timeout 属性,使用 hello 作为 classNames 属性。CSSTransition 组件需要将用户界面作为子属性。

render() {
   return (
      <CSSTransition in={this.state.inProp} timeout={this.duration} 
         classNames="hello">
         // ... user interface code ...   
      </CSSTransition>
   );
}

接下来,编写组件的用户界面。

render() {
   return (
       <CSSTransition in={this.state.inProp} timeout={this.duration} 
      classNames="hello">
      <div>
          <h1>Hello World!</h1>
      </div>
       </CSSTransition>
   );
}

最后,暴露组件。

export default HelloWorldCSSTransition;

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

import React from 'react';
import { CSSTransition } from 'react-transition-group'
import './HelloWorldCSSTransition.css' 

class HelloWorldCSSTransition extends React.Component {
   constructor(props) {
      super(props);
      this.duration = 2000;
      this.state = {
         inProp: true
      }
      setInterval(() => {
         this.setState((state, props) => {
            let newState = {
               inProp: !state.inProp
            };
            return newState;
         })
      }, 3000);
   }
   render() {
      return (
         <CSSTransition in={this.state.inProp} timeout={this.duration} 
            classNames="hello">
            <div>
               <h1>Hello World!</h1>
            </div>
         </CSSTransition>
      );
   }
}
export default HelloWorldCSSTransition;

接下来,在 src 文件夹下创建一个文件 index.js,并使用 HelloWorld 组件。

import React from 'react';
import ReactDOM from 'react-dom';
import HelloWorldCSSTransition from './components/HelloWorldCSSTransition';

ReactDOM.render(
   <React.StrictMode>
      <HelloWorldCSSTransition />
   </React.StrictMode>,
   document.getElementById('root')
);

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

npm start

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

消息将每 3 秒淡入淡出一次。

Animation

TransitionGroup

TransitionGroup 是一个容器组件,它管理列表中的多个过渡组件。例如,虽然列表中的每个项目都使用 CSSTransition,但 TransitionGroup 可用于对所有项目进行分组以实现适当的动画。

<TransitionGroup>
   {items.map(({ id, text }) => (
      <CSSTransition key={id} timeout={500} classNames="item" >
         <Button
            onClick={() =>
               setItems(items =>
                  items.filter(item => item.id !== id)
               )
            }
            >
            &times;
         </Button>
         {text}
      </CSSTransition>
   ))}
</TransitionGroup>

ReactJS - Bootstrap

Bootstrap 是全球前端开发人员使用的流行 CSS 框架。Bootstrap 通过其灵活、响应迅速且高性能的实用 CSS 组件为网页设计提供了出色的支持。Bootstrap 还提供了大量基于 jQuery 的 UI 组件。

使用 bootstrap CSS 和 JavaScript 组件,前端开发人员可以设计漂亮的网页,并为任何设备提供响应支持。React 可以与 Bootstrap 一起使用,并在其 Web 应用程序中获得 Bootstrap 的所有好处。让我们在本章中了解如何将 Bootstrap 集成到 React 应用程序中。

集成 Bootstrap

Bootstrap 可以通过多种方式集成到 React 应用程序中。如果开发人员只想使用 bootstrap 库中的 CSS 功能,则开发人员可以通过 CDN 导入 bootstrap 库并在所需的位置使用 bootstrap CSS 类。

如果开发人员想要使用 Bootstrap JavaScript 库,则开发人员可以使用围绕原始 bootstrap jQuery 组件包装的 react 组件,也可以使用旨在利用 bootstrap 库功能的特殊 react UI 库。

以下是将 bootstrap 库集成到 React 应用程序中的选项列表。

  • 链接标签(仅限 CSS)。

  • 导入功能(仅限 CSS)。

  • 链接标签(Bootstrap + jQuery UI)。

  • 包装器 react 组件。

  • 原生 react bootstrap 组件。

链接标签(CSS仅)

让我们在本章中学习如何通过创建 React 应用程序来应用链接标签。首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,打开主 html 页面 (public/index.html) 并在头部包含以下标签

<!-- CSS only -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css"
rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">

接下来,打开 App.css (src/App.css) 并更新 CSS 以设置按钮元素的边距。

button {
   margin: 5px;
}

接下来,打开 App 组件(src/App.js)并使用 bootstrap 按钮更新内容,如下所示 −

import './App.css'
function App() {
   return (
      <div className="container">
         <button type="button" class="btn btn-primary">Primary</button>
         <button type="button" class="btn btn-secondary">Secondary</button>
         <button type="button" class="btn btn-success">Success</button>
         <button type="button" class="btn btn-danger">Danger</button>
         <button type="button" class="btn btn-warning">Warning</button>
         <button type="button" class="btn btn-info">Info</button>
         <button type="button" class="btn btn-light">Light</button>
         <button type="button" class="btn btn-dark">Dark</button>
         <button type="button" class="btn btn-link">Link</button>
      </div>
   );
}
export default App;

这里,

  • 为不同类型的按钮应用 bootstrap CSS 类。

  • 包含 App.css 样式。

最后,在浏览器中打开应用程序并检查 bootstrap 类是否正确应用于按钮元素,如下所示 −

Integrating Bootstrap

导入功能(仅限 CSS)

让我们在本节中了解如何使用导入功能集成 bootstrap CSS。

首先,创建一个新的 react 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,使用以下命令安装 bootstrap 库。

npm install --save bootstrap

接下来,打开 App.css (src/App.css) 并更新 CSS 以设置按钮元素的边距。

button {
   margin: 5px;
}

接下来,打开 App 组件 (src/App.js),导入 bootstrap css 并使用 bootstrap 按钮更新内容,如下所示 −

// Bootstrap CSS
import "bootstrap/dist/css/bootstrap.min.css";
// Bootstrap Bundle JS
import "bootstrap/dist/js/bootstrap.bundle.min";
import './App.css'
function App() {
   return (
      <div className="container">
         <button type="button" class="btn btn-primary">Primary</button>
         <button type="button" class="btn btn-secondary">Secondary</button>
         <button type="button" class="btn btn-success">Success</button>
         <button type="button" class="btn btn-danger">Danger</button>
         <button type="button" class="btn btn-warning">Warning</button>
         <button type="button" class="btn btn-info">Info</button>
         <button type="button" class="btn btn-light">Light</button>
         <button type="button" class="btn btn-dark">Dark</button>
         <button type="button" class="btn btn-link">Link</button>
      </div>
   );
}
export default App;

这里我们有 −

  • 使用 import 语句导入 bootstrap 类。

  • 为不同类型的按钮应用 bootstrap CSS 类。

  • 包含 App.css 样式。

最后,在浏览器中打开应用程序并检查 bootstrap 类是否正确应用于按钮元素,如下所示 −

Import Feature

链接标签 (Bootstrap + jQuery UI)

React 允许开发人员使用 createRoot 方法将 react 组件集成到网页的特定部分。此功能使开发人员能够在单个网页中使用 React 和 Bootstrap 组件。开发人员可以混合使用这两个库而不会互相影响。这是小型网页的简单且最佳选择。由于不需要任何额外的学习,因此在 Web 应用程序中实现起来既简单又安全。

包装器 React 组件

开发人员可以为必要的引导组件创建一个包装器 React 组件,并可以在其应用程序中使用它。此方法可用于引导组件未广泛使用的中等到复杂的 Web 应用程序。

原生 React 引导组件

React 社区创建了许多集成 Bootstrap 和 React 的组件库。一些流行的库如下 −

在本章中,让我们通过创建一个简单的 React 应用程序来了解如何使用 React-bootstrap。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,使用以下命令安装 bootstrap 库,

npm install --save react-bootstrap bootstrap

接下来,打开 App.css (src/App.css) 并更新 CSS 以设置按钮元素的边距。

button {
   margin: 5px;
}

接下来,打开 App 组件 (src/App.js),导入 bootstrap css 并使用 bootstrap 按钮更新内容,如下所示 −

// Bootstrap CSS
import "bootstrap/dist/css/bootstrap.min.css";
// Bootstrap Bundle JS
import "bootstrap/dist/js/bootstrap.bundle.min";
import './App.css'
import { Button } from 'react-bootstrap';
function App() {
   return (
      <div className="container">
         <Button variant="primary">Primary</Button>{' '}
         <Button variant="secondary">Secondary</Button>{' '}
         <Button variant="success">Success</Button>{' '}
         <Button variant="warning">Warning</Button>{' '}
         <Button variant="danger">Danger</Button>{' '}
         <Button variant="info">Info</Button>{' '}
         <Button variant="light">Light</Button>{' '}
         <Button variant="dark">Dark</Button> <Button variant="link">Link</Button>
      </div>
   );
}
export default App;

这里我们有 −

  • 使用 import 语句导入 bootstrap 类。

  • 导入 bootstrap 按钮组件。

  • 使用不同变体的按钮组件。

  • 包含 App.css 样式。

最后,在浏览器中打开应用程序并检查 bootstrap 类是否正确应用于按钮元素,如下所示 −

Native React Bootstrap

摘要

React 有许多选项可以与 bootstrap 库集成。React 可以在 Web 应用程序中将 bootstrap 组件顺利迁移到 React bootstrap 组件。丰富的第三方引导组件使开发人员无需脱离引导库即可提供出色的 UI/UX 体验。

ReactJS - Map

JavaScript Array 数据类型提供了一系列易于使用的函数来操作数组及其值。 map() 就是这样一个函数,它接受一个转换函数,并通过应用转换函数转换给定数组中的每个项目来创建一个新数组,并返回新创建的数组。

map 函数的签名如下 −

array.map(function(item, index, items), thisValue)

这里,

  • currentValue 表示当前元素的值

  • index 表示当前元素的索引值

  • items 表示当前元素的数组

  • thisValue 是可选的 this值,可以在调用 map 函数时传递

假设我们有一个数字列表,并且想要将数组中的每个值加倍。我们可以使用 map 函数在一行中完成此操作,如下所示 −

var numbers = [2, 4, 6]
var formed = numbers.map((val) => val + val)
for(var item of formed) { console.log(item) }

此处,输出将如下所示 −

4
8
12

示例

让我们使用 create-react-app 创建一个新应用程序并启动该应用程序。

create-react-app myapp
cd myapp
npm start

接下来,创建一个组件,组件文件夹下的 ExpenseListUsingForLoop (src/components/ExpenseListUsingForLoop.js)。

import React from 'react'
class ExpenseListUsingForLoop extends React.Component {
   render() {
      return <table>
         <thead>
            <tr>
               <th>Item</th>
               <th>Amount</th>
            </tr>
         </thead>
         <tbody>
         </tbody>
         <tfoot>
            <tr>
               <th>Sum</th>
               <th></th>
            </tr>
         </tfoot>
      </table>
   }
}
export default ExpenseListUsingForLoop

在这里,我们创建了一个带有页眉和页脚的基本表格结构。

接下来,创建一个函数来查找总费用金额。我们稍后会在 render 方法中使用它。

getTotalExpenses() {
   var items = this.props['expenses'];
   var total = 0;
   for(let i = 0; i < items.length; i++) {
      total += parseInt(items[i]);
   }
   return total;
}

这里,getTotalExpenses 循环遍历费用 props,并汇总总费用。

接下来,在 render 方法中添加费用项目和总金额。

render() {
   var items = this.props['expenses'];
   var expenses = []
   expenses = items.map((item, idx) => <tr><td>item {idx + 1}</td><td>{item}</td></tr>)
   var total = this.getTotalExpenses();
   return <table>
      <thead>
         <tr>
            <th>Item</th>
            <th>Amount</th>
         </tr>
      </thead>
      <tbody>
         {expenses}
      </tbody>
      <tfoot>
         <tr>
            <th>Sum</th>
            <th>{total}</th>
         </tr>
      </tfoot>
   </table>
}

这里我们有,

  • 使用 map 函数导航费用数组中的每个项目,使用 transform 函数为每个条目创建表行 (tr),最后在 expenses 变量中设置返回的数组。

  • 在 JSX 表达式中使用 expenses 数组来包含生成的行。

  • 使用 getTotalExpenses 方法查找总费用金额并将其添加到 render 方法中。

ExpenseListUsingForLoop 组件的完整源代码如下 −

import React from 'react'
class ExpenseListUsingForLoop extends React.Component {
   getTotalExpenses() {
      var items = this.props['expenses'];
      var total = 0;
      for(let i = 0; i < items.length; i++) {
         total += parseInt(items[i]);
      }
      return total;
   }
   render() {
      var items = this.props['expenses'];
      var expenses = []
      expenses = items.map((item, idx) => <tr><td>item {idx + 1}</td><td>{item}</td></tr>)
      var total = this.getTotalExpenses();
      return <table>
         <thead>
            <tr>
               <th>Item</th>
               <th>Amount</th>
            </tr>
         </thead>
         <tbody>
            {expenses}
         </tbody>
         <tfoot>
            <tr>
               <th>Sum</th>
               <th>{total}</th>
            </tr>
         </tfoot>
      </table>
   }
}
export default ExpenseListUsingForLoop

接下来,使用 ExpenseListUsingForLoop 组件更新 App 组件 (App.js)。

import ExpenseListUsingForLoop from './components/ExpenseListUsingForLoop';
import './App.css';
function App() {
   var expenses = [100, 200, 300]
   return (
      <div>
         <ExpenseListUsingForLoop expenses={expenses} />
      </div>
   );
}
export default App;

接下来,在 App.css 中添加包含基本样式

/* 演示表格居中 */
table {
    margin: 0 auto;
}
div {
    padding: 5px;
}
/* 默认表格样式 */
table {
   color: #333;
   background: white;
   border: 1px solid grey;
   font-size: 12pt;
   border-collapse: collapse;
}
table thead th,
table tfoot th {
   color: #777;
   background: rgba(0,0,0,.1);
   text-align: left;
}
table caption {
   padding:.5em;
}
table th,
table td {
   padding: .5em;
   border: 1px solid lightgrey;
}

最后,在浏览器中检查应用程序。它将显示费用,如下所示 −

ReactJS Map

JSX 中的 Map

JSX 允许将任何 JavaScript 表达式包含在其中。由于 map 只是 JavaScript 中的表达式,我们可以直接在 JSX 中使用它,如下所示 −

render() {
   var items = this.props['expenses'];
   var expenses = []
   
   // expenses = items.map((item, idx) => <tr><td>item {idx + 1}</td><td>{item}</td></tr>)
   var total = this.getTotalExpenses();
   return <table>
      <thead>
         <tr>
            <th>Item</th>
            <th>Amount</th>
         </tr>
      </thead>
      <tbody>
         {items.map((item, idx) => <tr><td>item {idx + 1}</td><td>{item}</td></tr>)}
      </tbody>
      <tfoot>
         <tr>
            <th>Sum</th>
            <th>{total}</th>
         </tr>
      </tfoot>
   </table>
}
export default ExpenseListUsingForLoop

ReactJS - 表格

React 通过第三方 UI 组件库提供表格组件。React 社区提供了大量的 UI / UX 组件,很难选择适合我们需求的库。

Bootstrap UI 库是开发人员的热门选择之一,被广泛使用。React Bootstrap (https://react-bootstrap.github.io/) 已将几乎所有 bootstrap UI 组件移植到 React 库,并且它对表格组件也提供了最佳支持。

让我们在本章中学习如何使用 react-bootstrap 库中的表格组件。

Table 表格组件

Table 表格组件允许开发人员在 Web 应用程序中使用 bootstrap UI 设计创建简单的表格。表格组件接受如下所示的表格标签 −

  • thead

  • tbody

  • tfoot

Table 表格组件接受一小组属性来自定义表格组件,它们如下 −

  • bordered (boolean) − 在表格和单元格的所有边上添加边框。

  • borderless (boolean) − 删除表格和单元格所有边上的边框。

  • hover (boolean) −为表格 (tbody) 中的每一行启用悬停状态。

  • responsive (boolean | string) − 为小型设备启用垂直滚动。sm | md | lg | xl 选项为相关设备启用响应式。例如,仅当设备分辨率非常小时才会启用 sm。

  • size (string) − 启用表格的紧凑渲染。可能的选项有 sm、md 等,

  • striped (boolean | string) − 为所有表格行启用斑马线条纹。columns 选项也会为列添加斑马线条纹。

  • variant (dark) 使用深色值时启用深色变体。

  • bsPrefix (string) −用于自定义底层 CSS 类的前缀。

应用表格组件

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,使用以下命令安装 bootstrap 和 react-bootstrap 库,

npm install --save bootstrap react-bootstrap

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。

// 删除所有 css 类

接下来,创建一个简单的表格组件, SimpleTable (src/Components/SimpleTable.js) 并渲染一个表格,如下所示 −

import { Table } from 'react-bootstrap';
function SimpleTable() {
   return (
      <Table striped bordered hover>
         <thead>
            <tr>
               <th>#</th>
               <th>Name</th>
               <th>Age</th>
               <th>Email</th>
            </tr>
         </thead>
         <tbody>
            <tr>
               <td>1</td>
               <td>John</td>
               <td>25</td>
               <td>john.example@tutorialspoint.com</td>
            </tr>
            <tr>
               <td>1</td>
               <td>Peter</td>
               <td>15</td>
               <td>peter.example@tutorialspoint.com</td>
            </tr>
            <tr>
               <td>1</td>
               <td>Olivia</td>
               <td>23</td>
               <td>olivia.example@tutorialspoint.com</td>
            </tr>
         </tbody>
      </Table>
   );
}
export default SimpleTable;

这里我们有,

  • 使用条纹道具创建斑马表。

  • 使用边框道具启用表格和单元格周围的边框。

  • 使用悬停道具启用悬停状态。

接下来,打开 App 组件 (src/App.js),导入 bootstrap css 并使用 bootstrap 按钮更新内容,如下所示 −

import './App.css'
import "bootstrap/dist/css/bootstrap.min.css";
import SimpleTable from './Components/SimpleTable'
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleTable />
            </div>
         </div>
      </div>
   );
}
export default App;

这里我们有,

  • 使用 import 语句导入 bootstrap 类。

  • 渲染我们的新 SimpleTable 组件。

  • 包含 App.css 样式。

最后,在浏览器中打开应用程序并检查最终结果。表格组件将呈现如下图所示 −

Applying Table Component

添加深色变体和列条

让我们在表格组件中应用深色变体和列条选项,看看它如何更新表格设计。

首先,打开我们的轮播应用程序并更新 SimpleCarousel 组件,如下所示 −

import { Table } from 'react-bootstrap';
function SimpleTable() {
   return (
      <Table bordered hover striped="columns" variant="dark">
      // ...

这里我们有,

  • 使用带有列的条纹道具来启用基于列的斑马条纹。

  • 使用带有深色选项的变体道具来启用表格设计的深色变体。

接下来,在浏览器中打开应用程序并检查最终结果。表格组件将使用列条和深色变体呈现,如下所示 −

添加深色变体和列条

摘要

Bootstrap 表格组件提供了以简单、直观和灵活的方式设计表格所需的所有选项。

ReactJS - 使用 Flux 管理状态

前端应用程序的一个重要特性是状态管理。React 对其组件有自己的状态管理技术。React 状态管理仅在组件级别工作。即使组件处于父/子关系(嵌套组件),另一个组件也不会访问组件的状态。为了解决这个问题,有很多第三方状态管理库,如 redux、mobx 等,

Flux 是有效管理应用程序状态的技术之一。Flux 由 Facebook 引入,并在其 Web 应用程序中广泛使用。Flux 使用单向数据流模式来提供清晰的状态管理。让我们在本章中了解什么是 flux 以及如何使用它。

使用 Flux 管理状态

Flux 使用单向数据流模式。它有四个不同的部分,

Store −顾名思义,所有业务数据都存储在 store 中。Store 执行两个过程。

  • Store 将通过从注册的调度程序收集数据来自行更新其数据。调度程序为 store 提供数据和相关操作。

  • 一旦数据更新,store 就会发出一个更改数据事件来通知视图数据已更改。View 将监听更改事件,并在收到更改事件后通过访问来自 store 的更新数据来更新其视图。

Action − Action 只是要处理的操作的表示,其中包含必要的数据。View 将根据用户交互创建一个包含必要数据的操作并将其发送给调度程序。例如,下面提到的有效负载是由视图(操作创建者)根据用户交互创建的,用于添加用户。

{
   actionType: "add",
   data: {
      name: "Peter"
   }
}

上述操作将传递给调度程序,调度程序将信息发送给所有已注册的商店。商店将相应地更新数据,并将更改事件发送给所有已注册的视图。

调度程序 − 调度程序接收具有适当负载的操作,并将其发送给所有已注册的商店进行进一步处理。

视图 − 视图根据用户交互创建操作并将其发送给调度程序。它向 store 注册以获取更改,一旦通过事件接收到更改,它就会使用新数据更新自身。

为了 flux 的有效工作,需要初始化一些东西,如下所示 −

  • 应用程序应使用适当的操作及其回调初始化 Dispatcher。

  • 应初始化 Store 并向 dispatcher 注册以接收数据更新。

  • 应使用 dispatcher 和 store 初始化视图。视图应该注册以监听存储更改(事件)。

flux 架构的工作流程如下 −

  • 用户在视图中交互并触发事件。

  • 视图处理事件并根据用户的操作创建操作。

  • 视图将操作发送给调度程序。

  • 调度程序将操作发布到所有向其注册的存储,

  • 已注册的存储将接收带有有效负载的操作。 Store 将根据操作进行自我更新。

  • Store 将向视图发出更改事件。

  • 监听 store 更改的视图将使用更新的数据更新前端。

使用 Flux 管理状态

应用 flux

让我们创建一个新的 react 应用程序来学习如何在本节中应用 flux 概念。首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,使用 npm 安装 flux 包,如下所示 −

npm install flux --save

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。接下来,创建一个 flux 调度程序 Dispatcher (src/Flux/Dispatcher.js),如下所示 −

import {Dispatcher} from "flux";
export default new Dispatcher();

在这里,我们从 flux 包创建了一个新的调度程序。接下来,创建动作(和动作创建者),UserActions(src/Flux/UserActions.js),如下所示 −

import dispatcher from "./Dispatcher";
export const USER_ACTIONS = {
   ADD: 'addUser'
};
export function addUser(userName) {
   dispatcher.dispatch({
      type: USER_ACTIONS.ADD,
      value: {
         name: userName
      }
   })
}

这里,

  • USER_ACTIONS.ADD 是一个常量,用于引用用户的添加操作。

  • addUser() 是用于与用户数据一起创建操作并将创建的操作分派给调度程序的方法。

接下来,创建一个存储 UserStore (src/Flux/UserStore.js),如下所示 −

import dispatcher from "./Dispatcher";
import {EventEmitter} from "events";
import * as UserActions from "./UserActions";
class UserStore extends EventEmitter {
   constructor() {
      super();
      this.users = [];
   }
   handleActions(action) {
      switch (action.type) {
         case UserActions.USER_ACTIONS.ADD: {
            this.users.push(action.value);
            this.emit("storeUpdated");
            break;
         }
         default: {
         }
      }
   }
   getUsers() {
      return this.users;
   }
}
const userStore = new userStore();
dispatcher.register(userStore.handleActions.bind(userStore));
export default userStore;

此处,

  • UserStore 扩展自 EventEmitter,用于发出更改。

  • handleActions 从调度程序检索用户详细信息并更新自身(this.users)。

  • handleActions 发出商店更新事件,以通知视图商店已更新。

  • getUsers() 方法将返回当前用户列表信息。

接下来,创建一个用户输入组件 UserInput 组件以获取新用户信息,如下所示 −

import React from "react";
import * as UserActions from "./UserActions";
export default class ButtonComponent extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         username: ''
      }
   }
   onButtonClick = () => {
      UserActions.addUser(this.state.username)
   };
   render() {
      return (
         <div>
            <input name="username" onChange={(e) => this.setState({username: e.target.value})}/>
            <button onClick={() => this.onButtonClick()}>Add user</button>
         </div>
      );
   }
}

这里,

  • 创建一个输入元素,从用户那里获取新的用户数据。

  • 添加了一个按钮,用于将用户信息提交给 UserActions 的 addUser() 方法

  • addUser 将更新用户数据,并使用适当的操作类型将其发送给调度程序。调度程序将使用操作类型调用商店。商店将更新用户列表并通知所有向其注册的视图。

接下来,创建一个用户列表组件,UserList 组件用于显示商店中可用的用户,如下所示 −

import React from "react";
import UserStore from "./UserStore";
export default class UserList extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         users: UserStore.getUsers()
      }
   }
   componentDidMount() {
      UserStore.on("storeUpdated", this.updateUserList);
   }
   componentWillUnmount() {
      UserStore.removeListener("storeUpdated", this.updateUserList);
   }
   updateUserList = () => {
      this.setState({users: UserStore.getUsers()})
   };
   render() {
      return (
         <ul>{
            this.state.users && this.state.users.length > 0 &&
            this.state.users.map((items) => <li>{items.name}</li>)
         }
         </ul>
      );
   }
}

这里,

  • componentDidMount通过UserStore.on方法注册store事件(storeUpdated)。

  • componentWillUnmount通过UserStore.removeListener方法取消注册store事件(storeUpdated)。

  • updateUserList从store获取最新的用户数据并更新其自己的store。

  • render方法从其状态(this.state.users)呈现用户列表。

接下来,打开App组件(src/App.js),并使用UserInputUserList组件,如下所示 −

import './App.css'
import React, { Suspense, lazy } from 'react';
import UserInput from './Flux/UserInput';
import UserList from './Flux/UserList';
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <UserInput />
               <UserList />
            </div>
         </div>
      </div>
   );
}
export default App;

这里,

  • UserInput 将用于从用户那里获取信息。

  • UserList 将从存储中获取最新的用户列表并呈现它。

最后,在浏览器中打开应用程序并检查最终结果。最初,用户列表将为空。一旦用户输入用户名并提交,下面的列表将显示更新的用户列表,如下所示 −

Applying Flux

摘要

Flux 是 React 应用程序的简单、单向状态管理模式。它有助于降低 React 应用程序的复杂性。它以透明的方式通过调度程序连接视图和存储。 React 社区对 flux 模式进行了增强,并发布了许多成熟的状态管理库(如 redux),功能更加强大,使用更加方便。

ReactJS - 测试

测试是确保任何应用程序中创建的功能符合业务逻辑和编码规范的过程之一。React 建议使用 React 测试库 来测试 React 组件,并使用 jest 测试运行器来运行测试。react-testing-library 允许单独检查组件。

可以使用以下命令将其安装在应用程序中 −

npm install --save @testing-library/react @testing-library/jest-dom

创建 React 应用

创建 React 应用 默认配置 React 测试库jest 测试运行器。因此,测试使用 Create React App 创建的 React 应用程序只需一个命令即可。

cd /go/to/react/application
npm test

npm test 命令类似于 npm build 命令。当开发人员更改代码时,两者都会重新编译。在命令提示符中执行命令后,它会发出以下问题。

No tests found related to files changed since last commit.
Press `a` to run all tests, or run Jest with `--watchAll`.

Watch Usage
   › Press a to run all tests.
   › Press f to run only failed tests.
   › Press q to quit watch mode.
   › Press p to filter by a filename regex pattern.
   › Press t to filter by a test name regex pattern.
   › Press Enter to trigger a test run.  

按 a 将尝试运行所有测试脚本并最终总结结果,如下所示 −

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        4.312 s, estimated 12 s
Ran all test suites.

Watch Usage: Press w to show more. 

在自定义应用程序中进行测试

在本章中,我们使用 Rollup 捆绑器 编写自定义 React 应用程序,并使用 React 测试库jest 测试运行器对其进行测试。

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

接下来,安装测试库。

cd /go/to/react-test-app
npm install --save @testing-library/react @testing-library/jest-dom

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

接下来,在 下创建一个文件 HelloWorld.test.js src/components 文件夹,为 HelloWorld 组件编写测试并开始编辑。

接下来,导入 react 库。

import React from 'react';

接下来,导入测试库。

import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom';

接下来,导入我们的 HelloWorld 组件。

import HelloWorld from './HelloWorld';

接下来,编写测试来检查文档中是否存在 Hello World 文本

test('test scenario 1', () => {
   render(<HelloWorld />);
   const element = screen.getByText(/Hello World/i);
   expect(element).toBeInTheDocument();
});

下面给出测试代码的完整源代码 −

import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import HelloWorld from './HelloWorld';

test('test scenario 1', () => {
   render(<HelloWorld />);
   const element = screen.getByText(/Hello World/i);
   expect(element).toBeInTheDocument();
});

接下来,如果系统中尚未安装 jest 测试运行器,请安装它。

npm install jest -g

接下来,在应用程序的根文件夹中运行 jest 命令。

jest

接下来,在应用程序的根文件夹中运行 jest 命令。

PASS  src/components/HelloWorld.test.js
   √ test scenario 1 (29 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        5.148 s
Ran all test suites.

ReactJS - CLI 命令

React 有自己的命令行界面 (CLI) 命令。但是,这些 CLI 命令目前仅用于使用命令行创建 React 应用程序的可传递版本。这将包含一个默认模板作为其设计,因此以这种方式创建的所有 React 应用程序都将具有很好的一致性,因为它们都具有相同的结构。

React 中的基本 CLI 命令

让我们在本章中学习 Create React App 命令行应用程序中可用的基本命令。

创建新应用程序

Create React App 提供多种创建 React 应用程序的方法。

使用 npx 脚本。

npx create-react-app <react-app-name>
npx create-react-app hello-react-app

使用 npm 包管理器。

npm init react-app <react-app-name>
npm init react-app hello-react-app

使用 yarn 包管理器。

yarn init react-app <react-app-name>
yarn init react-app hello-react-app

选择模板

Create React App 使用默认模板创建 React 应用程序。模板是指具有某些内置功能的初始代码。npm 包服务器中有数百个具有许多高级功能的模板。 Create React App 允许用户通过 -template 命令行开关选择模板。

create-react-app my-app --template typescript

上述命令将使用来自 npm 服务器的 cra-template-typescript 包创建 React 应用。

安装依赖项

可以使用常规 npmyarn 包命令安装 React 依赖项包,因为 React 使用 npmyarn 推荐的项目结构。

使用 npm 包管理器。

npm install --save react-router-dom

使用 yarn 包管理器。

yarn add react-router-dom

运行应用程序

可以使用 npmyarn 命令启动 React 应用程序,具体取决于项目中使用的包管理器。

使用 npm 包管理器。

npm start

使用 yarn 包管理器。

yarn start

要在安全模式 (HTTPS) 下运行应用程序,请在启动应用程序之前设置环境变量 HTTPS 并将其设置为 true。例如,在 Windows 命令提示符 (cmd.exe) 中,以下命令设置 HTTPS 并以 HTTPS 模式启动应用程序。

set HTTPS=true && npm start

ReactJS - 构建和部署

在本章中,让我们学习如何进行 React 应用程序的生产构建和部署。

构建

一旦完成 React 应用程序开发,就需要将应用程序捆绑并部署到生产服务器。在本章中,让我们学习可用于构建和部署应用程序的命令。

一个命令就足以创建应用程序的生产版本。

npm run build
> expense-manager@0.1.0 build path	o\expense-manager
> react-scripts build

Creating an optimized production build...
Compiled with warnings.

File sizes after gzip:

   41.69 KB   build\static\js\2.a164da11.chunk.js
    2.24 KB   build\static\js\main.de70a883.chunk.js
    1.4  KB   build\static\js\3.d8a9fc85.chunk.js
    1.17 KB   build\static\js
untime-main.560bee6e.js
  493     B   build\static\css\main.e75e7bbe.chunk.css

The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.

The build folder is ready to be deployed.
You may serve it with a static server:

   npm install -g serve
   serve -s build

Find out more about deployment here:

   https://cra.link/deployment

一旦构建了应用程序,该应用程序便可在 build/static 文件夹下使用。

默认情况下,profiling 选项处于禁用状态,可以通过 -profile 命令行选项启用。-profile 将在代码中包含分析信息。分析信息可与 React DevTools 一起使用来分析应用程序。

npm run build -- --profile

部署

一旦构建了应用程序,就可以将其部署到任何 Web 服务器。让我们在本章中学习如何部署 React 应用程序。

本地部署

可以使用 serve 包进行本地部署。首先使用以下命令安装 serve 包 −

npm install -g server

要使用 serve 启动应用程序,请使用以下命令 −

cd /go/to/app/root/folder
serve -s build

默认情况下,serve 使用端口 5000 为应用程序提供服务。可以通过 http://localhost:5000 查看应用程序。

生产部署

只需将 build/static 文件夹下的文件复制到生产应用程序的根目录,即可轻松完成生产部署。它可以在包括 Apache、IIS、Nginx 等在内的所有 Web 服务器中运行。

相对路径

默认情况下,生产版本是在假设应用程序将托管在 Web 应用程序的根文件夹中的情况下创建的。如果应用程序需要托管在子文件夹中,则在 package.json 中使用以下配置,然后构建应用程序。

{ ... "homepage": "http://domainname.com/path/to/subfolder", ... }

ReactJS - 示例

在本章中,让我们应用在本教程中学到的概念来创建一个示例 Expense Manager(费用管理器)应用程序。一些概念列于下方 −

  • React 基础知识(组件、jsx、props 和状态)

  • 使用 react-router

  • 的路由器
  • Http 客户端编程(Web API)

  • 使用 Formik 进行表单编程

  • 使用 Redux 进行高级状态管理

  • 异步/等待编程

功能

我们的示例 Expense Manager(费用管理器)应用程序的一些功能是 −

  • 从服务器列出所有费用

  • 添加费用项目

  • 删除费用项目

在这里,

ReactJS - Hooks 简介

在 React 16.8 之前,函数组件只是无状态组件。要向组件添加状态,我们需要将函数组件转换为基于类的组件。此外,函数组件没有操纵组件生命周期事件的选项。为了在函数组件中启用状态和生命周期事件,React 引入了一个名为 Hooks 的新概念。

Hooks 是普通的 JavaScript 函数,可以访问使用/应用它的组件的状态和生命周期事件。一般来说, Hooks 以 use 关键字开头。React 带有一些内置 Hooks ,也允许创建自定义 Hooks 。

内置 Hooks

让我们知道 React 中可用的 Hooks 列表及其基本用法。

  • useState −用于操作组件的状态。

  • useReducer − useState 的高级版本,带有 Reducer 概念。

  • useEffect − 用于挂接到组件的生命周期。

  • useLayoutEffect − 与 useEffect 类似,但在所有 DOM 变异之后或 DOM 即将在屏幕上绘制之前同步触发。

  • useContext − 提供对组件内部上下文提供程序的访问。

  • useMemo − 用于返回变量/函数的记忆版本,该版本仅根据提供的预定义依赖项集进行更改。这将减少昂贵计算的重新计算次数并提高应用程序的性能。

  • useCallback − 返回回调函数的记忆版本,该版本仅根据提供的预定义依赖项集进行更改。

  • useRef − 基于 React ref 对象提供对原始 DOM 节点的访问。

  • useImperativeHandle − 用于将子组件中基于 ref 的值公开给父组件。

  • useDeferredValue − 用于推迟类似于防抖或节流的值以推迟更新。

  • useDebug −用于在 React DevTools 中显示自定义 Hooks 的标签。

  • useTransition − 用于标识转换的待处理状态。

  • useId −用于为应用程序中的元素创建唯一 ID。

应用 Hooks

让我们通过创建应用程序来学习如何在函数组件中使用 Hooks 。

使用 create-react-app 创建 React 应用程序并使用以下命令启动该应用程序

create-react-app myapp
cd myapp
npm start

接下来,让我们创建一个新的函数组件 HelloWorld (src/components/HelloWorld.js),它呈现一个输入元素并根据用户输入到输入元素中的数据呈现问候消息。

import { useState } from 'react';
export default function HelloWorld() {
   const [name, setName] = useState("World")
   return (
      <div style={{ textAlign: "center", padding: "5px" }}>
         <input id="name" name="name"
            value={name}
            onChange={(e) => setName(e.target.value)} />
         <div>Hello {name}</div>
      </div>
   )
}

此处,

  • useState 是一个 Hooks ,它接收一个初始值并返回一个状态和一个用于更新状态的函数。它接收 World 作为初始值并返回一个包含两个项目的数组,a) 状态的初始值 (name) 和 b) 用于更新状态的函数 (setName)。使用的语法是数组破坏语法,用于获取数组值并将其设置为 name 和 setName 变量。

  • input 是一个带有 onChange 事件的反应输入元素。 onchange 事件通过 event.target.value 获取用户的更新值,并使用 setName 函数将其设置为当前状态。

  • 每当用户更新输入时,onchange 事件就会触发并更新状态,进而触发组件的渲染函数。

接下来,让我们在我们的应用程序 (App.js) 中应用我们的组件,如下所示 −

import './App.css';
import HelloWorld from './components/HelloWorld';
function App() {
   return (
      <HelloWorld />
   );
}
export default App;

最后,打开浏览器并通过更改输入值来检查结果。只要输入发生变化,消息就会更新,如下所示 −

应用 Hooks

Hooks 的优势

Hooks 一起使用时,函数组件比基于类的组件具有许多优势。它们如下 −

  • Hooks 易于理解,并且可以快速开始编码。

  • 在大型应用程序中,应用程序的复杂性可以保持在最低水平。在基于类的组件中,复杂性(状态管理和处理生命周期事件)会随着项目的增长而增长。

  • this 对于 JavaScript 编程初学者来说很难理解类(组件)中的 this。由于函数组件和 Hooks 不依赖于此,开发人员可以快速开始使用 React 进行编码,而无需陡峭的学习曲线。

  • 状态逻辑可以在组件之间轻松重用。

  • 函数组件可以与基于类的组件一起使用,这使得它很容易在任何规模的现有项目中采用。

  • 与基于类的组件相比,函数组件只需几行代码即可编写。

Hooks 的缺点

Hooks 是创建组件的替代方法,它也有自己的缺点。它们如下 −

  • Hooks 应该只在顶层调用,并且应该避免在条件、循环或嵌套函数中使用。

  • Hooks 是专门的功能,它们可能不是最适合某些情况,我们可能必须恢复到基于类的组件。

  • React 处理 hooks 的内部结构,而不暴露核心进行优化,这使得它不太灵活,不适合某些场景。

摘要

Hooks 是一种相对较新的创建组件的方式。大量项目仍在使用基于类的组件。将这些项目中的组件从基于类转换为基于函数在实践中是不可能的,我们必须接受它。相反,我们可以分阶段转换应用程序。

ReactJS - 使用 useState

useState 是一个基本的 React hook,它允许函数组件维护自己的状态并根据状态变化重新渲染自身。 useState 的签名如下 −

const [ <state>, <setState> ] = useState( <initialValue> )

其中,

  • initialValue − 状态的初始值。 state 可以指定为任何类型(数字、字符串、数组和对象)。

  • state − 表示状态值的变量。

  • setState −函数变量,表示更新 useState 返回的状态的函数。

setState 函数的签名如下 −

setState( <valueToBeUpdated> )

其中,valueToBeUpdated 是状态需要更新的值。设置和更新用户姓名的示例用法如下 −

// 初始化状态
const [name, setName] = useState('John')

// 更新状态
setName('Peter)

功能

useState 的显著功能如下 −

函数参数 − 它接受一个函数(返回初始状态)而不是初始值,并且在组件的初始渲染期间仅执行一次该函数。如果初始值的计算成本很高,这将有助于提高性能。

const [val, setVal] = useState(() => {
   var initialValue = null
   // 初始值的计算成本高昂
   return initialValue
})

验证先前的值 − 它检查状态的当前值和先前值,只有当它们不同时,React 才会渲染其子项并触发效果。这将提高渲染的性能。

// ...
setName('John') // 更新状态并重新渲染组件
// ...
// ...
setName('John') // 不会触发子项的渲染,因为状态的值没有改变。
// ...

批量更新多个状态 − React 在内部批量处理多个状态更新。如果需要立即更新多个状态,则可以使用 React 提供的特殊函数 flushSync,该函数将立即刷新所有状态更改。

flushSync(() => setName('Peter'))

应用状态Hooks(钩子)

让我们创建一个登录表单组件,并使用 useState Hooks(钩子)维护表单的值。

首先,使用以下命令创建并启动 React 应用程序,

create-react-app myapp
cd myapp
npm start

接下来,在组件文件夹 (src/components/LoginForm.js) 下创建一个 React 组件 LoginForm

import { useState } from 'react';
export default function LoginForm() {
   // 渲染代码
}

接下来,使用 useState Hooks(钩子)创建两个状态变量,用户名和密码,如下所示 −

import { useState } from 'react';
export default function LoginForm() {
   const [username, setUsername] = useState('')
   const [password, setPassword] = useState('')
   
   // 渲染代码
}

接下来,创建一个函数来验证登录数据,如下所示−

import { useState } from 'react';
export default function LoginForm() {
   const [username, setUsername] = useState('')
   const [password, setPassword] = useState('')
   
   let isEmpty = (val) => {
      if(val == null || val == '') {
         return true;
      } else {
         return false;
      }
   }
   
   let validate = (e) => {
      e.preventDefault()
      if(!isEmpty(username) && !isEmpty(password)) {
         alert(JSON.stringify({
            username: username,
            password: password
         }))
      } else {
         alert("Please enter username / password")
      }
   }
   // render code
}

此处,isEmpty 是用于检查数据是否可用或为空的函数。

接下来,呈现一个包含两个输入字段的登录表单,并使用状态变量(usernamepassword)、状态更新方法(setUsername 和 setPassword)和验证方法来处理表单。

import { useState } from 'react';
export default function LoginForm() {
   return (
      <div style={{ textAlign: "center", padding: "5px" }}>
         <form name="loginForm">
            <label for="username">Username: </label>
               <input id="username" name="username" type="text"
               value={username}
               onChange={(e) => setUsername(e.target.value)} />
            <br />
            <label for="password">Password: </label>
               <input id="password" name="password" type="password"
               value={password}
               onChange={(e) => setPassword(e.target.value)} />
            <br />
            <button type="submit" onClick={(e) => validate(e)}>Submit</button>
         </form>
      </div>
   )
}

这里,

  • onChange使用了hook返回的状态设置函数。

  • onClick使用了validate函数来验证并显示用户输入的数据。

LoginForm组件的完整代码如下 −

import { useState } from 'react';
export default function LoginForm() {
   const [username, setUsername] = useState('')
   const [password, setPassword] = useState('')
   
   let isEmpty = (val) => {
      if(val == null || val == '') {
         return true;
      } else {
         return false;
      }
   }
   
   let validate = (e) => {
      e.preventDefault()
      if(!isEmpty(username) && !isEmpty(password)) {
         alert(JSON.stringify({
            username: username,
            password: password
         }))
      } else {
         alert("Please enter username / password")
      }
   }
   
   return (
      <div style={{ textAlign: "center", padding: "5px" }}>
         <form name="loginForm">
            <label for="username">Username: </label>
               <input id="username" name="username" type="text"
               value={username}
               onChange={(e) => setUsername(e.target.value)} />
            <br />
            <label for="password">Password: </label>
               <input id="password" name="password" type="password"
               value={password}
               onChange={(e) => setPassword(e.target.value)} />
            <br />
            <button type="submit" onClick={(e) => validate(e)}>Submit</button>
         </form>
      </div>
   )
}

接下来,更新根应用程序组件 App.js,如下所示,

import './App.css';
import HelloWorld from './components/HelloWorld';
import LoginForm from './components/LoginForm';
function App() {
   return (
      <LoginForm />
   );
}
export default App;

接下来,打开浏览器并检查应用程序。应用程序将使用状态变量收集用户输入的数据,并使用验证函数对其进行验证。如果用户输入了正确的数据,它将显示如下所示的数据 −

Applying State Hook

否则,它将抛出如下所示的错误 −

Applying State Hook

对象作为状态

在基于类的状态管理中,setState 方法支持状态对象的部分更新。例如,让我们考虑将登录表单数据作为对象保存在状态中。

Applying State Hook
{
   username: 'John',
   password: 'secret'
}

使用 setState 更新用户名只会更新状态对象中的用户名并保留密码字段。

this.setState({
    username: 'Peter'
})

在 hooks 中,setData(useState 返回的函数)将更新整个对象,如下所示 −

// 创建状态
const [data, setDate] = useState({
    username: 'John',
    password: 'secret'
})
// 更新状态 - 错误
setData({
    username: 'Peter'
})

更新后的状态没有密码字段,如下所示 −

{
   username: 'Peter'
}

为了解决这个问题,我们可以在 javascript 中使用扩展运算符,如下所示 −

setData({
    ...data,
    username: 'Peter'
})

让我们通过转换我们的 LoginForm 组件并使用对象状态变量来创建一个新组件,如下所示 −

import { useState } from 'react';
export default function LoginFormObject() {
   const [data, setData] = useState({})
   let isEmpty = (val) => {
      if(val == null || val == '') {
         return true;
      } else {
         return false;
      }
   }
   let validate = (e) => {
      e.preventDefault()
      if(!isEmpty(data.username) && !isEmpty(data.password)) {
         alert(JSON.stringify(data))
      } else {
         alert("Please enter username / password")
      }
   }
   return (
      <div style={{ textAlign: "center", padding: "5px" }}>
         <form name="loginForm">
            <label for="username">Username: </label>
               <input id="username" name="username" type="text"
               value={data.username}
               onChange={(e) => setData( {...data, username: e.target.value} )} />
            <br />
            <label for="password">Password: </label>
               <input id="password" name="password" type="password"
               value={data.password}
               onChange={(e) => setData({...data, password: e.target.value})} />
            <br />
            <button type="submit" onClick={(e) => validate(e)}>Submit</button>
         </form>
      </div>
   )
}

此处,

  • State 在对象 (data) 中维护。

  • setData 由 useState Hooks(钩子)返回并用作状态更新函数。

  • data.* 语法用于获取状态的详细信息。

  • …data 扩展运算符与 setData 函数一起使用以更新状态。

摘要

useState Hooks(钩子)是一种在函数组件中进行状态管理的简单易行的方法。useState 可用于处理状态中的单个值或多个值。它支持基本数据类型和复杂对象。它允许多个状态设置函数 (set*) 和内部批处理以简化流程。由于引入了 useState Hooks(钩子),函数组件最终被改进为可以执行任何功能(从无状态到有状态)。

ReactJS - 使用 useEffect

React 提供 useEffect 来在组件中执行副作用。一些副作用如下 −

  • 从外部源获取数据并更新渲染的内容。

  • 渲染后更新 DOM 元素。

  • 订阅

  • 使用计时器

  • 日志记录

在基于类的组件中,这些副作用是使用生命周期组件完成的。因此,useEffect Hooks(钩子)是下面提到的生命周期事件的效果替代品。

  • componentDidMount −首次渲染完成后触发。

  • componentDidUpdate − 由于 prop 或 state 更改而更新渲染后触发。

  • componentWillUnmount − 在组件销毁期间卸载渲染内容后触发。

本章让我们学习如何使用 effect hook。

useEffect 的签名

useEffect 的签名如下 −

useEffect( <update function>, <dependency> )

其中,update 函数的签名如下 −

{
   // code
   return <clean up function>
}

这里,

Update function − Update function 是每次渲染阶段后执行的函数。这对应于 componentDidMountcomponentDidUpdate 事件

Dependency − Dependency 是一个包含函数所依赖的所有变量的数组。指定依赖关系对于优化 effect hook 非常重要。一般来说,update function 会在每次渲染后调用。有时没有必要在每次渲染时都渲染 update function。假设我们从外部源获取数据,并在渲染阶段之后对其进行更新,如下所示 −

const [data, setDate] = useState({})
const [toggle, setToggle] = useState(false)
const [id, setID] = useState(0)
useEffect( () => {
    fetch('/data/url/', {id: id}).then( fetchedData => setData(fetchedData) )
})
// code

只要更新 datatoggle 变量,组件就会重新渲染。但如您所见,我们不需要在每次更新切换状态时运行定义的效果。要解决此问题,我们可以传递一个空依赖项,如下所示 −

const [data, setDate] = useState({})
const [toggle, setToggle] = useState(false)
const [id, setID] = useState(0)
useEffect( () => {
   fetch('/data/url/', { id: id }).then( fetchedData => setData(fetchedData) )
}, [])

上述代码在第一次渲染后只会运行一次效果。即使它可以解决问题,每次更改 id 时都必须运行效果。为了实现这一点,我们可以将 id 作为效果的依赖项,如下所示 −

const [data, setDate] = useState({})
const [toggle, setToggle] = useState(false)
const [id, setID] = useState(0)
useEffect( () => {
    fetch('/data/url/', { id: id }).then( fetchedData => setData(fetchedData) )
}, [id])

这将确保仅在修改 id 后才会重新运行效果

清理功能 −清理函数用于在使用订阅函数和定时器函数过程中进行清理工作,如下所示 −

const [time, setTime] = useState(new Date())
useEffect(() => {
   let interval = setInterval(() => {
      setTime(new Date())
   }, 1000)
   return () => clearInterval(interval)
}, [time])

让我们创建一个完整的应用程序来了解后面部分的清理功能。

effect hook 的功能

effect hook 的一些显著功能如下 −

  • React 允许在一个函数组件中使用多个 effect hook。这将帮助我们为每个副作用编写一个函数并将其设置为单独的效果。

  • 每个 hook 将按照声明的顺序运行。开发人员应确保正确声明效果的顺序。

  • 依赖项功能可用于提高性能和正确执行副作用。

  • 清理功能可防止内存泄漏和不必要的事件触发。

使用效果获取数据

让我们创建一个应用程序,它将从外部源获取数据并使用本节中的 useEffect Hooks(钩子)对其进行渲染。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,在组件文件夹 (src/components/NameList.js) 下创建一个 React 组件 NameList

function NameList() {
   return <div>names</div>
}
export default NameList

此处,NameList 组件的目的是展示常见名称的流行列表

接下来,更新根组件 App.js 以使用新创建的 NameList 组件。

import NameList from "./components/NameList";
function App() {
   return (
      <div style={{ padding: "5px"}}>
         <NameList />
      </div>
   );
}
export default App;

接下来,创建一个 json 文件 names.json (public/json/names.json),并以 json 格式存储流行名称,如下所示。

[
   {
      "id": 1,
      "name": "Liam"
   },
   {
      "id": 2,
      "name": "Olivia"
   },
   {
      "id": 3,
      "name": "Noah"
   },
   {
      "id": 4,
      "name": "Emma"
   },
   {
      "id": 5,
      "name": "Oliver"
   },
   {
      "id": 6,
      "name": "Charlotte"
   },
   {
      "id": 7,
      "name": "Elijah"
   },
   {
      "id": 8,
      "name": "Amelia"
   },
   {
      "id": 9,
      "name": "James"
   },
   {
      "id": 10,
      "name": "Ava"
   },
   {
      "id": 11,
      "name": "William"
   },
   {
      "id": 12,
      "name": "Sophia"
   },
   {
      "id": 13,
      "name": "Benjamin"
   },
   {
      "id": 14,
      "name": "Isabella"
   },
   {
      "id": 15,
      "name": "Lucas"
   },
   {
      "id": 16,
      "name": "Mia"
   },
   {
      "id": 17,
      "name": "Henry"
   },
   {
      "id": 18,
      "name": "Evelyn"
   },
   {
      "id": 19,
      "name": "Theodore"
   },
   {
      "id": 20,
      "name": "Harper"
   }
]

接下来,创建一个新的状态变量 data,用于将热门名称存储在 NameList 组件中,如下所示 −

const [data, setData] = useState([])

接下来,创建一个新的状态变量 isLoading,用于存储加载状态,如下所示 −

const [isLoading, setLoading] = useState([])

接下来,使用 fetch 方法从 json 文件中获取热门名称,并将其设置为 useEffect Hooks(钩子)内的数据状态变量

useEffect(() => {
   setTimeout(() => {
      fetch("json/names.json")
         .then( (response) => response.json())
         .then( (json) => { console.log(json); setLoading(false); setData(json); } )
   }, 2000)
})

这里我们有,

  • 使用setTimout方法模拟加载过程。

  • 使用fetch方法获取json文件。

  • 使用json方法解析json文件。

  • 使用setData将从json文件解析的名称设置为数据状态变量。

  • 使用setLoading设置加载状态。

接下来,使用map方法渲染名称。在获取期间,显示加载状态。

<div>
   {isLoading && <span>loading...</span>}
   {!isLoading && data && <span>Popular names: </span>}
   {!isLoading && data && data.map((item) =>
      <span key={item.id}>{item.name} </span>
   )}
</div>

这里有,

  • 使用isLoading显示加载状态

  • 使用data变量显示热门名称列表

组件NameList的完整源代码如下 −

import { useState, useEffect } from "react"
function NameList() {
   const [data, setData] = useState([])
   const [isLoading, setLoading] = useState([])
   useEffect(() => {
      setTimeout(() => {
         fetch("json/names.json")
         .then( (response) => response.json())
         .then( (json) => { console.log(json); setLoading(false); setData(json); } )
      }, 2000)
   })
   return (
      <div>
         {isLoading && <span>loading...</span>}
         {!isLoading && data && <span>Popular names: </span>}
         {!isLoading && data && data.map((item) =>
            <span key={item.id}>{item.name} </span>
         )}
      </div>
   )
}
export default NameList

接下来,打开浏览器并检查应用程序。它将显示加载状态,2 秒后,它将获取 json 并显示热门名称,如下所示 −

使用 Effect 获取数据

使用 Effect 获取数据

DOM 突变

useEffect Hooks(钩子)可用于使用 DOM 及其方法来操作文档。它确保其中的代码仅在 DOM 准备就绪后才执行。让我们更改我们的姓名列表应用程序并使用 DOM 突变更新页面的标题。

首先,打开 NameList 组件并根据加载状态添加文档标题,如下所示 −

useEffect(() => {
   if(isLoading)
      document.title = "Loading popular names..."
   else
      document.title = "Popular name list"
   setTimeout(() => {
      fetch("json/names.json")
         .then( (response) => response.json())
         .then( (json) => { console.log(json); setLoading(false); setData(json);} )
   }, 2000)
})

这里我们使用了 DOM 对象 document.title 来更新页面的标题。

最后,打开浏览器,检查文​​档的标题是如何通过 DOM 操作更新的

DOM Mutations

DOM Mutations

清理函数

useEffect 可用于在从页面文档卸载组件期间删除清理函数,例如 clearIntervalremoveEventListener 等。这将防止内存泄漏并提高性能。为此,我们可以创建自己的清理函数并从 useEffect 回调参数中返回它。

让我们将名称列表应用程序更改为使用 setInterval 而不是 setTimeout,然后在卸载组件期间使用 clearInterval 删除设置的回调函数。

首先,打开 NameList 组件并更新 useEffect 部分,如下所示 −

useEffect(() => {
   if(isLoading)
      document.title = "Loading popular names..."
   else
      document.title = "Popular name list"
   let interval = setInterval(() => {
      setLoading(true)
      fetch("json/names.json")
         .then( (response) => response.json())
         .then( (json) => { console.log(json); setLoading(false); setData(json);} )
      }, 5000)
   return () => { clearInterval(interval) }
})

这里我们有,

  • 使用setImterval每5秒更新一次流行名称。

  • 在清理函数中使用clearInterval在卸载组件期间删除setInterval。

最后,打开浏览器并检查应用程序的行为。我们将看到数据每5秒更新一次。卸载组件时,将在后台调用clearInterval

总结

useEffect是函数组件的基本功能,使组件能够使用生命周期事件。它有助于函数组件提供丰富的功能以及可预测和优化的性能。

ReactJS - 使用 useContext

Context 是 React 中的重要概念之一。它提供了将信息从父组件传递到其所有子组件到任何嵌套级别的能力,而无需通过每个级别的 props 传递信息。Context 将使代码更具可读性和易于理解。Context 可用于存储不变或变化极小的信息。context 的一些用例如下 −

  • 应用程序配置

  • 当前经过身份验证的用户信息

  • 当前用户设置

  • 语言设置

  • 按应用程序/用户进行主题/设计配置

React 提供了一个特殊的Hooks(钩子) useContext 来访问和更新函数组件中的上下文信息。让我们在本章中学习上下文及其相应的Hooks(钩子)。

上下文如何工作?

在理解 useContext Hooks(钩子)之前,让我们重新回顾一下上下文的基本概念及其工作原理。上下文有四个部分,

  • 创建新上下文

  • 在根组件中设置上下文提供程序

  • 在需要上下文信息的组件中设置上下文消费者

  • 访问上下文信息并在渲染方法中使用它

让我们创建一个应用程序来更好地理解上下文及其用法。让我们创建一个全局上下文,用于在应用程序根组件中维护主题信息,并在子组件中使用它。

首先,使用以下命令创建并启动应用程序,

create-react-app myapp
cd myapp
npm start

接下来,在 components 文件夹 (src/components/HelloWorld.js) 下创建一个组件 HelloWorld

import React from "react";
import ThemeContext from "../ThemeContext";
class HelloWorld extends React.Component {
   render() {
      return <div>Hello World</div>
   }
}
export default HelloWorld

接下来,创建一个新的上下文(src/ThemeContext.js)用于维护主题信息。

import React from 'react'
const ThemeContext = React.createContext({
   color: 'black',
   backgroundColor: 'white'
})
export default ThemeContext

这里,

  • 使用 React.createContext 创建一个新上下文。

  • 上下文被建模为具有样式信息的对象。

  • 设置文本颜色和背景的初始值。

接下来,通过包含 HelloWorld 组件和主题提供程序以及主题上下文的初始值来更新根组件 App.js

import './App.css';
import HelloWorld from './components/HelloWorld';
import ThemeContext from './ThemeContext'
function App() {
   return (
      <ThemeContext.Provider value={{
         color: 'white',
         backgroundColor: 'green'
      }}>
      <HelloWorld />
      </ThemeContext.Provider>
   );
}
export default App;

这里使用了 ThemeContext.Provider,这是一个非可视组件,用于设置其所有子组件中使用的主题上下文的值。

接下来,在 HelloWorld 组件中包含一个上下文消费者,并使用 HelloWorld 组件中的主题信息来设置 hello world 消息的样式。

import React from "react";
import ThemeContext from "../ThemeContext";
class HelloWorld extends React.Component {
   render() {
      return  (
         <ThemeContext.Consumer>
         {
            ( {color, backgroundColor} ) =>
            (<div style={{
               color: color,
               backgroundColor: backgroundColor }}>
               Hello World
            </div>)
         }
      </ThemeContext.Consumer>)
   }
}
export default HelloWorld

这里我们有,

  • 使用ThemeContext.Consumer,这是一个非可视化组件,提供对当前主题上下文详细信息的访问

  • 使用函数表达式获取ThemeContext.Consumer中的当前上下文信息

  • 使用对象解构语法获取主题信息并设置colorbackgroundColor变量中的值。

  • 使用主题信息通过style属性设置组件样式。

最后,打开浏览器并检查应用程序的输出

ReactJS 使用 UseContext

useContext 的签名

useContext 的签名如下 −

let contextValue = useContext( <contextName> )

这里,

  • contextName 指的是要访问的上下文的名称。

  • contextValue 指的是所引用上下文的当前值。

使用Hooks(钩子)访问上下文的示例代码如下 −

const theme = useContext(ThemContext)

通过hook

让我们更新我们的应用程序并使用上下文Hooks(钩子)代替上下文消费者。

首先,将 HelloWorld 组件转换为函数组件。

import React from "react";
function HelloWorld() {
   return <div>Hello World</div>
}
export default HelloWorld

接下来,通过 useContext hook 访问上下文的当前值

import React, { useContext } from "react"
import ThemeContext from '../ThemeContext'
function HelloWorld() {
   let theme = useContext(ThemeContext)
   return <div>Hello World</div>
}
export default HelloWorld

接下来,更新渲染函数以使用通过上下文获取的主题信息。

import React, { useContext } from "react"
import ThemeContext from '../ThemeContext'
function HelloWorld() {
   let theme = useContext(ThemeContext)
   return (
      <div style={{
         color: theme.color,
         backgroundColor: theme.backgroundColor }}>
            Hello World
      </div>
   )
}
export default HelloWorld

这里我们有,

  • 使用 useContext 访问 ThemeContext 上下文信息。

  • 使用 ThemeContext 信息设置背景颜色和文本颜色。

最后,打开浏览器并检查应用程序的输出。

Context Usage Through Hook

更新上下文

在某些情况下,更新上下文信息是必要的。例如,我们可以提供一个选项来让用户更改主题信息。当用户更改主题时,上下文应该得到更新。更新上下文将重新渲染所有子组件,这将改变应用程序的主题。

React 提供了一个使用 useStateuseContext Hooks(钩子)更新上下文的选项。让我们更新我们的应用程序以支持主题选择。

首先,更新根组件 App.js 并使用 useState Hooks(钩子)来管理主题信息,如下所示 −

import './App.css'
import { useState } from 'react'
import HelloWorld from './components/HelloWorld'
import ThemeContext from './ThemeContext'
function App() {
   let initialTheme = {
      color: 'white',
      backgroundColor: 'green'
   }
   const [theme, setTheme] = useState(initialTheme)
   return (
      <ThemeContext.Provider value={{ theme, setTheme }}>
         <HelloWorld />
      </ThemeContext.Provider>
   );
}
export default App;

这里我们有,

  • 使用 useState Hooks(钩子)在根组件的状态中设置主题信息。

  • 主题更新函数,useState 返回的 useTheme 也作为主题信息的一部分包含在上下文中。

接下来,更新 HelloWorld 组件以获取存储在上下文中的主题信息。

import React, { useContext } from "react"
import ThemeContext from '../ThemeContext'
function HelloWorld() {
   let { theme, setTheme } = useContext(ThemeContext)
   return (<div style={{
            color: theme.color,
            backgroundColor: theme.backgroundColor }}>
         <div>Hello World</div>
      </div>)
}
export default HelloWorld

接下来,通过下拉选项为用户提供更改主题的选项。

import React, { useContext } from "react"
import ThemeContext from '../ThemeContext'
function HelloWorld() {
   let { theme, setTheme } = useContext(ThemeContext)
   return (<div style={{
      color: theme.color,
      backgroundColor: theme.backgroundColor }}>
   <div>
      <select value={theme.backgroundColor}>
         <option value="green">Green</option>
         <option value="red">Red</option>
      </select>
      <div>Hello World</div>
   </div>)
}
export default HelloWorld

这里我们有,

  • 添加了一个下拉框,其中有两个选项,绿色和红色。

  • 使用当前主题值 value={theme.backgroundColor) 设置下拉框的当前值。

接下来,每当用户通过 onChange 事件更改主题时,更新上下文。

import React, { useContext } from "react"
import ThemeContext from '../ThemeContext'
function HelloWorld() {
   let { theme, setTheme } = useContext(ThemeContext)
   return (<div style={{
      color: theme.color,
      backgroundColor: theme.backgroundColor }}>
   <div>
      <select value={theme.backgroundColor}
         onChange = {
            (e) => {
               setTheme({
                  ...theme,
                  backgroundColor: e.target.value
               })
            }} >
         <option value="green">Green</option>
         <option value="red">Red</option>
      </select>
   </div>
      <div>Hello World</div>
   </div>)
}
export default HelloWorld

这里我们有,

  • onChange 事件附加到下拉框。

  • 在事件处理程序中使用 setTheme 函数,并将主题的背景颜色更新为用户选择的颜色。

根组件和 HelloWorld 组件的完整代码如下 &miinus;

import React, { useContext } from "react"
import ThemeContext from '../ThemeContext'
function HelloWorld() {
   let { theme, setTheme } = useContext(ThemeContext)
   return (<div style={{
      color: theme.color,
      backgroundColor: theme.backgroundColor }}>
   <div>
      <select value={theme.backgroundColor}
         onChange= {
            (e) => {
               setTheme({
                  ...theme,
                  backgroundColor: e.target.value
               })
            }
         } >
         <option value="green">Green</option>
         <option value="red">Red</option>
      </select>
   </div>
      <div>Hello World</div>
   </div>)
}
export default HelloWorld

接下来,打开浏览器并检查应用程序。

更新上下文

当用户选择不同的背景颜色时,它将更新上下文,因此,它将以新主题重新渲染组件,如下所示 −

更新上下文

摘要

上下文降低了在 React 应用程序中维护全局数据的复杂性。上下文Hooks(钩子)通过简化访问和更新(通过 useState)上下文进一步降低了复杂性。

ReactJS - useRef

React 会在组件状态发生变化时自动发出 HTML 元素。这大大简化了 UI 开发,因为只需更新组件的状态即可。传统上,直接访问 DOM 元素来更新组件的 UI 是很正常的。

有时我们可能需要回退到直接访问 DOM 元素并更新组件的 UI。React ref 在此场景中提供帮助。它提供对 DOM 元素的直接访问。此外,它确保组件能够与 React Virtual DOM 和 HTML DOM 顺利配合使用。

React 提供了一个函数 createRef 来在基于类的组件中创建 ref。函数组件中 createRef 的对应项是 useRef Hooks(钩子)。让我们在本章中学习如何使用 useRef。

useRef Hooks(钩子)的签名

useRef 的目的是返回一个可变对象,该对象将在重新渲染之间持续存在。 useRef 的签名如下 −

<refObj> = useRef(<val>)

此处,

  • val 是要为返回的可变对象 refObj 设置的初始值。

  • refObj 是Hooks(钩子)返回的对象。

要自动将 DOM 对象附加到 refObj,应在元素的 ref props 中设置它,如下所示 −

<input ref={refObj} />

要访问附加的 DOM 元素,请使用 refObj 的 current 属性,如下所示 −

const refElement = refObj.current

应用 ref hook

让我们在本章中通过创建一个 React 应用程序来学习如何应用 useRef

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,在组件文件夹 (src/components/RefInput.js) 下创建一个 React 组件 RefInput

function RefInput() {
   return <div>Hello World</div>
}
export default RefInput

接下来,更新根组件(App.js)以使用我们的新组件。

import RefInput from "./components/RefInput";
function App() {
   return (
      <div style={{ padding: "5px"}}>
         <RefInput />
      </div>
   );
}
export default App;

接下来,向 RefInput 组件添加计数器功能,如下所示 −

import {useState} from 'react'
function RefInput() {
   const [count, setCount] = useState(0)
   const handleClick = () => setCount(count + 1)
   return (
      <div>
         <div>Counter: {count} <button onClick={handleClick}>+</button></div>
      </div>
   )
}
export default RefInput

这里我们有,

  • 使用 useState Hooks(钩子)来处理计数器状态变量(count)。

  • 在 JSX 中呈现计数器状态变量。

  • 添加一个按钮并附加一个点击处理程序事件(handleClick),它将使用 setCount 方法增加计数器。

接下来,添加一个输入字段并根据用户在输入字段中输入的值显示问候消息,如下所示 −

import {useState, useRef} from 'react'
function RefInput() {
   const [count, setCount] = useState(0)
   const inputRef = useRef(null)
   const labelRef = useRef(null)
   console.log("RefInput is (re)rendered")
   const handleClick = () => setCount(count + 1)
   const handleChange = () => labelRef.current.innerText = inputRef.current.
   value == "" ? "World" : inputRef.current.value
   return (
      <div>
         <div>Counter: {count} <button onClick={handleClick}>+</button></div>
            <div style={{ paddingTop: "5px"}}>
               <label>Enter your name: </label><input type="text" name="username"
                  ref={inputRef} onChange={handleChange}/>
               <br />
            <div>Hello, <span ref={labelRef}></span></div>
         </div>
      </div>
   )
}
export default RefInput

这里我们有 −

  • 创建了一个 ref,inputRef 来表示输入元素,并通过 ref props 将其附加到相关元素。

  • 创建了另一个 ref,labelRef 来表示问候消息元素,并通过 ref props 将其附加到相关元素。

  • 将事件处理程序 handleChange 附加到输入元素。事件处理程序使用 inputRef ref 获取问候消息,并使用 labelRef ref 更新消息。

接下来,在浏览器中打开应用程序并输入您的姓名。应用程序将更新问候消息,如下所示。

Applying Ref Hook

检查您的控制台,您会注意到组件未重新渲染。由于 react 仅在状态更改时重新渲染,而 ref 不会进行任何状态更改,因此组件不会重新渲染。

接下来,单击 + 按钮。当状态(计数)发生变化时,它将通过重新渲染组件来更新计数器。如果仔细观察,您会发现消息保持不变。这种行为的原因是 ref 值在 react 渲染之间保留。

useRef 的用例

useRef 的一些用例如下 −

访问 JavaScript DOM API − JavaScript DOM API 提供了丰富的功能集来操作应用程序的 UI。当应用程序功能需要访问 JavaScript DOM API 时,可以使用 useRef 来检索原始 DOM 对象。检索到原始 DOM 对象后,应用程序可以使用 DOM API 访问所有功能。 DOM API 的一些示例如下 −

  • 聚焦输入元素

  • 选择文本

  • 使用媒体播放 API 播放音频或视频

命令式动画 − Web 动画 API 通过命令式编程而非声明式编程提供了一组丰富的动画功能。要使用 Web 动画 API,我们需要访问原始 DOM。

与第三方库集成 − 由于第三方库需要访问原始 DOM 才能执行其功能,因此必须使用 useRef 从 react 获取 DOM 引用并将其提供给第三方库。

总结

尽管 React 开发 UI 的方式简单易行,但基于 DOM API 开发 UI 在某些情况下有其自身的优势。 useRef hook 非常适合这些场景,并提供了简单干净的 API 来直接访问 DOM 元素并随后访问其 API。

ReactJS - 使用 useReducer

useReducer Hooks(钩子)是 useState Hooks(钩子)的高级版本。众所周知,useState 的目的是管理状态变量。useState 返回一个函数,该函数接受一个值并使用给定的值更新状态变量。

// counter = 0
const [counter, setCounter] = useState(0)

// counter = 1
setCounter(1)

// counter = 2
setCounter(2)

useReducer Hooks(钩子)接受一个 Reducer 函数以及初始值并返回一个调度器函数。Reducer 函数将接受初始状态和一个操作(特定场景),然后提供逻辑以根据操作更新状态。调度器函数接受操作(和相应的详细信息)并使用提供的操作调用 Reducer 函数。

例如,useReducer 可用于根据增量和减量操作更新计数器状态。增加操作将使计数器状态增加 1,减少操作将使计数器状态减少 1。

本章让我们学习如何使用 useReducer Hooks(钩子)。

useReducer Hooks(钩子)的签名

useReducer Hooks(钩子)的签名如下 −

const [<state>, <dispatch function>] = useReducer(<reducer function>, <initialargument>, <init function>);

这里,

  • state表示状态中需要维护的信息

  • reducer函数是一个javascript函数,用于根据action更新状态。

以下是reducer函数−的语法

(<state>, <action>) => <updated state>

其中,

  • state − 当前状态信息

  • action −要执行的操作(应具有执行操作的有效负载)

  • 更新状态 − 更新状态

  • 初始参数 −表示状态的初始值

  • init 函数 −表示初始化函数,可用于设置初始值/重置状态的当前值。如果需要计算初始值,则可以使用 init 函数。否则,可以跳过该参数。

应用 Reducer Hooks(钩子)

让我们创建一个 React 应用程序来管理待办事项集合。首先,我们将使用 useState 实现它,然后将其转换为使用 useReducer。通过使用两个Hooks(钩子)实现应用程序,我们将了解 useReducer 相对于 useState 的优势。此外,我们可以根据情况明智地选择Hooks(钩子)。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,在组件文件夹 (src/components/TodoList.js) 下创建一个 React 组件 TodoList。

function TodoList() {
    return <div>Todo List</div>
}
export default TodoList

接下来,更新根组件 App.js 以使用新创建的 TodoList 组件。

import logo from './logo.svg';
import './App.css';
import TodoList from './components/TodoList';
function App() {
   return (
      <div style={{ padding: "5px"}}>
         <TodoList />
      </div>
   );
}
export default App;

接下来,创建一个变量 todoData 来管理待办事项列表,并使用 useState Hooks(钩子)将其设置为状态。

const [todoData, setTodoData] = useState({
   action: '',
   items: [],
   newItem: null,
   id: 0
})

此处,

  • action 用于表示当前操作,adddelete 将应用于当前待办事项列表 (items)。

  • items 是用于保存当前待办事项列表的数组。

  • newItem 是用于表示当前待办事项的对象。该对象将有两个字段,idtodo

  • id 是删除操作期间要删除的当前项目的 id。

  • useState 用于获取和设置待办事项列表 (todoData)。

接下来,渲染当前待办事项列表 (todoData.items) 以及删除按钮和输入文本字段以输入新的待办事项。

<div>
   <p>List of Todo list</p>
   <ul>
      {todoData.items && todoData.items.map((item) =>
         <li key={item.id}>{item.todo} <span><button onClick={(e) =>
         handleDeleteButton(item.id, e)}>Delete</button></span></li>
      )}
      <li><input type="text" name="todo" onChange={handleInput} />
      <button onClick={handleAddButton}>Add</button></li>
   </ul>
</div>

此处,

  • 从状态变量 todoData 呈现待办事项的当前列表。

  • 呈现一个输入字段,供用户输入新的待办事项,并附加 onChange 事件处理程序 handleInput。事件处理程序将使用用户在输入字段中输入的数据更新待办事项状态 (todoData) 中的 newItem

  • 呈现一个按钮,供用户将新输入的待办事项添加到当前待办事项列表,并附加 onClick 事件处理程序 handleAddButton。事件处理程序将当前/新的待办事项添加到待办事项状态。

  • 为待办事项列表中的每个项目呈现一个按钮,并附加 onClick 事件处理程序 handleDeleteButton。事件处理程序将从待办事项状态中删除相应的待办事项。

接下来,实现 handleInput 事件处理程序,如下所示 −

const handleInput = (e) => {
   var id = 0
   if(todoData.newItem == null) {
      for(let i = 0; i < todoData.items.length; i++) {
         if(id < todoData.items[i].id) {
            id = todoData.items[i].id
         }
      }
      id += 1
   } else {
      id = todoData.newItem.id
   }
   let data = {
      actions: '',
      items: todoData.items,
      newItem: {
         id: id,
         todo: e.target.value
      },
      id: 0
   }
   setTodoData(data)
}

这里我们有,

  • 使用用户输入的数据 (e.target.value) 更新 newItem.todo

  • 创建并设置新项目的 id。

接下来,实现 handleDeleteButton 事件处理程序,如下所示 −

const handleDeleteButton = (deleteId, e) => {
   let data = {
      action: 'delete',
      items: todoData.items,
      newItem: todoData.newItem,
      id: deleteId
   }
   setTodoData(data)
}

此处,处理程序设置要删除的待办事项的 id(deleteid)以及待办事项状态中的delete操作

接下来,实现handleAddButton事件处理程序,如下所示 −

const handleAddButton = () => {
   let data = {
      action: 'add',
      items: todoData.items,
      newItem: todoData.newItem,
      id: 0
   }
   setTodoData(data)
}

此处,处理程序在状态中设置新项目和添加操作

接下来,实现add操作,如下所示 −

if(todoData.action == 'add') {
   if(todoData.newItem != null) {
      let data = {
         action: '',
         items: [...todoData.items, todoData.newItem],
         newItem: null,
         id: 0
      }
      setTodoData(data)
   }
}

此处,新项目被添加到待办事项状态的现有列表 (todoData.items)。

接下来,实现 delete 操作,如下所示 −

if(todoData.action == 'delete' && todoData.id != 0) {
   var newItemList = []
   for(let i = 0; i < todoData.items.length; i++) {
      if(todoData.items[i].id != todoData.id) {
         newItemList.push(todoData.items[i])
      }
   }
   let data = {
      action: '',
      items: newItemList,
      newItem: null,
      id: 0
   }
   setTodoData(data)
}

此处从待办事项列表(todoData.items)中删除指定id的项目。

该组件的完整源代码如下 −

import { useState } from "react"
function TodoList() {
   const [todoData, setTodoData] = useState({
      action: '',
      items: [],
      newItem: null,
      id: 0
   })
   if(todoData.action == 'add') {
      if(todoData.newItem != null) {
         let data = {
            action: '',
            items: [...todoData.items, todoData.newItem],
            newItem: null,
            id: 0
         }
         setTodoData(data)
      }
   }
   if(todoData.action == 'delete' && todoData.id != 0) {
      var newItemList = []
      for(let i = 0; i < todoData.items.length; i++) {
         if(todoData.items[i].id != todoData.id) {
            newItemList.push(todoData.items[i])
         }
      }
      let data = {
         action: '',
         items: newItemList,
         newItem: null,
         id: 0
      }
      setTodoData(data)
   }
   const handleInput = (e) => {
      var id = 0
      if(todoData.newItem == null) {
         for(let i = 0; i < todoData.items.length; i++) {
            if(id < todoData.items[i].id) {
               id = todoData.items[i].id
            }
         }
         id += 1
      } else {
         id = todoData.newItem.id
      }
      let data = {
         action: '',
         items: todoData.items,
         newItem: {
            id: id,
            todo: e.target.value
         },
         id: 0
      }
      setTodoData(data)
   }
   const handleDeleteButton = (deleteId, e) => {
      let data = {
         action: 'delete',
         items: todoData.items,
         newItem: todoData.newItem,
         id: deleteId
      }
      setTodoData(data)
   }
   const handleAddButton = () => {
      let data = {
         action: 'add',
         items: todoData.items,
         newItem: todoData.newItem,
         id: 0
      }
      setTodoData(data)
   }
   return (
      <div>
         <p>List of Todo list</p>
         <ul>
            {todoData.items && todoData.items.map((item) =>
            <li key={item.id}>{item.todo} <span><button onClick={(e) => handleDeleteButton(item.id, e)}>Delete</button></span></li>
            )}
            <li><input type="text" name="todo" onChange={handleInput} /><button onClick={handleAddButton}>Add</button></li>
         </ul>
      </div>
   )
}
export default TodoList

此处,应用程序使用 useState Hooks(钩子)来实现该功能。

接下来,打开浏览器并添加/删除待办事项。应用程序将按如下所示运行 −

Applying Reducer Hook

使用 useReducer

让我们使用 useReducer

重新实现该功能

首先,在组件文件夹 (src/components/TodoReducerList.js) 下创建一个 React 组件 TodoReducerList

function TodoReducerList() {
    return <div>Todo List</div>
}
export default TodoReducerList

接下来,更新根组件 App.js 以使用新创建的 TodoReducerList 组件。

import './App.css';
import TodoReducerList from './components/TodoReducerList';
function App() {
   return (
      <div style={{ padding: "5px"}}>
         <TodoReducerList />
      </div>
   );
}
export default App;

接下来,实现一个 Reducer 函数 todoReducer,它将接收两个参数,如下所示 −

  • 当前待办事项列表 (items)

  • 操作 (action.type) 以及操作相关信息 (action.payload)。对于添加操作 (action.type),有效负载 (action.payload) 将包含新的待办事项,对于删除操作 (action.type),它将包含要删除的待办事项的 ID

Reducer 将向待办事项列表应用相关操作并发回修改后的待办事项列表,如下所示 −

function todoReducer(items, action) {
   // action = { type: 'add / delete', payload: 'new todo item'}
   let newTodoList = []
   
   switch (action.type) {
      case 'add':
         var id = 0
         for(let i = 0; i < items.length; i++) {
            if(id < items[i].id) {
               id = items[i].id
            }
         }
         action.payload.id = id + 1
         newTodoList = [...items, action.payload]
      break;
      case 'delete':
         for(let i = 0; i < items.length; i++) {
            if(items[i].id != action.payload.id) {
               newTodoList.push(items[i])
            }
         }
      break;
      default:
         throw new Error()
   }
   return newTodoList
}

这里我们有,

  • 使用 switch 语句处理两个操作。

  • Added 操作将有效负载添加到现有的待办事项列表中。

  • deleted 操作从现有的待办事项列表中删除有效负载中指定的项目

  • 最后,该函数返回更新的待办事项列表

接下来,在 TodoReducerList 组件中使用新创建的 Reducer,如下所示 −

const [items, dispatch] = useReducer(todoReducer, [])

这里,useReducer 接收两个参数,a) Reducer 函数和 b)当前待办事项列表并返回当前待办事项列表和调度函数dispatch。调度函数(dispatch)应使用添加或删除操作以及相关有效负载信息进行调用。

接下来,为输入文本字段(新待办事项)和两个按钮(添加和删除)创建处理程序函数,如下所示 −

const [todo, setTodo] = useState('')
const handleInput = (e) => {
   setTodo(e.target.value)
}
const handleAddButton = () => {
   dispatch({
      type: 'add',
      payload: {
         todo: todo
      }
   })
   setTodo('')
}
const handleDeleteButton = (id) => {
   dispatch({
      type: 'delete',
      payload: {
      id: id
      }
   })
}

这里我们有,

  • 使用 useState 来管理用户输入(待办事项)

  • 使用 dispatch 方法在相关处理程序中处理 adddelete 操作

  • 在处理程序中传递特定于操作的有效负载。

接下来,更新渲染方法,如下所示 −

<div>
   <p>List of Todo list</p>
   <ul>
      {items && items.map((item) =>
         <li key={item.id}>{item.todo} <span><button onClick={(e) =>
         handleDeleteButton(item.id)}>Delete</button></span></li>
       )}
      <li><input type="text" name="todo" value={todo} onChange={handleInput} />
      <button onClick={handleAddButton}>Add</button></li>
   </ul>
</div>

该组件的完整源代码以及reducer函数如下−

import {
   useReducer,
   useState
} from "react"
function todoReducer(items, action) {
   
   // action = { type: 'add / delete', payload: 'new todo item'}
   let newTodoList = []
   switch (action.type) {
      case 'add':
         var id = 0
         for(let i = 0; i < items.length; i++) {
            if(id < items[i].id) {
               id = items[i].id
            }
         }
         action.payload.id = id + 1
         newTodoList = [...items, action.payload]
      break;
      case 'delete':
         for(let i = 0; i < items.length; i++) {
            if(items[i].id != action.payload.id) {
               newTodoList.push(items[i])
         }
      }
      break;
      default:
         throw new Error()
   }
   return newTodoList
}
function TodoReducerList() {
   const [todo, setTodo] = useState('')
   const [items, dispatch] = useReducer(todoReducer, [])
   const handleInput = (e) => {
      setTodo(e.target.value)
   }
   const handleAddButton = () => {
      dispatch({
         type: 'add',
         payload: {
            todo: todo
         }
      })
      setTodo('')
   }
   const handleDeleteButton = (id) => {
      dispatch({
         type: 'delete',
         payload: {
            id: id
         }
      })
   }
   return (
      <div>
         <p>List of Todo list</p>
         <ul>
            {items && items.map((item) =>
            <li key={item.id}>{item.todo} <span><button onClick={(e) => 
               handleDeleteButton(item.id)}>Delete</button></span></li>
            )}
            <li><input type="text" name="todo" value={todo} onChange={handleInput} />
               <button onClick={handleAddButton}>Add</button></li>
         </ul>
      </div>
   )
}
export default TodoReducerList

接下来,打开浏览器并检查输出。

使用 UseReducer

我们可以清楚地看到,与纯 useState 实现相比,useReducer 实现简单、易懂且易于理解。

总结

useReducer hook 在状态管理中引入了 Reducer 模式。它鼓励代码重用并提高组件的可读性和可理解性。总的来说,useReducer 是 React 开发人员工具包中必不可少且功能强大的工具。

ReactJS - useCallback

useCallback Hooks(钩子)类似于 useMemo Hooks(钩子),提供记忆函数而不是值的功能。由于回调函数是 JavaScript 编程不可或缺的一部分,并且回调函数是通过引用传递的,因此 React 提供了一个单独的Hooks(钩子) useCallback 来记忆回调函数。理论上,useCallback 功能可以使用 useMemo Hooks(钩子)本身来实现。但是,useCallback 提高了 React 代码的可读性。

useCallback Hooks(钩子)的签名

useCallback Hooks(钩子)的签名如下 −

const <memoized_callback_fn> = useCallback(<callback_fn>, <dependency_array>);

此处,useCallback 接受两个输入并返回一个记忆化回调函数。输入参数如下 −

  • callback_fn − 要记忆的回调函数。

  • dependency_array − 保存回调函数所依赖的变量。

useCallback Hooks(钩子)的输出是 callback_fn 的记忆化回调函数。useCallback Hooks(钩子)的用法如下 −

const memoizedCallbackFn = useCallback(() => {
   // code
}, [a, b])

注意 − useCallback(callback_fn, dependency_array) 相当于 useMemo(() => callback_fn, dependency_array)。

应用 useCallback

让我们通过创建一个 React 应用程序来学习如何应用 useCallback Hooks(钩子)。

首先,使用 create-react-app 命令创建并启动一个 React 应用程序,如下所示 −

create-react-app myapp
cd myapp
npm start

接下来,在 components 文件夹下创建一个组件 PureListComponent (src/components/PureListComponent.js)

import React from "react";
function PureListComponent() {
   return <div>List</div>
}
export default PureListComponent

接下来,使用 PureListComponent 组件更新根组件,如下所示−

import PureListComponent from './components/PureListComponent';
function App() {
   return (
      <div style={{ padding: "5px" }}>
         <PureListComponent />
      </div>
   );
}
export default App;

接下来,打开 PureListComponent.js 并添加两个 props

  • 组件中要显示的项目列表

  • 回调函数,用于在控制台中显示用户单击的项目

import React from "react";
function PureListComponent(props) {
   const items = props['items'];
   const handleClick = props['handleClick']
   console.log("I am inside the PureListComponent")
   return (
      <div>
         {items.map((item, idx) =>
            <span key={idx} onClick={handleClick}>{item} </span>
         )}
      </div>
   )
}
export default React.memo(PureListComponent)

这里我们有,

  • 使用 items props 获取项目列表

  • 使用 handleClick 获取点击事件的处理程序

  • 使用 React.memo 包装组件以记忆组件。由于组件将为给定的一组输入呈现相同的输出,因此它在 react 中被称为 PureComponent。

接下来,让我们更新 App.js 并使用 PureListComponent,如下所示 −

import React, {useState, useEffect} from 'react';
import PureListComponent from './components/PureListComponent';
function App() {
   
   // 数字数组
   var listOfNumbers = [...Array(100).keys()];
   
   // 回调函数
   const handleCallbackFn = (e) => { console.log(e.target.innerText) }
   const [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => {
         setCurrentTime(new Date())
      }, 1000)
      return () => clearInterval(interval)
   }, [currentTime])
   return (
      <div style={ { padding: "5px" } }>
         <PureListComponent items={listOfNumbers} handleClick={handleCallbackFn}/>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   );
}
export default App;

这里我们包含了一个状态 currentTime,并使用 setInterval 每秒更新一次,以确保组件每秒重新渲染一次。

我们可能认为 PureListComponent 不会每秒重新渲染一次,因为它使用了 React.memo。但是,它会重新渲染,因为 props 值是引用类型。

接下来,更新根组件并使用 useMemouseCallback 来保存数组和回调函数,如下所示 −

import React, {useState, useEffect, useCallback, useMemo} from 'react';
import PureListComponent from './components/PureListComponent';
function App() {
   
   // 数字数组
   const listOfNumbers = useMemo(() => [...Array(100).keys()], []);
   
   // 回调函数
   const handleCallbackFn = useCallback((e) => console.log(e.target.innerText), [])
   const [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => {
         setCurrentTime(new Date())
      }, 1000)
      return () => clearInterval(interval)
   }, [currentTime])
   return (
      <div style={ { padding: "5px" } }>
         <PureListComponent items={listOfNumbers} handleClick={handleCallbackFn}/>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   );
}
export default App;

这里我们有,

  • 使用 useMemo 保存 items 数组

  • 使用 useCallback 保存 handleClick 回调函数

最后,在浏览器中检查应用程序,它不会每秒重新渲染 PureListComponent。

UseCallback Hook 的签名

useCallback 的用例

useCallback Hooks(钩子)的一些用例如下 −

  • 带有函数的纯函数组件props

  • useEffect 或其他具有函数依赖项的Hooks(钩子)

  • 在防抖动/节流或类似操作期间使用函数

优点

useCallback Hooks(钩子)的优点如下 −

  • 易于使用的 API

  • 易于理解

  • 提高应用程序的性能

缺点

从技术上讲,useCallback 没有缺点。但是,在应用程序中大量使用 useCallback 会带来以下缺点。

  • 降低应用程序的可读性

  • 降低应用程序的可理解性

  • 应用程序调试很复杂

  • 开发人员应该对 JavaScript 语言有深入的了解才能使用它

总结

useCallback 易于使用并可提高性能。同时,useCallback 应仅在绝对必要时使用。它不应该在每个场景中都使用。一般规则是检查应用程序的性能。如果需要改进,那么我们可以检查 useCallback 的使用是否有助于提高性能。如果我们得到积极的回应,那么我们可以使用它。否则,我们可以把改进和优化应用程序性能的工作交给 React。

Hooks:useMemo

React 提供了一组核心内置Hooks(钩子)来简化开发并构建高效的应用程序。应用程序的重要特征之一是良好的性能。每个应用程序都会有一些复杂的计算逻辑,这会降低应用程序的性能。我们有两种选择来应对复杂的计算。

  • 改进复杂计算的算法并使其快速运行。

  • 仅在绝对必要时调用复杂计算。

useMemoHooks(钩子)通过提供仅在绝对必要时调用复杂计算的选项来帮助我们提高应用程序的性能。useMemo 保留/记忆复杂计算的输出并将其返回,而不是重新计算复杂计算。 useMemo 将知道何时使用记忆值以及何时重新运行复杂的计算。

useMemo Hooks(钩子)的签名

useMemo Hooks(钩子)的签名如下 −

const <output of the expensive logic> = useMemo(<expensive_fn>, <dependency_array>);

这里,useMemo 接受两个输入并返回一个计算值。

输入参数如下 −

  • expensive_fn −执行昂贵的逻辑并返回要记忆的输出。

  • dependency_array − 保存昂贵的逻辑所依赖的变量。如果数组的值发生变化,则必须重新运行昂贵的逻辑并更新值。

useMemo Hooks(钩子)的输出是 expensive_fn 的输出。useMemo 将通过执行函数或获取记忆值来返回输出。

useMemo Hooks(钩子)的用法如下 −

const limit = 1000;
const val = useMemo(() => {
   var sum = 0;
   for(let i = 1; i <= limit; i++) {
      sum += i;
   }
   return sum;
}, [limit])

此处,求和逻辑将在开始时以及限制值更改/更新时执行一次。在此期间,useMemo Hooks(钩子)返回记忆值。

应用 memo Hooks(钩子)

让我们通过在 React 应用程序中应用Hooks(钩子)来深入学习 useMemo Hooks(钩子)。

首先,使用 create-react-app 命令创建并启动 React 应用程序,如下所示 −

create-react-app myapp
cd myapp
npm start

接下来,在 components 文件夹 (src/components/Sum.js) 下创建一个组件 Sum

import React from "react";
function Sum() {
   return <div>Sum</div>
}
export default Sum

接下来,使用 Sum 组件更新根组件,如下所示 −

import './App.css';
import Sum from './components/Sum';
function App() {
   return (
      <div>
         <Sum />
      </div>
   );
}
export default App;

接下来打开 Sum.js,添加一个状态来表示当前时间,并通过 setInterval 更新当前时间,如下所示 −

import React, {
   useState,
   useEffect,
   useMemo
} from "react";
function Sum() {
   let [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => setCurrentTime(new Date()), 1000)
      return () => { clearInterval(interval) }
   })
   const limit = 1000;
   var sum = 0;
   for(let i = 1; i <= limit; i++) {
      sum += i;
   }
   return (
      <div style={ {padding: "5px" } }>
         <div>Summation of values from <i>1</i> to <i>{limit}</i>:
            <b>{sum}</b></div>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   )
}
export default Sum

这里我们有,

  • 使用 setState 通过 useState Hooks(钩子)设置状态中的当前时间。它返回两个变量;一个状态变量,currentTime 和一个函数 setCurrentTime 来更新状态变量 currentTime。

  • 使用 useEffect Hooks(钩子)通过 setInterval 函数以 1 秒为间隔定期更新时间。我们定期更新时间,以确保组件定期重新渲染。

  • 使用 clearInterval 在卸载组件时删除更新时间功能。

  • 通过 JSX 在文档中显示 sumcurrentTime

  • Sum 组件每秒重新渲染一次以更新 currentTime。在每次渲染期间,即使没有必要,也会执行求和逻辑。

接下来,让我们引入 useMemo Hooks(钩子),以防止在重新渲染之间重新计算求和逻辑,如下所示 −

import React, { useState, useEffect, useMemo } from "react";
function Sum() {
   let [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => setCurrentTime(new Date()), 1000)
      return () => { clearInterval(interval) }
   })
   const limit = 1000;
   const sum = useMemo(() => {
      var sum = 0;
      for(let i = 1; i <= limit; i++) {
         sum += i;
      }
      return sum;
   }, [limit])
   return (
      <div style={ {padding: "5px" } }>
         <div>Summation of values from <i>1</i> to <i>{limit}</i>: <b>{sum}</b></div>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   )
}
export default Sum

在这里,我们将求和逻辑转换为函数并将其传递给 useMemo Hooks(钩子)。这将防止每次重新渲染时重新计算求和逻辑。

limit 设置为依赖项。这将确保在 limit 值更改之前不会执行求和逻辑。

接下来,添加一个输入来更改限制,如下所示 −

import React, {
   useState,
   useEffect,
   useMemo
} from "react";
function Sum() {
   let [currentTime, setCurrentTime] = useState(new Date())
   let [limit, setLimit] = useState(10);
   useEffect(() => {
      let interval = setInterval(() => setCurrentTime(new Date()), 1000)
      return () => { clearInterval(interval) }
   })
   const sum = useMemo(() => {
   var sum = 0;
   for(let i = 1; i <= limit; i++) {
      sum += i;
   }
      return sum;
   }, [limit])
   return (<div style={ {padding: "5px" } }>
   <div><input value={limit} onChange={ (e) => { setLimit(e.target.value) }} /></div>
      <div>Summation of values from <i>1</i> to <i>{limit}</i>: <b>{sum}</b></div>
      <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
   </div>)
}
export default Sum

这里我们有,

  • 使用 useState Hooks(钩子)来管理限制变量。useState Hooks(钩子)将返回两个变量;一个状态变量 limit 和一个函数变量 setLimit,用于更新 limit 变量。

  • 添加了一个新的输入元素,允许用户为 limit 变量输入新值

  • 添加了一个事件 onChange,用于在用户更新 limit 变量时对其进行更新。

当用户在输入元素中输入新值时,

  • onChange 事件将被触发。

  • setLimit 函数将由 onChange 事件调用。

  • limit 变量将由 setLimit 更新函数。

  • Sum 组件将重新渲染为状态变量,limit 已更新。

  • useMemo 将在限制变量更新时重新运行逻辑,它将记住新值并将其设置为 sum 变量。

  • render 函数将使用 sum 变量的新值并渲染它。

最后,打开浏览器并检查应用程序。

Memo Hook

保留引用

让我们在本章中了解如何使用 useMemo Hooks(钩子)以及引用数据类型。一般来说,变量的数据类型分为类别、值类型和引用类型。值类型在堆栈中管理,并以值的形式在函数之间传递。但是,引用数据类型在堆中管理,并使用其内存地址或简单的引用在函数之间传递。

值类型如下 −

  • Number

  • String

  • Boolean

  • null

  • undefined

引用数据类型如下 −

  • 数组

  • 对象

  • 函数

一般来说,即使两个变量的值相同,引用数据类型的变量也不容易比较。例如,让我们考虑两个具有值类型的变量和另外两个具有引用数据类型的变量,并进行比较,如下所示 −

var a = 10
var b = 10
a == b // true
var x = [10, 20]
var y = [10, 20]
x == y // false

即使 x 和 y 的值相同,但它们都指向内存中的不同位置。 useMemo 可用于正确记忆引用数据类型的变量。

useMemo(x) == useMemo(y) // true

让我们创建一个新的 React 应用程序,以更好地了解 useMemo 处理引用数据类型的用法。

首先,使用 create-react-app 命令创建并启动 React 应用程序,如下所示 −

create-react-app myapp
cd myapp
npm start

接下来,在 components 文件夹 (src/components/PureSumComponent.js) 下创建一个组件 PureSumComponent

import React from "react";
function PureSumComponent() {
   return <div>Sum</div>
}
export default PureSumComponent

接下来,使用 PureSumComponent 组件更新根组件,如下所示 −

import './App.css';
import PureSumComponent from './components/PureSumComponent';
function App() {
   return (
      <div style={{ padding: "5px" }}>
         <PureSumComponent />
      </div>
   );
}
export default App;

接下来,打开 PureSumComponent.js 并添加一个 props, range 来表示数字的范围,如下所示 −

import React from "react";
function PureSumComponent(props) {
   const range = props['range'];
   const start = range[0];
   const end = range[1];
   console.log('I am inside PureSumComponent component')
   var sum = 0;
   for(let i = start; i <= end; i++) {
      sum += i;
   }
   return (
      <div>
         <div>Summation of values from <i>{start}</i> to
            <i>{end}</i>: <b>{sum}</b></div>
      </div>
   )
}
export default React.memo(PureSumComponent)

这里,

  • 使用 range props 来计算值范围的总和。range 是一个包含两个数字的数组,即范围的开始和结束。

  • 通过 JSX 在文档中显示 startendsum

  • 使用 React.memo 包装组件以记忆组件。由于组件将为给定的一组输入呈现相同的输出,因此在 react 中它被称为 PureComponent

接下来,让我们更新 App.js 并使用 PureSumComponent,如下所示 −

import React, {useState, useEffect} from 'react';
import PureSumComponent from './components/PureSumComponent';
function App() {
   var input = [1,1000]
   const [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => {
         setCurrentTime(new Date())
      }, 1000)
      return () => clearInterval(interval)
   }, [currentTime])
   return (
      <div style={ {padding: "5px" } }>
         <PureSumComponent range={input}/>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   );
}
export default App;

在这里,我们包含了一个状态 currentTime,并使用 setInterval 每秒更新一次,以确保组件每秒重新渲染一次。

我们可能认为 PureSumComponent 不会每秒重新渲染,因为它使用了 React.memo。但是,它会重新渲染,因为 props 是一个数组,并且每秒都会使用新的引用创建它。您可以通过检查控制台来确认这一点。

接下来,更新根组件并使用 useMemo 保存范围数组,如下所示 −

import React, {useState, useEffect, useMemo} from 'react';
import PureSumComponent from './components/PureSumComponent';
function App() {
   const input = useMemo(() => [1, 1000], [])
   const [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => {
         setCurrentTime(new Date())
      }, 1000)
      return () => clearInterval(interval)
   }, [currentTime])
   return (
      <div style={ {padding: "5px" } }>
         <PureSumComponent range={input}/>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   );
}
export default App;

这里我们使用了 useMemo 来保存范围数组

最后,在浏览器中检查应用程序,它不会每秒重新渲染 PureSumComponent

优点

useMemo Hooks(钩子)的优点如下 −

  • 简单易用的 API

  • 易于理解

  • 提高应用程序的性能

缺点

从技术上讲,useMemo 没有缺点。但是,在应用程序中大量使用 useMemo 会带来以下缺点。

  • 降低应用程序的可读性

  • 降低应用程序的可理解性

  • 应用程序调试很复杂

  • 开发人员应该对 JavaScript 语言有深入的了解才能使用它

总结

一般来说,React 会在内部优化应用程序并提供高性能。不过,在某些情况下,我们可能需要干预并提供自己的优化。useMemo 可以帮助我们解决这种情况,并提供一种简单的解决方案来提高 React 应用程序的性能。总而言之,在绝对必要时使用 useMemo Hooks(钩子)。否则,让 React 自己来优化和提供高性能。

ReactJS - 自定义Hooks(钩子)

Hooks(钩子)是函数组件不可或缺的一部分。它们可用于增强函数组件的功能。React 提供了一些内置Hooks(钩子)。尽管内置Hooks(钩子)功能强大且可以执行任何功能,但专用Hooks(钩子)是必需的,而且需求量很大。

React 了解开发人员的这种需求,并允许通过现有Hooks(钩子)创建新的自定义Hooks(钩子)。开发人员可以从函数组件中提取特殊功能,并将其创建为单独的Hooks(钩子),可用于任何函数组件。

让我们在本章中学习如何创建自定义Hooks(钩子)。

创建自定义Hooks(钩子)

让我们创建一个具有无限滚动功能的新 React 函数组件,然后从函数组件中提取无限功能并创建自定义Hooks(钩子)。创建自定义Hooks(钩子)后,我们将尝试更改原始函数组件以使用我们的自定义Hooks(钩子)。

实现无限滚动功能

组件的基本功能是通过生成待办事项的虚拟列表来显示它。当用户滚动时,组件将生成一组新的虚拟待办事项列表并将其附加到现有列表中。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,在组件文件夹 (src/components/TodoList.js) 下创建一个 React 组件 TodoList

function TodoList() {
   return <div>Todo List</div>
}
export default TodoList

接下来,更新根组件 App.js 以使用新创建的 TodoList 组件。

import TodoList from './components/TodoList';
function App() {
   return (
      <div style={{ padding: "5px"}}>
         <TodoList />
      </div>
   );
}
export default App;

接下来,创建一个 count 状态变量来维护要生成的待办事项数量。

const [count, setCount] = useState(100)

这里,

  • 要生成的初始项目数为 100

  • count 是用于维护待办事项数量的状态变量

接下来,创建一个数据数组来维护生成的虚拟待办事项并使用 useMemo Hooks(钩子)保留引用。

React.useMemo(() => {
   for (let i = 1; i <= count; i++) {
      data.push({
         id: i,
         todo: "Todo Item " + i.toString()
      })
   }
}, [count]);

这里,

  • 使用 useMemo 限制每次渲染时生成待办事项。

  • 使用 count 作为依赖项,在计数更改时重新生成待办事项

接下来,将处理程序附加到滚动事件,并更新待办事项的计数,以便在用户移动到页面末尾时生成。

React.useEffect(() => {
   function handleScroll() {
      const isBottom =
         window.innerHeight + document.documentElement.scrollTop
         === document.documentElement.offsetHeight;
      if (isBottom) {
         setCount(count + 100)
      }
   }
   window.addEventListener("scroll", handleScroll);
   return () => {
      window.removeEventListener("scroll", handleScroll);
   };
}, [])

这里我们有,

  • 使用 useEffect Hooks(钩子)来确保 DOM 已准备好附加事件。

  • 为滚动事件附加事件处理程序 scrollHandler

  • 从应用程序中卸载组件时删除事件处理程序

  • 发现用户使用滚动事件处理程序中的 DOM 属性点击页面底部。

  • 一旦用户点击页面底部,计数就会增加 100

  • 一旦计数状态变量发生变化,组件就会重新渲染,并加载更多待办事项。

最后,渲染待办事项,如下所示−

<div>
   <p>List of Todo list</p>
   <ul>
      {data && data.map((item) =>
         <li key={item.id}>{item.todo}</li>
      )}
   </ul>
</div>

组件完整源代码如下 −

import React, { useState } from "react"
function TodoList() {
   const [count, setCount] = useState(100)
   let data = []
   
   React.useMemo(() => {
      for (let i = 1; i <= count; i++) {
         data.push({
            id: i,
            todo: "Todo Item " + i.toString()
         })
      }
   }, [count]);
   
   React.useEffect(() => {
      function handleScroll() {
         const isBottom =
            window.innerHeight + document.documentElement.scrollTop
            === document.documentElement.offsetHeight;
         
         if (isBottom) {
            setCount(count + 100)
         }
      }
      window.addEventListener("scroll", handleScroll);
      return () => {
         window.removeEventListener("scroll", handleScroll);
      };
   }, [])
   return (
      <div>
         <p>List of Todo list</p>
         <ul>
            {data && data.map((item) =>
               <li key={item.id}>{item.todo}</li>
            )}
         </ul>
      </div>
   )
}
export default TodoList

接下来,打开浏览器并运行应用程序。一旦用户到达页面末尾,应用程序就会附加新的待办事项,并无限继续,如下所示 −

实现无限滚动功能

实现 useInfiniteScroll Hooks(钩子)

接下来,让我们尝试通过从现有组件中提取逻辑来创建新的自定义Hooks(钩子),然后在单独的组件中使用它。创建一个新的自定义Hooks(钩子) useInfiniteScroll (src/Hooks/useInfiniteScroll.js),如下所示

import React from "react";
export default function useInfiniteScroll(loadDataFn) {
   const [bottom, setBottom] = React.useState(false);
   React.useEffect(() => {
      window.addEventListener("scroll", handleScroll);
      return () => {
         window.removeEventListener("scroll", handleScroll);
      };
   }, []);
   React.useEffect(() => {
      if (!bottom) return;
      loadDataFn();
   }, [bottom]);
   
   function handleScroll() {
      if (window.innerHeight + document.documentElement.scrollTop
         != document.documentElement.offsetHeight) {
         return;
      }
      setBottom(true)
   }
   return [bottom, setBottom];
}

这里我们有,

  • 使用 use 关键字来命名自定义Hooks(钩子)。这是以 use 关键字开始自定义Hooks(钩子)名称的一般做法,也是让反应知道该函数是自定义Hooks(钩子)的提示

  • 使用状态变量 bottom 来了解用户是否点击了页面底部。

  • 使用 useEffect 在 DOM 可用时注册滚动事件

  • 使用通用函数 loadDataFn 在组件中创建Hooks(钩子)期间提供。这将能够创建用于加载数据的自定义逻辑。

  • 在滚动事件处理程序中使用 DOM 属性来跟踪用户滚动位置。当用户点击页面底部时,底部状态变量会发生变化。

  • 返回底部状态变量的当前值(bottom)和更新底部状态变量的函数(setBottom)

接下来,创建一个新组件 TodoListUsingCustomHook 来应用新创建的Hooks(钩子)。

function TodoListUsingCustomHook() {
   return <div>Todo List</div>
}
export default TodoListUsingCustomHook

接下来,更新根组件 App.js 以使用新创建的 TodoListUsingCustomHook 组件。

import TodoListUsingCustomHook from './components/TodoListUsingCustomHook';
function App() {
   return (
      <div style={{ padding: "5px"}}>
         <TodoListUsingCustomHook />
      </div>
   );
}
export default App;

接下来,创建一个状态变量 data 来管理待办事项列表项。

const [data, setData] = useState([])

接下来,创建一个状态变量 count 来管理要生成的待办事项列表项的数量。

const [data, setData] = useState([])

接下来,应用无限滚动自定义Hooks(钩子)。

const [bottom, setBottom] = useInfiniteScroll(loadMoreData)

这里,bottomsetBottomuseInfiniteScroll Hooks(钩子)。

接下来,创建函数来生成待办事项列表项,loadMoreData

function loadMoreData() {
   let data = []
   for (let i = 1; i <= count; i++) {
      data.push({
         id: i,
         todo: "Todo Item " + i.toString()
      })
   }
   setData(data)
   setCount(count + 100)
   setBottom(false)
}

这里,

  • 根据 count 状态变量生成待办事项

  • 将 count 状态变量增加 100

  • 使用 setData 函数将生成的待办事项列表设置为 data 状态变量

  • 使用 setBottom 函数将 bottom 状态变量设置为 false

接下来,生成初始待办事项,如下所示 −

const loadData = () => {
   let data = []
   for (let i = 1; i <= count; i++) {
      data.push({
         id: i,
         todo: "Todo Item " + i.toString()
      })
   }
   setData(data)
}
React.useEffect(() => {
   loadData(count)
}, [])

这里,

  • 根据初始计数状态变量生成初始待办事项

  • 使用 setData 函数将生成的待办事项列表设置为数据状态变量

  • 使用 useEffect 确保一旦 DOM 可用就会生成数据

最后,按如下所示渲染待办事项 −

<div>
   <p>List of Todo list</p>
   <ul>
      {data && data.map((item) =>
         <li key={item.id}>{item.todo}</li>
      )}
   </ul>
</div>

组件完整源代码如下所示 −

import React, { useState } from "react"
import useInfiniteScroll from '../Hooks/useInfiniteScroll'

function TodoListUsingCustomHook() {
   const [data, setData] = useState([])
   const [count, setCount] = useState(100)
   const [bottom, setBottom] = useInfiniteScroll(loadMoreData)
   
   const loadData = () => {
      let data = []
      for (let i = 1; i <= count; i++) {
         data.push({
            id: i,
            todo: "Todo Item " + i.toString()
         })
      }
      setData(data)
   }
  
  function loadMoreData() {
      let data = []
      
      for (let i = 1; i <= count; i++) {
         data.push({
            id: i,
            todo: "Todo Item " + i.toString()
         })
      }
      setData(data)
      setCount(count + 100)
      setBottom(false)
   }
   React.useEffect(() => {
      loadData(count)
   }, [])
   return (
      <div>
         <p>List of Todo list</p>
         <ul>
            {data && data.map((item) =>
               <li key={item.id}>{item.todo}</li>
            )}
         </ul>
      </div>
   )
}
export default TodoListUsingCustomHook

最后,打开浏览器并检查输出。一旦用户到达页面末尾,应用程序就会附加新的待办事项,并无限继续,如下所示 −

Implementing UseInfiniteScroll Hook

行为与 TodoList 组件相同。我们已成功从组件中提取逻辑并使用它来创建我们的第一个自定义Hooks(钩子)。现在,自定义Hooks(钩子)可用于任何应用程序。

摘要

自定义Hooks(钩子)是一种现有功能,用于将可重用逻辑与功能组件分开,并允许使其成为跨不同功能组件真正可重用的Hooks(钩子)。

ReactJS - 可访问性

可访问性 (a11y) 是以这样一种方式设计 Web 应用程序,即应用程序可供所有人访问,并支持辅助技术为最终用户读取应用程序的内容。

React 支持 Web 应用程序中可访问性的所有方面。让我们在本章中看看 React 如何支持可访问性。

ARIA (aria-*) 属性

WAI-ARIA(Web 可访问性倡议 - 可访问的富互联网应用程序)是一种标准,指定了构建完全可访问的 JavaScript 小部件的方法。它提供了一大组 HTML 属性 (aria-*) 来支持可访问性。React 在其组件中支持所有这些属性。一般情况下,React 会限制 HTML 属性采用 camelCase 格式,但对于可访问性属性,则应采用 kebab-case 或 lisp-case 格式,或者直接按照 HTML 文档中的格式使用。

例如,以下代码展示了如何使用 HTML 可访问性属性。

<input
   type="text"
   aria-label={labelText}
   aria-required="true"
   name="name"
/>

此处,

  • aria-label 用于指定输入元素的标签

  • aria-required 用于指定应填写的输入。

请注意,属性按原样使用(kebab-case 格式)。

语义 HTML

通过应用语义 HTML(文章、部分、导航等)标签编码的 Web 文档可提高文档的可访问性。在 React 中,有些情况下我们使用块(div)只是为了满足 React 框架。例如,React 在其渲染代码中不支持多个标签。为了克服限制,开发人员可以使用父标签(div)将多个标签作为子标签。

function ShowItems({ data }) {
   return (
      <div>
         <dt>{data.title}</dt>
         <dd>{data.description}</dd>
      </div>
   );
}

React 提供了 Fragment 组件来解决此问题。我们只需替换 Fragment 即可,如下所示 −

function ShowItems({ data }) {
   return (
      <Fragment>
         <dt>{data.title}</dt>
         <dd>{data.description}</dd>
      </Fragment>
   );
}

表单

每个输入都应贴上标签,并且标签应具有描述性,以便理解输入元素。React 提供了一个特殊的 props htmlFor 来指定输入元素的具体描述。开发人员可以使用它来创建可访问的表单。

<label htmlFor="firstName">Firstname:</label>
<input id="firstName" type="text" name="name"/>

键盘支持

键盘支持是创建可访问的 Web 应用程序的必备条件。一些需要键盘支持的功能包括:

Focus − React 提供了一个名为 Ref 的概念来访问原始 DOM 元素。当应用程序需要对 DOM 元素进行原始访问时,可以使用 Ref转发 Ref 来管理原始 DOM 元素。

跳过链接 − 跳过导航链接是支持可访问性的必备功能。它们允许用户在仅使用键盘访问应用程序时一次性跳过所有导航。可以使用智能锚标记来完成,React 完全支持这些标记。

<body>
<a href="#maincontent">Skip to main content</a>
...
<main id="maincontent">
   ...
</main>

鼠标和指针功能 − 要创​​建真正可访问的应用程序,所有功能都应通过键盘访问。应将具有高级鼠标和指针用户交互的组件更改为仅适应键盘用户交互。React 提供所有事件处理逻辑,以将默认的基于鼠标的 UI 修改为基于键盘的 UI。

Aria 组件

React 社区提供了许多具有完全可访问性的组件。它们可以按原样使用,无需任何修改。它们会自动使应用程序可访问。一些具有 aria 支持的第三方组件如下 −

  • react-aria − react-aria 提供了具有完全可访问性的大量 React 组件

  • react-modal − react-modal 提供了具有 aria 支持的模态组件。

  • react-aria-modal − react-aria-modal 是另一个支持 aria 的模式组件。

  • react-select − react-select 提供支持 aria 的选择组件。

  • react-dropdown-aria − react-dropdown-aria 提供支持 aria 的下拉组件。

  • react-aria-menubutton − react-aria-menubutton 提供支持 aria 的菜单按钮组件。

  • react-aria-tabpanel − react-aria-tabpanel 提供支持 aria 的选项卡面板组件。

摘要

React 提供了许多功能来创建完全可访问、支持 aria 的 Web 应用程序。创建可访问的应用程序始终是一项挑战,而 React 以现成的组件和核心功能的形式减轻了负担,可以从头开始编写可访问的应用程序。

ReactJS - 代码拆分

打包是前端应用程序中的重要阶段之一。它将所有前端代码连同依赖项一起打包成一个大包(bundle.js)。最终的包由打包器进行大小优化。一些流行的打包器是 webpack、parcel 和 rollup。大多数情况下,最终的包都很好。如果最终打包的代码很大,则可以指示打包器将代码打包成多个项目,而不是单个大块。

让我们学习如何提示打包器拆分代码并单独打包。

动态导入

动态导入指示打包器拆分代码。动态导入基本上是根据需要获取所需的模块。执行正常的代码如下所示 −

import { round } from './math';
console.log(round(67.78));

可以动态导入相同的代码,如下所示 −

import("./math").then(math => {
  console.log(math.round(67.78);
});

React Lazy 组件

React 提供了一个函数 React.lazy 来动态导入组件。通常,正如我们所知,React 组件将按如下所示导入 −

import MyComponent from './MyComponent';

使用 React.lazy() 函数动态导入上述组件,如下所示 −

const MyComponent = React.lazy(() => import('./MyComponent'));
The imported component should be wrapped into a Suspense component to use it in the application.
import React, { Suspense } from 'react';
const MyComponent = React.lazy(() => import('./MyComponent'));
function App() {
   return (
      <div>
         <Suspense fallback={<div>Loading...</div>}>
            <MyComponent />
         </Suspense>
      </div>
   );
}

Suspense 组件用于在原始组件加载期间加载临时 UI。Suspense 组件包含一个 fallback 属性来指定后备 UI。后备 UI 可以是任何 React 元素。有时,动态组件可能会由于网络问题或代码错误而无法加载。我们可以使用错误边界来处理这些情况,如下所示 −

import React, { Suspense } from 'react';
import MyErrorBoundary from './MyErrorBoundary';
const MyComponent = React.lazy(() => import('./MyComponent'));
const AnotherComponent = () => (
   <div>
      <MyErrorBoundary>
         <Suspense fallback={<div>Loading...</div>}>
            <section>
               <MyComponent />
            </section>
         </Suspense>
      </MyErrorBoundary>
   </div>
);

此处,

  • MyErrorBoundary 包裹在 Suspense 组件周围。

  • 如果在加载 MyComponent 时出现任何错误,则 MyErrorBoundary 会处理该错误并回退到其组件中指定的通用 UI。

应用代码拆分的最佳场景之一是路由。可以使用路由来应用代码拆分,如下所示 −

import React, { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
   <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
         <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
         </Routes>
      </Suspense>
   </BrowserRouter>
);

这里,

  • 所有路由(组件)均使用 React.lazy() 功能加载

  • 由于所有路由(HomeAbout )均通过动态导入加载,因此每个路由在初始化期间将仅加载必要的组件,而不是所有组件。

React.lazy() 仅支持默认导出。在 React 中,我们可以通过指定动态名称而不是默认关键字来导出组件,如下所示 −

export const MyComponent = /* ... */;

为了使其在 React.lazy() 中可用,我们可以使用 default 关键字重新导出组件,如下所示 −

export { MyLazyComponent as default } from "./MyComponent.js";

然后,我们可以像往常一样导入它,

import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));

应用延迟加载

让我们创建一个新的 React 应用程序来学习如何在本节中应用代码拆分。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。

// 删除所有 css 类

接下来,创建一个简单的 hello 组件 Hello (src/Components/Hello.js) 并呈现一条简单的消息,如下所示−

import React from "react";
class Hello extends React.Component {
   constructor(props) {
      super(props)
   }
   render() {
      return (
         <div>Hello, {this.props.name}</div>
      );
   }
}
export default Hello;

在这里,我们使用了 name 属性来呈现具有给定名称的 hello 消息。

接下来,创建一个简单的组件 SimpleErrorBoundary (src/Components/SimpleErrorBoundary.js),并在错误期间呈现后备 UI 或子组件,如下所示 −

import React from "react";
class SimpleErrorBoundary extends React.Component {
   
   constructor(props) {
      super(props);
      this.state = { hasError: false };
   }
   
   static getDerivedStateFromError(error) {
      return { hasError: true };
   }
   
   componentDidCatch(error, errorInfo) {
      console.log(error);
      console.log(errorInfo);
   }
   
   render() {
      if (this.state.hasError) {
         return <h1>Please contact the administrator.</h1>;
      }
      
      return this.props.children;
   }
}
export default SimpleErrorBoundary;

此处,

  • hasError 是一个使用 false 值初始化的状态变量。

  • getDerivedStateFromError 在出现错误时更新错误状态。

  • componentDidCatch 将错误记录到控制台中。

  • render 将根据应用程序中的错误呈现错误 UI 或子项。

接下来,打开 App 组件 (src/App.js),并通过 React.lazy() 加载 hello 组件,如下所示 −

import './App.css'
import React, { Suspense, lazy } from 'react';
import SimpleErrorBoundary from './Components/SimpleErrorBoundary';
const Hello = lazy(() => import('./Components/Hello'));

function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleErrorBoundary>
                  <Suspense fallback="<div>loading...</div>">
                     <Hello name="Peter" />
                  </Suspense>
               </SimpleErrorBoundary>
            </div>
         </div>
      </div>
   );
}
export default App;

这里我们有,

  • 从 react 包中导入了 lazySuspense 组件。

  • 通过使用 Suspense 和 SimpleErrorBoundary 组件包装 Hello 组件来使用它。

最后,在浏览器中打开应用程序并检查最终结果。延迟加载在前端没有任何可见的变化。它将以通常的方式呈现 hello 组件,如下所示 −

应用延迟加载

摘要

代码拆分将有助于通过仅加载特定页面中使用的必要组件来优化大型应用程序。 Suspense 和错误边界组件可以用来处理动态加载组件时出现的意外错误。

ReactJS - Context 上下文

Context 是 React 中的一个重要概念。它提供了将信息从父组件传递到其所有子组件到任何嵌套级别的能力,而无需通过每个级别的 props 传递信息。Context 将使代码更具可读性和易于理解。Context 可用于存储不变或变化最小的信息。上下文的一些用例如下 −

  • 应用程序配置

  • 当前已验证用户的信息

  • 当前用户设置

  • 语言设置

  • 应用程序/用户的主题/设计配置

让我们在本章中学习如何创建上下文及其用法。

上下文如何工作?

让我们学习上下文的基本概念及其工作原理。上下文有四个部分,

  • 创建新上下文

  • 在根组件中设置上下文提供程序

  • 在需要上下文信息的组件中设置上下文消费者

  • 访问上下文信息并在渲染方法中使用它

让我们创建一个应用程序以更好地了解上下文及其用法。让我们创建一个全局上下文,用于在应用程序根组件中维护主题信息,并在子组件中使用它。

首先,使用以下命令创建并启动应用程序,

create-react-app myapp
cd myapp
npm start

接下来,在 components 文件夹 (src/components/HelloWorld.js) 下创建一个组件 HelloWorld

import React from "react";
import ThemeContext from "../ThemeContext";
class HelloWorld extends React.Component {
   render() {
      return <div>Hello World</div>
   }
}
export default HelloWorld

接下来,创建一个新的上下文(src/ThemeContext.js)用于维护主题信息。

import React from 'react'
const ThemeContext = React.createContext({
   color: 'black',
   backgroundColor: 'white'
})
export default ThemeContext

这里,

  • 使用 React.createContext 创建一个新上下文。

  • 上下文被建模为具有样式信息的对象。

  • 设置文本颜色和背景的初始值。

接下来,通过包含 HelloWorld 组件和主题提供程序以及主题上下文的初始值来更新根组件 App.js

import './App.css';
import HelloWorld from './components/HelloWorld';
import ThemeContext from './ThemeContext'

function App() {
   return (
      <ThemeContext.Provider value={{
         color: 'white',
         backgroundColor: 'green'
      }}>
      <HelloWorld />
      </ThemeContext.Provider>
   );
}
export default App;

这里使用了 ThemeContext.Provider,这是一个非可视组件,用于设置其所有子组件中使用的主题上下文的值。

接下来,在 HelloWorld 组件中包含一个上下文消费者,并使用 HelloWorld 组件中的主题信息来设置 hello world 消息的样式。

import React from "react";
import ThemeContext from "../ThemeContext";
class HelloWorld extends React.Component {
   render() {
      return  (
         <ThemeContext.Consumer>
         {({color, backgroundColor} ) =>
            (<div style={{
               color: color,
               backgroundColor: backgroundColor }}>
               Hello World
            </div>)
         }
         </ThemeContext.Consumer>
      )
   }
}
export default HelloWorld

这里,

  • 使用了ThemeContext.Consumer,这是一个非可视化组件,提供对当前主题上下文详细信息的访问

  • 使用函数表达式获取ThemeContext.Consumer中的当前上下文信息

  • 使用对象解构语法获取主题信息并设置 color 和 backgroundColor 变量中的值。

  • 使用主题信息通过 style props 设置组件样式。

最后,打开浏览器并检查应用程序的输出

ReactJS Context

总结

上下文降低了维护全局数据的复杂性在 React 应用程序中。它提供了提供者和消费者的清晰概念,并简化了上下文的实现。

ReactJS - 错误边界

一般来说,在不影响应用程序 UI 的情况下捕获错误并进行处理是相当具有挑战性的。React 引入了一个称为错误边界的概念,用于在 UI 渲染期间捕获错误,在后台处理并在前端应用程序中显示后备 UI。这不会影响 UI 应用程序,因为它是使用替代 UI(如警告消息)而不是损坏的页面来处理的。

让我们在本章中了解什么是错误边界以及如何在我们的应用程序中应用它。

错误边界的概念

错误边界是具有特殊功能的普通 React 组件,可以捕获组件树中任何地方发生的所有错误。可以记录在过程中捕获的错误,然后可以向用户显示指定错误的替代用户界面。

通过实现两个函数,可以将基于普通 React 类的组件升级为支持错误边界的组件。

static getDerivedStateFromError() −这是一个静态函数,当应用程序发生错误时将被调用。

static getDerivedStateFromError(error) {
    return { hasError: true };
}

此处,hasError 是一个自定义状态变量,指定应用程序发生某些错误,可在后续渲染中用于显示后备 UI 而不是正常 UI。

componentDidCatch() −这是组件生命周期事件,当组件树中发生错误时,会使用错误信息调用该事件

componentDidCatch(error, errorInfo) {
// 在控制台中记录错误或使用日志服务将其存储在某处
}

一旦组件升级以处理错误,它就可以在应用程序的任何地方用作普通组件。假设组件的名称为 SimpleErrorBoundary,那么它可以按如下所示使用 −

<SimpleErrorBoundary>
   <MyComponent />
</SimpleErrorBoundary>

此处,

React 将捕获 MyComponent 组件中任何地方发生的错误,并将其发送到 SimpleErrorBoundary 组件进行进一步处理。

React 不会捕获除以下场景中发生的错误之外的所有错误,

  • 事件处理程序 − 事件处理程序是普通的 javascript 函数,可以使用 try/catch 并自行渲染回退 UI,可能不需要错误边界提示。

  • 像 setTimeout 这样的异步代码。

  • 服务器端渲染。错误边界仅用于前端应用程序。

  • 错误发生在错误边界组件本身中。

应用错误边界

让我们创建一个新的 React 应用程序来学习如何在本节中应用错误边界。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。然后,创建一个简单的组件 SimpleErrorBoundary (src/Components/SimpleErrorBoundary.js) 并在错误期间呈现后备 UI 或子组件,如下所示 −

import React from "react";
class SimpleErrorBoundary extends React.Component {
   
   constructor(props) {
      super(props);
      this.state = { hasError: false };
   }
   static getDerivedStateFromError(error) {
      return { hasError: true };
   }
   
   componentDidCatch(error, errorInfo) {
      console.log(error);
      console.log(errorInfo);
   }
   
   render() {
      if (this.state.hasError) {
         return <h1>Please contact the administrator.</h1>;
      }
      return this.props.children;
   }
}
export default SimpleErrorBoundary;

此处,

  • hasError 是一个使用 false 值初始化的状态变量。

  • getDerivedStateFromError 在出现错误时更新错误状态

  • componentDidCatch 将错误记录到控制台中。

  • render 将根据应用程序中的错误呈现错误 UI 或子项。

接下来,创建一个简单的组件 Hello (src/Components/Hello.js) 并呈现一个简单的文本消息,如下所示 −

import React from "react";
class Hello extends React.Component {
   constructor(props) {
      super(props)
   }
   render() {
      if(this.props.name == "error") {
         throw('Invoked error')
      }
      return (
         <div>Hello, {this.props.name}</div>
      );
   }
}
export default Hello;

这里我们有,

  • 使用 name 属性来显示问候消息。

  • 如果给定的名称是 error,则抛出错误。

接下来,打开 App 组件 (src/App.js),并使用 SimpleErrorBoundary 组件,如下所示 −

import './App.css'
import React from 'react';
import SimpleErrorBoundary from './Components/SimpleErrorBoundary'
import Hello from './Components/Hello'
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleErrorBoundary>
                  <Hello name="error" />
               </SimpleErrorBoundary>
            </div>
         </div>
      </div>
   );
}
export default App;

这里我们有,

  • 从 react 包中导入 SimpleErrorBoundary 组件。

  • 使用 SimpleErrorBoundary 组件,并将 error 作为 name props 的值。

最后,在浏览器中打开应用程序并检查最终结果。

Applying Error Boundary

在开发工具中打开控制台,您可以看到错误信息,如下所示 −

Invoked error
SimpleErrorBoundary.js:17 {
   componentStack: '
 at Hello (http://localhost:3000/static/js/bun…09:5)
 at
   div
 at div
 at div
    at App'
}

摘要

错误边界是处理前端应用程序中意外错误的安全且有价值的组件。如果没有错误边界,就很难隐藏错误并获取有关发生的错误的有价值的调试信息。

ReactJS - 转发 Refs

Ref 是直接操作 DOM 的出口,不受状态更改更新组件的影响。Ref 可以应用于 DOM 元素,但要将 Ref 应用于 React 组件并获取组件内部深处的 DOM 元素,转发 Ref 是最佳选择。 转发 Ref 允许组件从顶级组件接收 ref,并将其进一步传递到下一级组件以获取 DOM 元素。

让我们在本章中学习如何使用 转发 Ref。

forwardRef 方法的签名

forwardRef 的签名如下 −

const new-component = React.forwardRef(fn)

其中 fn 的签名如下 −

(props, ref) => {
    // 通过附加 ref 来渲染一个 React 组件并返回它
}

使用 forwardRef 的一个简单示例如下−

const MyInput = React.forwardRef((props, ref) => (
   <input type="text" ref={ref} value={props.value} />
));

const myRef = React.createRef();
<MyInput ref={myRef} value="Hi" />

这里,

  • MyInput 从顶层获取 ref 并将其传递给底层输入元素。

  • myRef 被分配给 MyInput 组件。

  • MyInput 组件将 myRef 传递给底层输入元素。

  • 最后,myRef 指向输入元素。

在组件中应用 forwardRef

让我们通过开发应用程序来学习 forwardRef 的概念。

首先,创建一个新的 react 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。然后,创建一个简单的组件 SimpleForwardRef (src/Components/SimpleForwardRef.js),如下所示 −

import React from "react";
const SimpleForwardRef = React.forwardRef((props, ref) => (
   <input type="text" ref={ref} value={props.value} />
));
export default SimpleForwardRef

这里我们有,

  • 使用 forwardRef 将 ref 传递给 input 元素。

  • input 元素使用 ref props 设置 ref 值。

接下来,打开 App 组件 (src/App.js) 并使用 SimpleForwardRef 组件更新内容,如下所示 −

import './App.css'
import React, { useEffect } from 'react';
import SimpleForwardRef from './Components/SimpleForwardRef'
function App() {
   const myRef = React.createRef();
   useEffect(() => {
      setTimeout(() => {
         myRef.current.value = "Hello"
      }, 5000)
   })
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleForwardRef ref={myRef} value="Hi" />
            </div>
         </div>
      </div>
   );
}
export default App;

这里,

  • myRef 使用 createRef 方法创建,并将其传递到 SimpleForwardRef 组件中。

  • myRef 表示 SimpleForwardRef 组件渲染的输入元素。

  • useEffect 将通过 myRef 访问输入元素,并尝试将输入的值从 hi 更改为 Hello。

最后,在浏览器中打开应用程序。 5 秒后,输入的值将更改为 Hello,如下所示 −

Applying ForwardRef Component

摘要

Forward ref 增强了 ref 概念,可在 React 应用程序的任何地方使用。任何 DOM 元素(可能是组件层次结构内任何深度的元素)都可以使用转发 Ref 概念进行访问和操作。

ReactJS - Fragments(片段)

Fragment 是一种简单的解决方案,无需在 DOM 中添加额外的标记即可对多个 React 元素进行分组。

在 React 中,组件的 render 方法允许返回一个 React 元素。要使组件返回多个元素,需要将元素包装到通用容器元素中。例如,要返回多个 p 元素,应将其包装在 div 元素周围,如下所示 −

<div>
   <p>One</p>
   <p>Two
   </p>
</div>

包装通用根元素对于大多数场景来说都是可以的,但某些场景需要特殊处理。它们如下 −

  • 某些元素(例如 litd 等)可能无法包装通用元素。

  • 当组件只需要返回元素的部分列表时(最终将包装在父组件中)。

让我们编写一个简单的组件来更好地理解我们的问题。该组件的功能是返回部分客户列表,如下所示 −

<li>John</li>
<li>Peter</li>

使用 create-react-app 创建一个新应用程序并启动该应用程序。

create-react-app myapp
cd myapp
npm start

在 components 文件夹下创建一个组件 ListOfCustomer (src/components/ListOfCustomer.js)

import React from 'react'
class ListOfCustomer extends React.Component {
   constructor(props) {
      super(props);
   }
   render() {
      console.log(this.props)
      var names = this.props['names'];
      var namesList = names.map(
         (name, index) => <li>{name}</li>
      )
      return <ul>{namesList}</ul>
   }
}
export default ListOfCustomer

此处,组件循环遍历 names 属性并将其呈现为 li 元素列表。

现在,在我们的根组件 (App.js) 中使用该组件

import ListOfCustomer from './components/ListOfCustomer';
function App() {
   var names = ["John", "Peter"]
   return (
      <ul>
         <ListOfCustomer names={names} />
      </ul>
   );
}
export default App;

这将导致渲染多个 ul,如下所示 −

<ul><ul><li>John</li><li>Peter</li></ul></ul>

让我们将组件更改为使用 React.Fragment

import React, { Fragment } from 'react'
class ListOfCustomer extends React.Component {
   constructor(props) {
      super(props);
   }
   render() {
      console.log(this.props)
      var names = this.props['names'];
      var namesList = names.map(
         (name, index) => <li>{name}</li>
      )
      return <React.Fragment>{namesList}</React.Fragment>
   }
}
export default ListOfCustomer

现在,我们的组件呈现有效的 HTML 文档。

<ul><li>John</li><li>Peter</li></ul>

带键的片段

在上面的示例中,React 在开发者控制台中抛出警告,如下所示 −

Warning: Each child in a list should have a unique "key" prop.
Check the render method of `ListOfCustomer`. See https://reactjs.org/link/warning-keys for more information.
li
ListOfCustomer@http://localhost:3000/main.4bbe8fa95c723e648ff5.hot-update.js:26:10
ul
App
render - ListOfCustomer.js:9

正如警告所述,React 为列表中的每个元素提供唯一的键。它使用键来标识哪些元素被更改。添加或删除。React.Fragment 允许通过其 key 属性传递键。React 将在内部使用它来仅呈现列表中已修改的项目。让我们更改代码并将键添加到 React.Fragment,如下所示−

import React, { Fragment } from 'react'
class ListOfCustomer extends React.Component {
   constructor(props) {
      super(props);
   }
   render() {
      console.log(this.props)
      var names = this.props['names'];
      var namesList = names.map(
         (name, index) => <li key={index}>{name}</li>
      )
      return <React.Fragment>{namesList}</React.Fragment>
   }
}
export default ListOfCustomer

这将消除错误。

简短语法

React.Fragment 有另一种更短的语法,既易于使用又易读。

<>
JSX element
</>

让我们更改代码以适应更短的语法。更新后的代码如下 −

import React, { Fragment } from 'react'
class ListOfCustomer extends React.Component {
   constructor(props) {
      super(props);
   }
   render() {
      console.log(this.props)
      var names = this.props['names'];
      var namesList = names.map(
         (name, index) => <li key={index}>{name}</li>
      )
      return <>{namesList}</>
   }
}
export default ListOfCustomer

ReactJS - 高阶组件

由于 React 组件是通过组合(一个组件位于另一个组件内)而不是通过继承来互连的,因此 React 组件中使用的逻辑不会直接共享给另一个组件。React 社区提供了多种选项来在组件之间共享逻辑,其中一种选项就是高阶组件。HOC 本身不是 React API,而是一种没有副作用的设计模式。

让我们在本章中学习如何使用高阶组件。

如何使用高阶组件

基本上,HOC 是一个函数,它将 React 组件作为输入,然后根据输入组件创建一个新的 React 组件并返回新创建的(包装的)组件。例如,HOC 函数可以接收纯数据渲染组件作为输入,然后返回一个具有数据获取功能的新组件 &使用输入组件的数据渲染功能。

让我们一步一步地看看如何使用 HOC 并在两个组件之间共享逻辑。让我们考虑从外部 URL 获取和渲染数据的场景。

  • 根据功能创建一个具有一个或多个输入参数的 HOC 函数。

  • HOC 函数的第一个参数应该是具有辅助逻辑(例如,数据渲染逻辑)的反应组件。

  • HOC 函数的第二个参数应根据我们的要求定义。对于我们的数据获取场景,数据 URL 是获取数据的必要信息。因此,我们应该将其作为 HOC 函数的第二个参数。

function createHOC(WrappedComponent, url) {
    // 使用 WrappedComponent 创建新组件
}
  • 如果确实有必要,HOC 函数可以使用任意数量的参数。

  • 在 HOC 函数内部创建一个新组件,在其 componentDidMount 事件中支持主要逻辑(例如,使用第二个 url 参数获取数据逻辑)。

function createFetchHOC(WrappedComponent, url) {
   class DataFetcher extends React.Component {
      componentDidMount() {
         fetch(url)
            .then((response) => response.json())
            .then((data) => {
               this.setState({
                  data: data
               });
         });
      }
   }
}
  • 通过传递从 componentDidMount 事件获取的数据来呈现输入组件。

function createFetchHOC(WrappedComponent, url) {
   class DataFetcher extends React.Component {
      render() {
         return (
            <WrappedComponent data={this.state.data} {...this.props} />
         )
      }
   }
}
  • 返回新创建的组件。

function createFetchHOC(WrappedComponent, url) {
   class DataFetcher extends React.Component {
   }
   return DataFetcher;
}
  • 通过结合 DataFetcher(createFetchHOC)和 Wrapped 组件创建一个新组件。

const UserListWithFetch = createFetchHOC(
   UserList,
   "users.json"
);
  • 最后,根据需要在任何地方使用新组件

<UserListWithFetch />

应用 HOC 组件

让我们通过应用 HOC 组件创建一个新的应用程序。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。

// 删除所有 css 类

接下来,创建一个新的 HOC 函数,如下所示 −

import React from 'react';
function createFetchHOC(WrappedComponent, url) {
   class DataFetcher extends React.Component {
      constructor(props) {
         super(props);
         this.state = {
            data: []
         };
      }
      
      componentDidMount() {
         fetch(url)
         .then((response) => response.json())
         .then((data) => {
            this.setState({
               data: data
            });
         });
      }
      render() {
         return (
            <WrappedComponent data={this.state.data} {...this.props} />
         )
      }
   }
   return DataFetcher;
}
export default createFetchHOC;

接下来,在 public 文件夹中创建一个文件 users.json (public/users.json) 来存储用户信息。我们将尝试使用 FetchRenderProps 组件获取它并在我们的应用程序中显示它。

[{"id":1,"name":"Fowler","age":18},
{"id":2,"name":"Donnell","age":24},
{"id":3,"name":"Pall","age":26}]

接下来,在 public 文件夹中创建一个文件 todo_list.json (public/todo_list.json) 来存储待办事项列表信息。我们将尝试使用 FetchRenderProps 组件获取它并在我们的应用程序中显示它。

[{"id":1,"title":"Learn JavaScript","is_done":true},
{"id":2,"title":"Learn React","is_done":true},
{"id":3,"title":"Learn Typescript","is_done":false}]

接下来,创建一个新组件 UserList (src/Components/UserList.js) 来呈现用户,如下所示 −

import React from "react";
class UserList extends React.Component {
   constructor(props) {
      super(props);
   }
   render() {
      return (
         <>
            <ul>
               {this.props.data && this.props.data.length && this.props.data.map((item) =>
                  <li key={item.id}>{item.name}</li>
               )}
            </ul>
         </>
      )
   }
}
export default UserList;

在这里,我们使用了数据 props 来呈现用户列表

接下来,创建一个新组件 TodoList (src/Components/TodoList.js) 来呈现待办事项,如下所示 −

import React from "react";
class TodoList extends React.Component {
   constructor(props) {
      super(props);
      this.todos = this.props.data
   }
   render() {
      return (
         <>
            <ul>
               {this.props.data && this.props.data.length && this.props.data.map(
                  (item) =>
                  <li key={item.id}>{item.title} {item.is_done && <strong>Done</strong>}</li>
               )}
            </ul>
         </>
      )
   }
}
export default TodoList;

在这里,我们使用数据道具来呈现待办事项列表。

接下来,创建一个新组件 SimpleHOC,通过单个 HOC 组件呈现用户列表和待办事项列表。

import React from "react";
import UserList from "./UserList";
import TodoList from "./TodoList";
import createFetchHOC from "./createFetchHOC";
const UserListWithFetch = createFetchHOC(
   UserList,
   "users.json"
);

const TodoListWithFetch = createFetchHOC(
   TodoList,
   "todo_list.json"
);

class SimpleHOC extends React.Component {
   constructor(props) {
      super(props);
   }
   render() {
      return (
      <>
         <UserListWithFetch />
         <TodoListWithFetch />
      </>
      )
   }
}
export default SimpleHOC;

这里我们有,

  • 通过结合 TodoList 和 DataFetcher 组件创建 UserListWithFetch 组件。

  • 通过结合 Users 和 DataFetcher 组件创建 TodoListWithFetch 组件。

接下来,打开 App.js 并使用 SimpleHOC 组件对其进行更新。

import './App.css'
import React from 'react';
import SimpleHOC from './Components/SimpleHOC'

function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleHOC />
            </div>
         </div>
      </div>
   );
}
export default App;

最后,在浏览器中打开应用程序并检查最终结果。应用程序将呈现如下图所示 −

应用 HOC 组件

摘要

高阶组件是一种在组件之间共享逻辑的有效方法。它在许多第三方组件中被广泛使用,成功率很高,并且是经过时间考验的在 React 域中共享逻辑的方法。

ReactJS - 与其他库集成

尽管 React 提供了创建完整 Web 应用程序所需的所有功能,但由于遗留系统在另一个库中编码、从另一个框架迁移等原因,与其他库集成是必须的,React 可以与其他库共存,并提供与其他系统一起使用所需的基础架构。

在本章中,让我们看看如何将 React 组件与其他库(如 jQuery、backbone 等)一起使用。

基于 CreateRoot 的集成

React 使用 ReactDOMClient 模块中的 createRoot() 方法将自身附加到主 HTML 文档。createRoot() 不会干扰除附加元素之外的 HTML 文档。开发人员可以利用此行为在同一文档中混合多个库。

让我们看看如何通过将 React 应用程序附加到单独的元素中,将 jQuery 和 React 组件集成到单个文档中。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,在组件文件夹 (src/components/Hello.js) 下创建一个 React 组件 Hello

import React from "react";
class Hello extends React.Component {
   constructor(props) {
      super(props)
   }
   render() {
      return (
         <div>Hello, {this.props.name}</div>
      );
   }
}
export default Hello;

接下来,打开index.html (public/index.html)并添加一个新容器(jquery-root),如下所示 −

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="utf-8" />
      <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <meta name="theme-color" content="#000000" />
      <meta name="description" content="Web site created using create-react-app" />
      <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
      <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
      
      <script src="https://code.jquery.com/jquery-3.6.1.slim.min.js"></script>
      <title>React App</title>
   </head>
   <body>
      <noscript>You need to enable JavaScript to run this app.</noscript>
      <div style="padding: 10px;">
         <div id="root"></div>
         <div id="jquery-root"></div>
      </div>
      <script>
         $(document).ready(function() {
            $("#jquery-root").text("Hello, from jQuery")
         })
      </script>
   </body>
</html>

这里,

  • jQuery 库通过 CDN 链接

  • 它通过 $(document).ready 方法按传统方式初始化

  • 并使用 jQuery 选择器 (#jquery-root) 和文本方法附加消息

接下来,打开 index.js (src/index.js) 并将我们的 hello 组件附加到根容器中,如下所示 −

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import Hello from './Components/Hello';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
   <React.StrictMode>
      <Hello name="from React" />
   </React.StrictMode>
);
reportWebVitals();

这里,

  • 使用 createRoot() 方法附加 React 应用程序。

  • Hello 组件渲染到 HTML 文档的根元素中。

最后,在浏览器中打开应用程序并检查结果。react 和 jQuery 库都会发出 hello 消息,如下所示 −

CreateRoot Based Integration

Ref 基于集成

一般来说,React 不知道其他库执行的任何 DOM 操作。因此,要将 React 与其他库一起使用,React 不应进行任何 DOM 操作,而应将所有更改转发给其他库。

众所周知,React 提供了一个名为 Ref 的出口,用于操作 DOM 元素而不影响/受状态更改的影响。开发人员可以利用这些功能创建其他库的包装器 React 组件,并在 React 应用程序中使用它。在 React 组件中使用其他库的标准步骤如下,

  • 创建一个 React 组件并渲染一个空的 div

render() {
   return <div />
}
  • 将 ref 附加到渲染的 div,如下所示 −

render() {
   return <div ref={el => this.el = el} />
}
  • componentDidMount() 生命周期事件中使用附加的 ref 操作 dom 元素,如下所示 −

componentDidMount() {
   this.$el = $(this.el);
   this.$el.somePlugin(); // create dom
   // 根据需要调用 this.$el.pluginAPI()
}
  • 在 componentWillUnmount() 生命周期事件中使用附加的 ref 销毁 dom 元素,如下所示−

componentWillUnmount() {
   this.$el.somePlugin('destroy'); // destroy dom
   // this.$el.destroyAPI() 从 dom 中删除元素
}

让我们在下一节中应用这些技术将 jQuery 插件集成到应用程序中。

JQuery slick 插件集成

让我们尝试将 slick jquery 插件(https://github.com/kenwheeler/slick)集成到 react 组件中

首先,创建一个新的 react 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,安装 slick jQuery 插件

npm install jquery slick-carousel --save

接下来,将 slick 插件包(css 和 assets)中的 slick 文件夹复制到应用程序的公共文件夹中。滑块文件夹的内容如下所示 −

.
├── ajax-loader.gif
├── config.rb
├── fonts
│   ├── slick.eot
│   ├── slick.svg
│   ├── slick.ttf
│   └── slick.woff
├── slick-theme.css
├── slick-theme.less
├── slick-theme.scss
├── slick.css
├── slick.js
├── slick.less
├── slick.min.js
└── slick.scss

接下来,创建一个简单的组件 ReactSlick (src/Components/ReactSlick.js),如下所示 −

import React from "react";
import $ from 'jquery';
import slick from 'slick-carousel';
class ReactSlick extends React.Component {
   componentDidMount() {
      this.$el = $(this.el);
      this.$el.slick();
   }
   componentWillUnmount() {
      this.$el.slick('destroy');
   }
   render() {
      return (
         <div>
            <div ref={el => this.el = el}>
               {this.props.children}
            </div>
         </div>
      );
   }
}
export default ReactSlick;

这里,

  • 使用 props 中的子元素渲染一个 div

  • 将 ref 附加到 div 元素

  • 在 componentDidMount() 生命周期事件中使用 ref 将插件附加到元素

  • 在 componentWillUnmount 生命周期事件中使用 ref 将插件销毁到元素

接下来,打开 App 组件 (src/App.js) 并使用 ReactSlick 组件更新内容,如下所示 −

import ReactSlick from './Components/ReactSlick';
function App() {
   return (
      <ReactSlick>
         <div className="box"><h1>1</h1></div>
         <div className="box"><h1>2</h1></div>
         <div className="box"><h1>3</h1></div>
         <div className="box"><h1>4</h1></div>
      </ReactSlick>
   );
}
export default App;

这里,

  • 渲染 ReactSlick 组件

  • 使用四个带有数字(1、2、3 和 4)的 div 作为滑块

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。然后,打开 index.html (public/index.html) 并添加必要的样式,如下所示 −

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="utf-8" />
      <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <meta name="theme-color" content="#000000" />
      <meta
      name="description"
      content="Web site created using create-react-app"
      />
      <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
      <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
      <link rel="stylesheet" type="text/css" href="slick/slick.css"/>
      <link rel="stylesheet" type="text/css" href="slick/slick-theme.css"/>
      <style>
         #root {
            margin: auto;
            padding-left: 25px;
            padding-top: 25px;
            width: 300px;
            color: gray;
            font-family:Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;
            padding-left : 25px;
            text-align: center;
         }
         .box {
            background-color: skyblue;
            text-align: center;
            color: white
         }
      </style>
      <title>React App</title>
   </head>
   <body>
      <noscript>You need to enable JavaScript to run this app.</noscript>
      <div style="background-color: white; margin: 2px;">
         <div id="root"></div>
      </div>
   </body>
</html>

这里我们有,

  • 更新了根组件的样式

  • 更新了滑块的样式(.box)

  • 包括 slick 插件特定的样式(slick/slick.css 和 slick/slick-theme.css)

最后,在浏览器中打开应用程序。jQuery slick 滑块将通过 React 组件呈现,如下所示 −

JQuery Slick Plugin Integration

摘要

React 提供了多种在同一个项目中使用 react 和其他库的方法。每种集成方法都简单有效。开发人员应避免使用其他库,除非在不可避免的情况下,例如遗留应用程序、迁移应用程序等。

ReactJS - 优化性能

React 内部处理应用程序的性能,并利用每个机会对其进行优化。作为开发人员,我们可以做一些事情来最大限度地提高应用程序的性能。让我们在本章中了解一些从 React 库中获得最大性能的技术。

性能优化技术

一些性能优化技术如下 −

使用生产构建 − React 有两种模式,开发和生产。开发模式适用于开发人员,开发模式为开发人员提供了许多有用的东西,以便更好地了解应用程序内部并调试应用程序。这会减慢应用程序的速度。为了获得最佳性能,请启用生产模式并部署应用程序。

CDN 使用 cdn 链接 React 库的应用程序应使用 React 库的生产版本,如下所示 −

<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>

创建 React 应用程序 −使用 create-react-app CLI 创建的应用程序可以使用以下命令创建应用程序的生产版本。

npm run build

Brunch − 使用 brunch 的应用程序应安装 terser-brunch 插件,然后调用生产构建以获得高效且性能良好的代码。

npm install --save-dev terser-brunch
brunch build -p

Rollup − 使用 rollup 的应用程序应安装 commonjs、replace 和 terser 插件并对其进行配置以获得最佳生产版本。

npm install --save-dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-terser

使用 React devtools − React 为所有浏览器提供开发工具作为扩展。一旦安装了扩展,浏览器的开发人员工具部分将显示一个专门用于 React 的部分。React 扩展提供的工具之一是 Profiler(React DevTool Profiler)。在将应用程序部署到生产环境之前,可以对应用程序进行分析和优化。

窗口技术 − 如果要在前端显示的数据非常庞大,则应用程序的性能将立即受到影响。一种方法是通过分页和其他类似技术仅显示一小部分数据。如果这些技术不可行,React 建议使用窗口技​​术,该技术将自动一次仅渲染一小部分数据。我们将在本章后面通过应用窗口技术来学习。

避免协调(shouldComponentUpdate) − 协调是一种提高 React 应用程序性能的强大技术。尽管如此,协调需要一些时间来运行并应用于我们的应用程序。跳过渲染(以及随后的协调)将提高性能。 React 提供了一个 API,shouldComponentUpdate,用于向 React 核心提示是否跳过或继续渲染。以下代码将跳过应用程序的渲染

shouldComponentUpdate(nextProps, nextState) {
   return false;
}

组件可以分析其当前状态和 props 及其更新状态,并决定是否可以跳过渲染部分。

纯组件 − 不要编写 shouldComponentUpdate,而是通过扩展 React.PureComponent 编写纯组件版本。如果给定的输入相同,纯组件通常会发出相同的输出。纯组件将进行浅层比较并跳过协调。但是,有一个问题。如果更改不浅,则 React 将跳过更新和渲染。要修复它,只需通过可见的突变完成更改即可,如下所示 −

// 非突变(错误的编码方式)
const words = this.state.words;
words.push('john');
this.setState({words: words});

// 突变版本(正确的编码方式)
this.setState(state => ({
   words: state.words.concat(['marklar'])
}));

这里,

  • 在代码的第一个版本中,对象没有发生变异。因此与旧对象的比较成功并跳过协调。

  • 在代码的第二个版本中,对象发生了变异,将在比较过程中被捕获。

应用窗口技术

让我们通过在本节中应用窗口技术创建一个新的 React 应用程序来呈现大量用户列表。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,使用以下命令安装 bootstrap 和 react-bootstrap 库,

npm install --save react-window

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。

// 删除所有 css 类

接下来,在 public 文件夹下创建一个文件 users.json,并填充以下用户信息,

[
   {
      "id":1,
      "name":"Fowler",
      "age":18
   },
   {
      "id":2,
      "name":"Donnell",
      "age":24
   },
   {
      "id":3,
      "name":"Pall",
      "age":26
   },
   {
      "id":4,
      "name":"Christos",
      "age":19
   },
   {
      "id":5,
      "name":"Dud",
      "age":29
   },
   {
      "id":6,
      "name":"Rayner",
      "age":22
   },
   {
      "id":7,
      "name":"Somerset",
      "age":31
   },
   {
      "id":8,
      "name":"Stavros",
      "age":32
   },
   {
      "id":9,
      "name":"Cody",
      "age":19
   },
   {
      "id":10,
      "name":"Sharai",
      "age":19
   },
   {
      "id":11,
      "name":"Kristo",
      "age":28
   },
   {
      "id":12,
      "name":"Harvey",
      "age":27
   },
   {
      "id":13,
      "name":"Christen",
      "age":27
   },
   {
      "id":14,
      "name":"Hillard",
      "age":19
   },
   {
      "id":15,
      "name":"Jaine",
      "age":32
   },
   {
      "id":16,
      "name":"Annabel",
      "age":29
   },
   {
      "id":17,
      "name":"Hildagarde",
      "age":29
   },
   {
      "id":18,
      "name":"Cherlyn",
      "age":18
   },
   {
      "id":19,
      "name":"Herold",
      "age":32
   },
   {
      "id":20,
      "name":"Gabriella",
      "age":32
   },
   {
      "id":21,
      "name":"Jessalyn",
      "age":32
   },
   {
      "id":22,
      "name":"Opal",
      "age":31
   },
   {
      "id":23,
      "name":"Westbrooke",
      "age":27
   },
   {
      "id":24,
      "name":"Morey",
      "age":22
   },
   {
      "id":25,
      "name":"Carleton",
      "age":26
   },
   {
      "id":26,
      "name":"Cosimo",
      "age":22
   },
   {
      "id":27,
      "name":"Petronia",
      "age":23
   },
   {
      "id":28,
      "name":"Justino",
      "age":32
   },
   {
      "id":29,
      "name":"Verla",
      "age":20
   },
   {
      "id":30,
      "name":"Lanita",
      "age":18
   },
   {
      "id":31,
      "name":"Karlik",
      "age":23
   },
   {
      "id":32,
      "name":"Emmett",
      "age":22
   },
   {
      "id":33,
      "name":"Abran",
      "age":26
   },
   {
      "id":34,
      "name":"Holly",
      "age":23
   },
   {
      "id":35,
      "name":"Beverie",
      "age":23
   },
   {
      "id":36,
      "name":"Ingelbert",
      "age":27
   },
   {
      "id":37,
      "name":"Kailey",
      "age":30
   },
   {
      "id":38,
      "name":"Ralina",
      "age":26
   },
   {
      "id":39,
      "name":"Stella",
      "age":29
   },
   {
      "id":40,
      "name":"Ronnica",
      "age":20
   },
   {
      "id":41,
      "name":"Brucie",
      "age":20
   },
   {
      "id":42,
      "name":"Ryan",
      "age":22
   },
   {
      "id":43,
      "name":"Fredek",
      "age":20
   },
   {
      "id":44,
      "name":"Corliss",
      "age":28
   },
   {
      "id":45,
      "name":"Kary",
      "age":32
   },
   {
      "id":46,
      "name":"Kaylee",
      "age":21
   },
   {
      "id":47,
      "name":"Haskell",
      "age":25
   },
   {
      "id":48,
      "name":"Jere",
      "age":29
   },
   {
      "id":49,
      "name":"Kathryne",
      "age":31
   },
   {
      "id":50,
      "name":"Linnea",
      "age":21
   },
   {
      "id":51,
      "name":"Theresina",
      "age":24
   },
   {
      "id":52,
      "name":"Arabela",
      "age":32
   },
   {
      "id":53,
      "name":"Howie",
      "age":22
   },
   {
      "id":54,
      "name":"Merci",
      "age":21
   },
   {
      "id":55,
      "name":"Mitchel",
      "age":30
   },
   {
      "id":56,
      "name":"Clari",
      "age":18
   },
   {
      "id":57,
      "name":"Laurena",
      "age":19
   },
   {
      "id":58,
      "name":"Odessa",
      "age":30
   },
   {
      "id":59,
      "name":"Pippy",
      "age":25
   },
   {
      "id":60,
      "name":"Wilmar",
      "age":23
   },
   {
      "id":61,
      "name":"Cherianne",
      "age":24
   },
   {
      "id":62,
      "name":"Huberto",
      "age":25
   },
   {
      "id":63,
      "name":"Ariella",
      "age":26
   },
   {
      "id":64,
      "name":"Lorant",
      "age":30
   },
   {
      "id":65,
      "name":"Francesca",
      "age":25
   },
   {
      "id":66,
      "name":"Ingamar",
      "age":28
   },
   {
      "id":67,
      "name":"Myrta",
      "age":27
   },
   {
      "id":68,
      "name":"Nicolette",
      "age":26
   },
   {
      "id":69,
      "name":"Petra",
      "age":22
   },
   {
      "id":70,
      "name":"Cyrill",
      "age":27
   },
   {
      "id":71,
      "name":"Ad",
      "age":23
   },
   {
      "id":72,
      "name":"Denys",
      "age":22

   },
   {
      "id":73,
      "name":"Karilynn",
      "age":23
   },
   {
      "id":74,
      "name":"Gunner",
      "age":30
   },
   {
      "id":75,
      "name":"Falkner",
      "age":20
   },
   {
      "id":76,
      "name":"Thurston",
      "age":19
   },
   {
      "id":77,
      "name":"Codi",
      "age":30
   },
   {
      "id":78,
      "name":"Jacob",
      "age":31
   },
   {
      "id":79,
      "name":"Gasparo",
      "age":26
   },
   {
      "id":80,
      "name":"Mitzi",
      "age":29
   },
   {
      "id":81,
      "name":"Rubetta",
      "age":21
   },
   {
      "id":82,
      "name":"Clary",
      "age":20
   },
   {
      "id":83,
      "name":"Oliviero",
      "age":24
   },
   {
      "id":84,
      "name":"Ranique",
      "age":21
   },
   {
      "id":85,
      "name":"Shae",
      "age":24
   },
   {
      "id":86,
      "name":"Woodrow",
      "age":20
   },
   {
      "id":87,
      "name":"Junia",
      "age":31
   },
   {
      "id":88,
      "name":"Athene",
      "age":26
   },
   {
      "id":89,
      "name":"Veriee",
      "age":18
   },
   {
      "id":90,
      "name":"Rickie",
      "age":30
   },
   {
      "id":91,
      "name":"Carly",
      "age":23
   },
   {
      "id":92,
      "name":"Vern",
      "age":19
   },
   {
      "id":93,
      "name":"Trix",
      "age":26
   },
   {
      "id":94,
      "name":"Lenore",
      "age":20
   },
   {
      "id":95,
      "name":"Hanna",
      "age":30
   },
   {
      "id":96,
      "name":"Dominique",
      "age":21
   },
   {
      "id":97,
      "name":"Karlotta",
      "age":22
   },
   {
      "id":98,
      "name":"Levey",
      "age":20
   },
   {
      "id":99,
      "name":"Dalila",
      "age":18
   },
   {
      "id":100,
      "name":"Launce",
      "age":21
   }
]

接下来,创建一个简单的用户列表组件 SimpleWindow (src/Components/SimpleWindow.js),并通过应用窗口功能呈现用户列表,如下所示 −

import React from "react";
import { FixedSizeList as List } from 'react-window';
class SimpleWindow extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         data: []
      };
   }
   componentDidMount() {
      fetch("users.json")
         .then((response) => response.json())
         .then((data) => {
            this.setState({
               data: data
            });
         });
   }
   render() {
      return (
         <List
            innerElementType="ul"
            itemData={this.state.data}
            itemCount={this.state.data.length}
            itemSize={35}
            width={500}
            height={300}
         >
         {
            ({data, index, style}) => {
               return (
                  <li style={style}>
                     {data[index].name}
                  </li>
               )
            }
         }
         </List>
      )
   }
}
export default SimpleWindow

这里我们有,

  • FixedSizeList 组件导入为 List。

  • 在 componentDidMount() 生命周期事件中使用 fetch 方法从 users.json url 获取用户列表。

  • 使用 FixedSizeList 组件渲染使用列表。

  • FixedSizeList 组件的 innerElementType 属性指的是要在组件内部生成的元素。

  • itemData、 itemCount 和 itemSize 指的是项目列表、可用项目的总数以及每个项目的大小。

接下来,打开 App 组件 (src/App.js),并包含 SimpleWindow 组件,如下所示 −

import './App.css'
import React from 'react';
import SimpleWindow from './Components/SimpleWindow'
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleWindow />
            </div>
         </div>
      </div>
   );
}
export default App;

这里,

  • 使用 import 语句导入我们的新组件 SimpleWindow

  • 渲染我们的新 SimpleWindow 组件。

最后,在浏览器中打开应用程序并检查最终结果。表格组件将呈现如下图所示 −

应用窗口技术

摘要

React 从基础开始优化应用程序。此外,React 库在每个版本中都改进了优化。除了这些优化之外,我们还可以遵循上面讨论的技术来从我们的角度提高性能。

ReactJS - Profiler API

分析是一种重要的技术,用于收集和显示特定函数在实时环境中执行所花费的时间。分析通常用于查找应用程序中的性能瓶颈。React 提供了两个选项来分析 React 应用程序

  • Profiler 组件

  • Profiler DevTools

Profiler 组件

React Profiler 组件只是另一个用于记录 React 组件的分析信息的 React 组件。Profiler 组件可以在 React 元素树中的任何位置使用。React 接受多个分析器和分析器的多级嵌套。本章让我们检查一下签名以及如何在 React 应用程序中应用 Profiler。

Profiler 组件的签名

Profiler 组件可以嵌套任何 React 组件,需要两个 props,

  • id

  • Profiler 组件的标识符

  • onRender 回调函数

  • 组件渲染每个阶段调用的回调函数

回调函数的签名如下 −

function onRenderCallback(
   id,
   phase,
   actualDuration,
   baseDuration,
   startTime,
   commitTime,
   interactions
){
   // 使用分析器信息执行任何操作
}

在控制台中记录分析数据的一个回调函数实现示例如下 −

const logProfilerData = (id, phase, actualTime, baseTime, startTime, commitTime) => {
   console.log(`${id}'s ${phase} phase:`);
   console.log(`Actual time: ${actualTime}`);
   console.log(`Base time: ${baseTime}`);
   console.log(`Start time: ${startTime}`);
   console.log(`Commit time: ${commitTime}`);
};

应用 Profiler

让我们创建一个新的 React 应用程序来学习如何在本节中应用 Profiler 组件。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。

// 删除所有 css 类

接下来,创建一个简单的 hello 组件 Hello (src/Components/Hello.js) 并呈现一条简单的消息,如下所示 −

import React from "react";
class Hello extends React.Component {
   constructor(props) {
      super(props)
   }
   render() {
      return (
         <div>Hello, {this.props.name}</div>
      );
   }
}
export default Hello;

这里,

  • 使用 name 属性来呈现具有给定名称的 hello 消息

接下来,打开 App 组件 (src/App.js),并使用分析器组件,如下所示 −

import './App.css'
import React, { Profiler } from 'react';
import Hello from './Components/Hello'
const logProfilerData = (id, phase, actualTime, baseTime, startTime, commitTime) => {
   console.log(`${id}'s ${phase} phase:`);
   console.log(`Actual time: ${actualTime}`);
   console.log(`Base time: ${baseTime}`);
   console.log(`Start time: ${startTime}`);
   console.log(`Commit time: ${commitTime}`);
};
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <Profiler id="helloWorld" onRender={logProfilerData}>
                  <Hello name="World" />
               </Profiler>
            </div>
         </div>
      </div>
   );
}
export default App;

这里,

  • 从 react 包中导入 Profiler 组件

  • 通过使用 Profiler 组件包装 Hello 组件来使用它

  • 创建一个回调函数 logProfilerData 并将所有分析器数据发送到控制台

  • 在 Profiler 组件的 onRender props 中设置 logProfilerData 回调函数

最后,在浏览器中打开应用程序并检查最终结果。它将呈现 hello 组件,如下所示 −

Applying Profiler

打开控制台,您可以看到分析器信息,如下所示 −

helloWorld's mount phase:
App.js:7 Actual time: 4.900000000372529
App.js:8 Base time: 1.800000000745058
App.js:9 Start time: 515.7999999988824
App.js:10 Commit time: 525.9000000003725
...
App.js:6 helloWorld's update phase:
App.js:7 Actual time: 1
App.js:8 Base time: 0.6999999992549419
App.js:9 Start time: 19836.900000000373
App.js:10 Commit time: 19838.400000000373

Profiler React DevTools

React DevTools 插件为分析器引入了一个单独的部分。开发人员可以打开"Profiler"选项卡并获得有关应用程序的大量有用见解。分析器 DevTools 中的一些可用功能如下 −

  • 浏览提交

  • 过滤提交

  • 火焰图

  • 排名图

  • 组件图

结论

React 分析器组件和分析器 devtools 是分析和优化 React 应用程序不可或缺的强大工具。

ReactJS - Portals

Portals 为组件提供了一种将其子组件渲染到其自身 DOM 层次结构之外的 DOM 节点中的方法。Portal 可用于模型对话框、弹出窗口、工具提示等,其中父组件(渲染组件)和子 DOM 节点(模型对话框)最好在不同的 DOM 节点中渲染。

让我们在本章中了解门户的工作原理以及如何在我们的应用程序中应用它。

门户的概念和用法

让我们假设主文档中有两个 DOM 节点,如下所示 −

<div id='root'></div>
<div id='modalRoot'></div>

此处,根 DOM 节点将与主 React 组件连接。React 应用程序需要显示模态对话框时,将使用 modalRoot,方法是将模态对话框附加到 modelRoot DOM 节点中,而不是在其自己的 DOM 元素内渲染模型对话框。

这将有助于将模态对话框与实际应用程序分开。将模态对话框与其父 DOM 元素分离将使其免受其父 DOM 元素样式的影响。样式可以单独应用,因为模态对话框、工具提示等在样式方面与其父元素不同。

React 在 ReactDOM 包中提供了一种特殊的方法 createPortal 来创建门户。方法签名如下 −

ReactDOM.createPortal(child, container)

这里,

  • child 是父组件渲染的模型对话框、工具提示等。

render() {
    return ReactDOM.createPortal(
        this.props.children, // 模态对话框/工具提示
        domNode // 组件外部的 dom
    );
}
  • container 是父 DOM 节点(上例中的 domNode)之外的 DOM 元素

应用门户

让我们创建一个新的 React 应用程序来学习如何在本节中应用门户。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类并包含模态对话框的 CSS。

.modal {
   position: absolute;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
   display: grid;
   justify-content: center;
   align-items: center;
   background-color: rgba(0,0,0,0.2);
}
.modalContent {
   padding: 20px;
   background-color: #fff;
   border-radius: 2px;
   display: inline-block;
   min-height: 300px;
   margin: 1rem;
   position: relative;
   min-width: 300px;
   box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
   justify-self: center;
}

接下来,打开 index.html (public/index.html) 并添加一个 DOM 节点来支持门户

<!DOCTYPE html>
<html lang="en">
   <head>
      <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
      <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
      <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
   </head>
   <body>
      <noscript>You need to enable JavaScript to run this app.</noscript>
      <div style="padding: 10px;">
         <div id="root"></div>
      </div>
      <div id="modalRoot"></div>
   </body>
</html>

接下来,创建一个简单的组件 SimplePortal (src/Components/SimplePortal.js) 并渲染一个模态对话框,如下所示 −

import React from "react";
import PortalReactDOM from 'react-dom'
const modalRoot = document.getElementById('modalRoot')
class SimplePortal extends React.Component {
   constructor(props) {
      super(props);
   }
   render() {
      return PortalReactDOM.createPortal(
         <div
            className="modal"
            onClick={this.props.onClose}
            >
            <div className="modalContent">
               {this.props.children}
               <hr />
               <button onClick={this.props.onClose}>Close</button>
            </div>
         </div>,
         modalRoot,
      )
   }
}
export default SimplePortal;

此处,

  • createPortal 创建新门户并呈现模态对话框。

  • 模态对话框的内容通过 this.props.children

  • 从组件的子项中检索。
  • 关闭按钮操作通过 props 处理,并将由父组件处理。

接下来,打开 App 组件 (src/App.js),并使用 SimplePortal 组件,如下所示 −

import './App.css'
import React from 'react';
import SimplePortal from './Components/SimplePortal'
class App extends React.Component {
   constructor(props) {
      super(props);
      this.state = { modal: false }
   }
   handleOpenModal = () => this.setState({ modal: true })
   handleCloseModal = () => this.setState({ modal: false })
   render() {
      return (
         <div className="container">
            <div style={{ padding: "10px" }}>
               <div>
                  <div><p>Main App</p></div>
                  <div>
                     <button onClick={this.handleOpenModal}>
                        Show Modal
                     </button>
                     { this.state.modal ? (
                        <SimplePortal onClose={this.handleCloseModal}>
                           Hi, I am the modal dialog created using portal.
                        </SimplePortal>
                     ) : null}
                  </div>
               </div>
            </div>
         </div>
      );
   }
}
export default App;

这里,

  • 导入SimplePortal组件

  • 添加了一个按钮来打开模态对话框

  • 创建了一个处理程序来打开模态对话框

  • 创建了一个处理程序来关闭模态对话框,并通过onClose props将其传递给SimplePortal组件。

最后,在浏览器中打开应用程序并检查最终结果。

ReactJS Portals

总结

React portal 提供了一种在组件外部访问和处理 DOM 的简便方法。它使事件在不同的 DOM 节点之间冒泡,而无需任何额外的努力。

ReactJS - 不使用 ES6 ECMAScript

根据 Ecma International,ECMAScript 是一种通用、与供应商无关且跨平台的编程语言。Ecma International 定义了 ECMAScript 语言语法、其特性和语言的各个方面,并将其发布为 ECMAScript 规范。JavaScript 是用于浏览器客户端编程的 ECMAScript 的流行实现之一。

ECMAScript 的最新规范是 ECMAScript 2022,最流行的规范是 ECMAScript 2015 语言规范,也称为 ES6。尽管几乎所有现代浏览器都支持 ES6,但旧版浏览器仍然缺乏对 ES6 的支持。现在,在客户端脚本中使用 ES6 功能被认为是安全的。

可以使用 ES6 语言规范开发 React 应用程序。React 库使用的一些核心 ES6 功能是 ES6 类和 ES6 模块。为了支持不允许 ES5 语法的旧版浏览器,React 提供了使用 ES5 创建组件的替代语法。

创建 React 类 (create-react-class)

create-react-class 是 React 社区提供的一个模块,用于在不使用 ES6 语法的情况下创建新组件。此外,我们应该在当前应用程序中安装 create-react-class 包以使用 ES5 语法。

让我们使用 create-react-app 创建一个 React 应用程序

create-react-app myapp

现在,将 create-react-class 包安装到我们新创建的应用程序中,如下所示 −

npm i create-react-class --save

现在,通过运行以下命令运行应用程序,

cd myapp
npm start

让我们看看如何使用 ES5(myapp/src/components/ES5/HelloWorldES6.js)和 ES6 语法(myapp/src/components/ES5/HelloWorldES6.js)创建一个简单的 hello world 组件,并了解使用时需要做什么ES5 语法。

ES6 语法中的 HelloWorldES6 组件如下 −

import React from 'react'
class HelloWorldES6 extends React.Component {
   render() {
      return <h1>Hello, {this.props.name}</h1>
   }
}
export default HelloWorldES6

可以使用以下代码以 ES5 语法创建相同的组件 (myapp/src/components/ES5/HelloWorldES5.js),如下所示 −

var createReactClass = require('create-react-class');
var HelloWorldES5 = createReactClass({
   render: function() {
      return <h1>Hello, {this.props.name}</h1>;
   }
});
export default HelloWorldES5;

现在,让我们在我们的应用程序(App.js)中使用该组件,如下所示 −

import HelloWorldES5 from "./components/ES5/HelloWorldES5";
import HelloWorldES6 from "./components/ES5/HelloWorldES6";
function App() {
   return (
      <div>
         <HelloWorldES5 name="Peter" />
         <HelloWorldES6 name="John" />
      </div>
   );
}
export default App;

应用程序的输出如下

Create React Class

设置 props 的默认值 (getDefaultProps)

让我们在 ES6 中为 name props 设置一个默认值。

class HelloWorld extends React.Component {
   render() {
      return <h1>Hello, {this.props.name}</h1>;
   }
}
HelloWorld.defaultProps = {
   name: 'John'
}

使用以下语法可以在 ES5 中实现相同的功能

var createReactClass = require('create-react-class');
var HelloWorld = createReactClass({
   render: function() {
      return <h1>Hello, {this.props.name}</h1>;
   },
   getDefaultProps: function() {
      return {
         name: 'John'
      };
   }
});

这里的getDefaultProps是一个特殊的函数,用来设置组件props的默认值。

设置初始状态(getInitialState)

我们知道,this.state可以在组件类的构造函数中使用,来设置状态的初始值。这是ES6类的特性之一。

import React from 'react'
class BookListES6 extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         list: ['C++ Programming', 'Java Programming']
      };
   }
   render() {
      return <ol>
         {this.state.list && this.state.list.map((item) =>
            <li>{item}</li>
         )}
      </ol>
   }
}
export default BookListES6

可以使用 ES5 语法中的 getInitialState 实现相同的功能,如下所示 −

var createReactClass = require('create-react-class');
var BookListES5 = createReactClass({
   getInitialState: function() {
      return {
         list: ['React Programming', 'Javascript Programming']
      };
   },
   render: function() {
      return <ol>
      {this.state.list && this.state.list.map((item) =>
         <li>{item}</li>
      )}
      </ol>
   }
});
export default BookListES5;

现在,让我们在我们的应用程序(App.js)中使用该组件,如下所示 −

import BookListES6 from "./components/ES5/BookListES6";
import BookListES5 from "./components/ES5/BookListES5";
function App() {
   return (
      <div>
         <BookListES6 />
         <BookListES5 />
      </div>
   );
}
export default App;

应用程序的输出如下

设置初始状态

事件处理程序的自动绑定

我们知道,React 组件类中定义的事件处理程序方法需要在类构造函数中绑定到此对象。伪代码如下

constructor(props) {
   super(props);
   this.state = {message: 'Hello!'};
   // binding of `this` object
   this.handleClick = this.handleClick.bind(this);
}

在 ES5 中,绑定不是必需的,因为函数默认绑定到 this 对象。

ReactJS - 不使用 JSX 的 React

在本章中,让我们学习如何使用 createElement 来创建 React 组件,而不是使用默认的 JSX。

什么是 JSX?

JSX 是一个 JavaScript 扩展,使用类似于 HTML 的语法创建任意 HTML 元素。这将简化 HTML 文档的创建,并使文档易于理解。React 将在执行 JSX 之前将其转换为由 React 的 createElement 函数调用组成的 JavaScript 对象。

它提高了应用程序的性能。此外,React 还允许使用纯 createElement 函数创建 HTML 文档,而无需 JSX。这使开发人员能够在 JSX 不太适合的情况下直接创建 HTML 文档。

什么是 createElement?

React.createElement 是用于生成和呈现 HTML 文档的核心 React API。 createElement 方法有三个参数,

  • 组件名称

  • props 作为对象

  • 组件的内部内容 / 子组件

此处,子组件可能引用另一个组件,同样使用 createElement 创建。createElement 可以嵌套到任何级别。使用 React.createElement 创建组件的示例代码如下 −

React.createElement('h1', null, 'Hello World')

这将呈现下面提到的 HTML 文档

<h1>Hello World</h1>

工作示例

我们来创建一个组件 BookListUsingCreateElement,以学习和理解 createElement 方法。

首先,使用 create-react-app 创建一个新应用程序

create-react-app myapp

然后,在 components 文件夹下添加一个新组件 BookListUsingCreateElement。初始代码如下 −

import React from 'react'
class BookListUsingCreateElement extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         list: ['C++ Programming', 'Java Programming', "JavaScript Programming"]
      };
   }
}
export default BookListUsingCreateElement

此处,list 是组件中可用的初始书籍集合。

现在,让我们使用 render 函数中的 createElement 来渲染书籍,如下所示。

render() {
   let content = React.createElement(
      'ol',
      null,
      this.state.list.map(
         (item) =>
         React.createElement('li', null, item)
      )
   )
   return content;
}

这里我们在两处使用了 createElement。首先,我们使用它来创建组件的最顶层,即 ul HTML 元素。其次,我们多次使用 createElement 根据列表中可用的书籍创建 li 元素。我们使用 map 函数循环遍历列表中的所有书籍,并使用 React.createElement('li', null, item) 代码为每本书创建一个 li 元素。

最后,组件的完整代码如下

import React from 'react'
class BookListUsingCreateElement extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         list: ['C++ Programming', 'Java Programming', "JavaScript Programming"]
      };
   }
   render() {
      let content = React.createElement(
         'ol',
         null,
         this.state.list.map(
            (item) =>
            React.createElement('li', null, item)
         )
      )
      return content;
   }
}
export default BookListUsingCreateElement

让我们通过 App.js 使用我们新创建的组件,如下所示 −

import BookListUsingCreateElement from "./components/CreateElement/BookListUsingCreateElement";
function App() {
   return (
      <div>
         <BookListUsingCreateElement />
      </div>
   );
}
export default App;

现在,使用以下命令运行应用程序

npm start

应用程序将在默认浏览器中启动并显示以下结果

ReactJS React Without JSX

ReactJS - Reconciliation

Reconciliation 是 React 库的一个内部过程。我们知道,React 应用程序会创建一个虚拟 DOM,然后根据虚拟 DOM 更新应用程序的实际 DOM。每当 React 收到更新请求时,它都会首先创建一个虚拟 DOM,然后使用不同的差异算法将虚拟 DOM 与之前的状态进行比较,然后只有在绝对必要时才会更新 DOM。

尽管差异算法和更新 DOM 是 React 核心内部的,但了解一些内部内容将有助于我们调整应用程序以最大限度地利用 React 库。

差异算法

让我们在本章中看看 React 核心应用的一些差异算法。

  • 相同类型的元素

  • 每当 React 组件将元素从一种类型更改为另一种类型(例如从 div 更改为更具体的 p)时,整个 React 虚拟 DOM 树都会发生变化并触发 DOM 更新。

<!-- Before -->
<div>
   <Content />
</div>
<!-- After -->
<p>
   <Content />
</p>

此时,整个元素将得到更新。

  • 相同类型的 DOM 属性。

  • 当元素类型相同时,react 将检查属性是否存在差异。如果 react 发现属性及其值有任何新变化,则它将仅更新已更改的属性。

<!-- Before -->
<div className="someClass">
   <Content />
</div>
<!-- After -->
<div className="someOtherClass">
   <Content />
</div>

此处,只有 DOM 实例的类属性会得到更新。

  • 相同类型的 DOM 属性(样式)。

  • 当元素属于相同类型并且 React 发现样式属性存在差异时,它将仅更新样式的属性。

<!-- Before -->
<div style={{fontFamily: 'Arial'}} />
   <p> ... </p>
</div>
<!-- After -->
<div style={{color: 'red', fontFamily: 'Arial'}} />
   <p> ... </p>
</div>

此处,只有 div 元素样式的颜色属性会被更新

  • 相同类型的组件元素 − 每当 React 看到相同的 React 组件类型时,它就会调用 componentWillUpdate 事件和 React 组件的其他更新事件,以便组件更新其状态。然后,它将调用组件的 render 方法,算法会递归

  • 相同类型的子元素集合 − 每当 React 看到相同类型的子元素集合时,它就会按顺序检查元素是否存在差异。因此,如果我们有一个新的第一个子元素,那么整个集合都会得到更新。

<!-- Before -->
<ul>
   <li>Peter</li>
   <li>Olivia</li>
</ul>
<!-- After -->
<ul>
   <li>John</li>
   <li>Peter</li>
   <li>Olivia</li>
</ul>

此处,由于第一个元素 (li) 是更新元素,因此整个元素 (ul 元素的子元素) 将得到更新。

为了解决这个问题,我们可以引入一个 key 属性,如下面的代码片段所示。React 为此有一个特殊的 key 属性。

<!-- Before -->
<ul>
   <li key="1">Peter</li>
   <li key="2">Olivia</li>
</ul>
<!-- After -->
<ul>
   <li key="3">John</li>
   <li key="1">Peter</li>
   <li key="2">Olivia</li>
</ul>

总结

React 尝试在每次发布中优化 diffing 算法,以确保更新最少。最少的更新意味着应用程序的性能更好。了解内部情况并遵循最佳实践进行相应的编码,我们可以成倍地提高应用程序的性能。

ReactJS - Refs 和 DOM

React 会在组件状态发生变化时自动发出 HTML 元素。这大大简化了 UI 开发,因为只需更新组件的状态即可。但是,传统上,直接访问 DOM 元素是更新组件 UI 的常态。

有时我们可能需要回退到直接访问 DOM 元素并更新 React 应用程序中的 UI。React ref 在此场景中提供帮助。它提供对 DOM 元素的直接访问。此外,它确保组件能够与 React Virtual DOM 和 HTML DOM 顺利配合使用。

React 提供了一个函数 createRef,用于在基于类的组件中创建 ref。本章我们来学习如何使用 createRef。

createRef 方法的签名

createRef 的目的是返回一个可变对象,该对象将在重新渲染之间持续存在。createRef 的签名如下 −

<refObj> = React.createRef()

这里,refObj 是Hooks(钩子)返回的对象

要自动将 DOM 对象附加到 refObj,应在元素的 ref props 中设置它,如下所示 −

<input ref={refObj} />

要访问附加的 DOM 元素,请使用 refObjcurrent 属性,如下所示 −

const refElement = refObj.current

应用 ref

让我们在本章中通过创建一个 React 应用程序来学习如何应用 createRef

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,在组件文件夹 (src/components/SimpleRef.js) 下创建一个 React 组件 SimpleRef

import React from "react";
class SimpleRef extends React.Component {
   render() {
      return (
         <div>Hello World</div>
      );
   }
}
export default SimpleRef;

接下来,打开 App.css (src/App.css) 并删除所有样式。然后,打开 App 组件 (src/App.js) 并使用我们新的 SimpleRef 组件更新内容,如下所示 −

import './App.css'
import SimpleRef from './Components/SimpleRef'
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleRef />
            </div>
         </div>
      </div>
   );
}
export default App;

接下来,向 SimpleRef 组件添加计数器功能,如下所示 −

import React from "react";
class SimpleRef extends React.Component {
   
   constructor(props) {
      super(props)
      this.state = {
         count: 0
      }
      this.handleClick = this.handleClick.bind(this);
   }
   handleClick() {
      this.setState(
         prevState => ({
            count: prevState.count + 1
         })
      )
   }
   render() {
      return (
         <div>
            <div>Counter: {this.state.count} <button onClick={this.handleClick}>+</button></div>
         </div>
      )
   }
}
export default SimpleRef;

这里我们有,

  • 使用 this.setState 来处理计数器状态变量 (count)。

  • 在 JSX 中呈现计数器状态变量

  • 添加一个按钮并附加一个点击处理程序事件 (this.handleClick),它将使用 this.setState 方法增加计数器

接下来,添加一个输入字段并根据用户在输入字段中输入的值显示问候消息,如下所示 −

import React from "react";
import { createRef } from "react";
class SimpleRef extends React.Component {
   constructor(props) {
      super(props)
      this.state = {
         count: 0
      }
      this.handleClick = this.handleClick.bind(this);
      this.handleChange = this.handleChange.bind(this);
      this.inputRef = createRef()
      this.labelRef = createRef()
   }
   handleClick() {
      this.setState(prevState => ({
         count: prevState.count + 1
      }))
   }
   handleChange() {
      this.labelRef.current.innerText =
      this.inputRef.current.value == "" ? "World" : this.inputRef.current.value
   }
   render() {
      return (
         <div>
            <div>Counter: {this.state.count} <button onClick={this.handleClick}>+</button></div>
            <div style={{ paddingTop: "5px"}}>
               <label>Enter your name: </label><input type="text" name="username"
                  ref={this.inputRef} onChange={this.handleChange}/>
               <br />
               <div>Hello, <span ref={this.labelRef}></span></div>
            </div>
         </div>
      )
   }
}
export default SimpleRef;

这里我们有,

  • 创建一个 ref,this.inputRef 来表示输入元素,并通过 ref props 将其附加到相关元素

  • 创建一个 ref,this.labelRef 来表示问候消息元素,并通过 ref props 将其附加到相关元素

  • 将事件处理程序 this.handleChange 附加到输入元素。事件处理程序使用 this.inputRef ref 获取问候消息,并使用 this.labelRef ref 更新消息

接下来,在浏览器中打开应用程序并输入您的姓名。应用程序将更新问候消息,如下所示。

Applying Ref

检查控制台,您会注意到组件未重新渲染。由于 react 仅在状态更改时重新渲染,而 ref 不会进行任何状态更改,因此组件未重新渲染。

接下来,单击 + 按钮。当状态(计数)发生变化时,它将通过重新渲染组件来更新计数器。如果仔细观察,您会发现消息保持不变。这种行为的原因是 ref 值在 react 渲染之间得到保留。

createRef 的用例

createRef 的一些用例如下 −

访问 JavaScript DOM API − JavaScript DOM API 提供了丰富的功能集来操作应用程序的 UI。当应用程序功能需要访问 JavaScript DOM API 时,可以使用 createRef 来检索原始 DOM 对象。检索到原始 DOM 对象后,应用程序可以使用 DOM API 访问所有功能。DOM API 的一些示例如下 −

  • 聚焦输入元素

  • 选择文本

  • 使用媒体播放 API 播放音频或视频

命令式动画 − Web 动画 API 通过命令式编程而非声明式编程提供了丰富的动画功能集。要使用 Web 动画 API,我们需要访问原始 DOM。

与第三方库集成 −由于第三方库需要访问原始 DOM 才能执行其功能,因此必须使用 createRef 从 react 获取 DOM 引用并将其提供给第三方库。

摘要

尽管 react 开发 UI 的方式简单易行,但基于 DOM API 开发 UI 在某些情况下有其自身的优势。createRef hook 非常适合这些场景,并提供简单干净的 API 来直接访问 DOM 元素,随后访问其 API。

ReactJS - 渲染道具

由于 React 组件是通过组合(一个组件包含另一个组件)而不是通过继承相互连接的,因此 React 组件中使用的逻辑不会直接共享给另一个组件。React 提供了多个选项来在组件之间共享逻辑,其中一个选项是渲染道具。渲染道具基本上是通过其道具将具有必要渲染逻辑的函数传递给具有核心功能的组件。因此,它被称为渲染道具。

让我们在本章中学习如何使用渲染道具。

如何使用渲染道具

让我们逐步了解如何使用渲染道具并在两个组件之间共享逻辑。让我们考虑从外部 URL 获取和呈现数据的场景。

  • 创建一个组件 FetcherComponent 来从外部 URL 获取数据,并创建一个 FetcherConsumerComponent 来使用数据并呈现它。

  • 创建一个组件 FetcherComponent,它具有针对给定 URL(props.url)的数据获取逻辑。

componentDidMount() {
   fetch(this.props.url)
   .then((response) => response.json())
   .then((data) => {
      this.setState({
         data: data
      });
   });
}

现在,更新 FetcherComponent,以便将核心渲染逻辑委托给 props(this.props.render)。

render() {
   return (
      <div>
         <h2>Fetch react component</h2>
         {this.state.data && this.props.render(this.state.data)}
      </div>
   )
}

此处,

  • this.props.render 是具有渲染逻辑的函数,任何其他组件都会通过其 props 将其传递到 FetcherComponent 中。

  • 创建一个组件 FetcherConsumerComponent,并通过传递获取的数据的渲染逻辑来渲染 FetcherComponent。

render() {
   return (<FetcherComponent url="users.json" render={(items) => (
      <ul>
         {items && items.length && items.map((item) =>
            <li key={item.id}>{item.name}</li>
         )}
      </ul>
   )} />)
}

这里,

  • items 是由 FetcherComponent 组件获取的数据。

  • 它们循环并发出 HTML 无序列表。

我们可以按照本节中定义的步骤,并在下一节中创建一个工作示例。

应用渲染道具

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。然后,创建一个组件 FetchRenderProps (src/Components/FetchRenderProps.js),其数据获取逻辑如下所示 −

import React from "react";
class FetchRenderProps extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         data: []
      }
   }
   componentDidMount() {
      fetch(this.props.url)
      .then((response) => response.json())
      .then((data) => {
         console.log(data)
         this.setState({
            data: data
         });
      });
   }
   render() {
      return (
         <div>
            <h2>Fetch react component</h2>
            {this.state.data && this.props.render(this.state.data)}
         </div>
      )
   }
}
export default FetchRenderProps;

这里我们有,

  • componentDidMount 事件中使用 fetch javascript 方法从外部 url 获取数据。

  • 使用通过 props 传递的 render 方法渲染获取的数据。

接下来,在 public 文件夹中创建一个文件 users.json (public/users.json) 来存储用户信息。我们将尝试使用 FetchRenderProps 组件获取它并在我们的应用程序中显示它。

[{"id":1,"name":"Fowler","age":18},
{"id":2,"name":"Donnell","age":24},
{"id":3,"name":"Pall","age":26}]

接下来,在 public 文件夹中创建一个文件 todo_list.json (public/todo_list.json),用于存储待办事项列表信息。我们将尝试使用 FetchRenderProps 组件获取它并在我们的应用程序中显示它。

[{"id":1,"title":"Learn JavaScript","is_done":true},
{"id":2,"title":"Learn React","is_done":true},
{"id":3,"title":"Learn Typescript","is_done":false

接下来,创建一个组件 SimpleRenderProps (src/Components/SimpleRenderProps.js) 来使用 FetchRenderProps 组件,如下所示 −

import React from "react";
import FetchRenderProps from "./FetchRenderProps";
class SimpleRenderProps extends React.Component {
   render() {
      return (
         <>
            <FetchRenderProps url="users.json" render={(items) => (
               <ul>
                  {items && items.length && items.map((item) =>
                     <li key={item.id}>{item.name}</li>
                  )}
               </ul>
            )} />
            <FetchRenderProps url="todo_list.json" render={(items) => (
               <ul>
                  {items && items.length && items.map((item) =>
                     <li key={item.id}>{item.title} {item.is_done && <strong>Done</strong>}</li>
                  )}
               </ul>
            )} />
         </>
      )
   }
}
export default SimpleRenderProps;

这里我们有,

  • 使用 FetchRenderProps 和 users.json 来获取和呈现用户列表

  • 使用 FetchRenderPropstodo_list.json 来获取和呈现待办事项列表

  • 获取用户和待办事项列表使用相同的 FetchRenderProps 组件。

接下来,打开 App.js 文件并呈现 SimpleRenderProps 组件,如下所示 −

import './App.css'
import React from 'react';
import SimpleRenderProps from './Components/SimpleRenderProps'
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleRenderProps />
            </div>
         </div>
      </div>
   );
}
export default App;

最后,在浏览器中打开应用程序并检查最终结果。应用程序将呈现如下图所示 −

应用渲染属性

摘要

渲染属性是一种在组件之间共享逻辑的有效方法。它在许多第三方组件中被广泛使用,成功率很高,并且是经过时间考验的在 React 域中共享逻辑的方法。

ReactJS - 静态类型检查

由于 JavaScript 是一种动态类型语言,因此在运行代码之前很难发现类型不匹配错误。React 通过 prop-types 包支持对 props 进行类型检查,可用于在开发阶段识别属性的类型不匹配。

程序的另一个方面仍然需要一个工具来正确识别开发阶段的类型问题。JavaScript 有很多静态类型检查工具来处理类型问题。我们将检查两个流行的选项,它们将顺利集成到 React 应用程序的工作流程中,并在应用程序的开发阶段提示可能出现的类型错误。它们如下 −

  • Flow

  • TypeScript 语言

Flow

Flow 是 JavaScript 的静态类型检查器。Flow 扩展了 JavaScript 语言以指定类型,并允许在 JavaScript 代码中设置静态类型注释。 Flow 会检查开发者在代码中设置的静态类型注解,确保使用了正确的类型,否则会抛出错误。简单示例如下 −

// @flow
function sum(a: number, b: number) : number {
   return a + b;
}
sum(10, 20)     // Pass
sum("10", "20") // Fail

此处,//@flow 注释使 flow 静态检查器能够分析下面定义的函数。如您所见,函数 sum 使用 Flow 语言扩展来指定参数的类型。让我们看看如何在 React 项目中启用 Flow,以及启用 Flow 的 React 项目的开发工作流程。

步骤 1 − 使用 create-react-app CLI 应用创建一个新的 React 项目。

create-react-app myapp

步骤 2 − 使用以下命令将 Flow 添加到项目中

cd myapp
npm install --save-bin flow-bin

步骤 3 − 现在,在 package.json 文件的脚本中添加 Flow 命令

{
   // ...
   "scripts": {
      "flow": "flow",
      // ...
   },
   // ...
}

这将允许我们通过 npm 运行 flow 命令

步骤 4 − 使用 flow 命令初始化 flow 配置,如下所示 −

npm run flow init

这将在项目根目录中创建一个包含以下内容的基本 flow 配置文件 .flowconfig。

[ignore]
[include]
[libs]
[lints]
[options]
[strict]

可在此处添加高级流程选项。默认情况下,流程将检查我们应用程序中的所有文件。要忽略 node_modules,请在 [ignore] 选项下添加 .*/node_modules/.*。这将指示流程应用程序忽略 node_modules 文件夹内的所有文件。

[ignore]
.*/node_modules/.*
[include]
[libs]
[lints]
[options]
react.runtime=automatic
[strict]

步骤 5 − 现在,我们已经将流程配置到我们的应用程序中。我们可以将流程注释到我们的代码中,并使用以下命令针对流程进行测试

npm run flow

Flow 将检查我们的代码并在控制台中显示类似的结果,如下所示 −

> myapp@0.1.0 flow /path/to/myapp
> flow
Launching Flow server for /path/to/myapp
Spawned flow server (pid=1629)
Logs will go to /private/tmp/flow/zSUserszSbalazSProjectszSArticleszSreact-revision-v2zSworkspacezSmyapp.log
Monitor logs will go to /private/tmp/flow/zSUserszSbalazSProjectszSArticleszSreact-revision-v2zSworkspacezSmyapp.monitor_log
No errors!

步骤 6 − 现在,我们可以在代码中使用 flow 注释了。让我们在代码中添加一个简单的 flow 注释,然后运行 ​​flow 命令来检查代码的正确性。创建一个简单的 HelloWorld 组件 (src/components/HelloWorld.js),如下所示 −

import React from 'react'
class HelloWorld extends React.Component {
   render() {
      return <h1>Hello, {this.props.name}</h1>
   }
}
export default HelloWorld

步骤 7 − 将该组件包含在我们的根组件(App.js)中,如下所示 −

// @flow
import React from "react";
import HelloWorld from "./components/HelloWorld";
function App() : any {
   var name: string = 10
   return (
      <div>
         <HelloWorld name={name} />
      </div>
   )
}
export default App;

步骤 8 − 现在,使用 flow 检查代码,如下所示

npm run flow

Flow 命令将检查代码并显示错误,即名称设置为字符串值,如下所示。

> myapp@0.1.0 flow /path/to/myapp
> flow
Error ............................................src/App.js:6:22
Cannot assign 10 to name because number [1] is incompatible with string [2]. [incompatible-type]
      3│ import HelloWorld from "./components/HelloWorld";
      4│
      5│ function App() : any {
[2][1]6│   var name: string = 10
      7│
      8│   return (
      9│     <div>
Found 1 error
.....
.....

步骤 9 − 让我们通过为 name 变量提供字符串值来修复错误并重新运行 flow 命令。

// @flow
import React from "react";
import HelloWorld from "./components/HelloWorld";
function App() : any {
   var name: string = "John"
   return (
      <div>
         <HelloWorld name={name} />
      </div>
   )
}
export default App;

现在,flow 命令将成功执行,并显示代码中没有问题。

> myapp@0.1.0 flow /path/to/myapp
> flow
No errors!

步骤 10 − 最后,我们可以通过运行以下命令来运行应用程序,

npm start

可以使用以下命令创建优化的生产版本,

npm run build

TypeScript

TypeScript 是一种对静态类型具有一流支持的语言。静态类型使 TypeScript 能够在编译时而不是运行时捕获类型错误。 TypeScript 支持 JavaScript 的所有语言特性。

因此,JavaScript 开发人员使用 TypeScript 非常容易。React 内置了对 TypeScript 的支持。要创建 React 项目,只需在通过 create-react-app 创建 React 应用时包含 TypeScript 模板即可。

create-react-app myapp --template typescript

创建应用后,在 src/components 文件夹下添加一个新的 HelloWorld 组件 (HelloWorld.tsx),如下所示 −

// src/components/HelloWorld.tsx
import React from 'react'
class HelloWorld extends React.Component {
   render() {
      return <h1>Hello, {this.props.name}</h1>
   }
}
export default HelloWorld

现在,包含 HelloWorld 组件 −

import React from "react";
import HelloWorld from "./components/HelloWorld";
function App() : any {
   var name: string = 10
   return (
      <div>
         <HelloWorld name={name} />
      </div>
   )
}
export default App;

这里,我们有意将 name 的值设置为 10。让我们运行应用程序并检查编译器是否捕获错误。

npm start

运行命令会引发错误,如下所示 −

...
...
ERROR in src/App.tsx:5:7
TS2322: Type 'number' is not assignable to type 'string'.
     3 |
     4 | function App() : any {
   > 5 |   var name: string = 10
       |       ^^^^
     6 |
     7 |   return (
     8 |     <div>
...
...

让我们更改 name 的值并设置一个字符串值("John")。上述错误已修复,但编译器仍然在 HelloWorld 组件中抛出错误,如下所示 −

Issues checking in progress...
ERROR in src/App.tsx:9:19
TS2769: No overload matches this call.
   Overload 1 of 2, '(props: {} | Readonly<{}>): HelloWorld', gave the following error.
      Type '{ name: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<HelloWorld> & Readonly<{}>'.
         Property 'name' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<HelloWorld> & Readonly<{}>'.
   Overload 2 of 2, '(props: {}, context: any): HelloWorld', gave the following error.
      Type '{ name: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<HelloWorld> & Readonly<{}>'.
         Property 'name' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<HelloWorld> & Readonly<{}>'.
     7 |   return (
     8 |     <div>
> 9 |       <HelloWorld name={name} />
       |                   ^^^^
    10 |     </div>
    11 |   )
    12 | }
ERROR in src/components/HelloWorld.tsx:9:39
TS2339: Property 'name' does not exist on type 'Readonly<{}>'.
     7 | class HelloWorld extends React.Component {
     8 |     render() {
> 9 |        return <h1>Hello, {this.props.name}</h1>
       |                                       ^^^^
    10 |     }
    11 | }
    12 |

为了修复错误,应为 HelloWorld 组件的属性提供类型信息。为属性创建一个新的接口,然后将其包含在 HelloWorld 组件中,如下所示 −

import React from 'react'
interface HelloWorldProps {
   name: string
}
class HelloWorld extends React.Component<HelloWorldProps> {
   render() {
      return <h1>Hello, {this.props.name}</h1>
   }
}
export default HelloWorld

最后,我们的代码编译成功,没有任何错误,如下所示,可以通过 `http://localhost:3000/

查看。
No issues found.
Static Type Checking

ReactJS - 严格模式

React 是基于组件、属性和状态的概念构建的。React 提供了一组相对较少的 API 来创建和更新组件。React 的基本功能足以满足我们大部分前端应用程序的需求。但是,现代应用程序仍然具有复杂的场景,需要高级前端逻辑。React 提供了许多高级 API,这将有助于我们创建复杂的前端应用程序。高级 API 是有代价的。高级 API 在我们的应用程序中学习和应用起来有点困难。

React 提供了一种严格模式,它通过突出显示应用程序开发阶段的潜在问题来帮助开发人员正确应用高级 API。众所周知,React 中的一切都是建立在组件概念之上的,严格模式只是一个非可视化组件,React.StrictMode。严格模式组件的使用也非常简单。只需用 React.StrictMode 组件包装要分析的组件,如下所示 −

<React.StrictMode>
   <OurComplexComponent />
</React.StrictMode>

严格模式的另一个功能是,当应用程序中使用某些遗留或弃用的 API(最终会在应用程序中引入错误)时抛出错误或警告。

让我们学习本章中严格模式组件突出显示的项目列表。

不安全的生命周期使用

React 确定一些遗留生命周期事件在基于异步的应用程序中不安全使用。它们如下 −

  • componentWillMount

  • componentWillReceiveProps

  • componentWillUpdate

不安全意味着它会在应用程序中创建难以调试的错误。React 最终将在库的未来版本中删除这些不安全的生命周期事件。在此之前,开发人员在使用旧版不安全生命周期事件时会收到警报。

旧版 ref API 用法

React 的早​​期版本使用基于字符串的 ref 管理,后来添加了基于回调的 ref 管理。建议使用基于回调的选项,因为它稳定且不易出错。如果我们使用基于字符串的选项,严格模式将发出警告。

React 的最新版本提供了改进的 ref 管理选项,它既易于编码又不易出错。

旧版 findDOMNode 用法

findDOMNode 有助于根据类实例搜索 DOM 节点树。 findDOMNode 的使用已弃用,并提倡使用基于 ref 的 DOM 管理。

副作用

React 有两个主要阶段,rendercommit。render 阶段涉及大量工作且耗时,而 commit 阶段则简单快捷。React 引入了并发模式,这将提高 render 阶段的性能。并发模式的要求之一是 render 阶段中使用的生命周期事件不应包含副作用。

React 将在开发阶段尝试使用不同的方法查找意外的副作用并将其报告为警告。

旧版上下文 API

旧版上下文 API 容易出错,不建议使用。它将在未来版本中被删除。在此之前,严格模式将检测旧上下文 API 的使用情况并报告。

可重用状态

具有可重用状态的组件可以多次挂载和销毁,不会产生任何副作用,并有助于提高应用程序的性能。React 库的未来版本将在保留状态的同时添加或删除 UI 部分。React 18 引入了严格模式功能,它将尝试卸载并重新挂载组件以确保组件具有弹性。此阶段的任何问题都将被报告。

ReactJS - Web 组件

React 和 Web 组件可以在 Web 应用程序中混合使用。React 组件可以有一个或多个 Web 组件,Web 组件可以使用 React 组件来创建其内容。React 支持这两种选项。

在 React 应用程序中使用 Web 组件

让我们创建一个 Web 组件,然后尝试将其应用于 React 应用程序中。首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。

// 删除所有 css 类

接下来,创建一个简单的 Web 组件 HelloMessage (public/WebComponents/HelloMessage.js) 并添加以下代码。Web 组件 () 的目的是欢迎用户(通过在 Web 组件的 name 属性中指定用户名)。

// Web 组件
class HelloMessage extends HTMLElement {
   constructor() {
      super();
      this.name = 'Folks';
   }
   static get observedAttributes() {
      return ['name'];
   }
   attributeChangedCallback(property, oldValue, newValue) {
      if (oldValue === newValue) return;
      this[property] = newValue;
   }
   connectedCallback() {
      this.textContent = `Hello ${this.name}!`;
   }
}
customElements.define('hello-message', HelloMessage);

此处,

  • connectedCallback() 用于创建 Web 组件的内容。

  • observedAttributes 函数访问名称属性。

  • attributeChangedCallback 函数更新名称属性的值(如果在应用程序中发生更改)。

  • customElements.define 用于将创建的带有标签名称的 Web 组件附加到 Web 文档中。

接下来,打开 index.html (public/index.html) 文件并添加 Web 组件,如下所示 −

<!DOCTYPE html>
<html lang="en">
   <head>
      <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
      <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
      <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
      <script src="%PUBLIC_URL%/WebComponents/HelloMessage.js"></script>
   </head>
   <body>
      <noscript>You need to enable JavaScript to run this app.</noscript>
      <div style="padding: 10px;">
         <div id="root"></div>
         <div style="padding: 10px;">
            <hello-message name="John"></hello-message>
         </div>
      </div>
   </body>
</html>

这里我们有,

  • 在 head 部分包含 Web 组件

  • 在页面中使用 hello-message Web 组件来展示它的用法

接下来,创建一个简单的组件 SimpleWebComponent (src/Components/SimpleWebComponent.js) 并呈现新创建的 Web 组件,如下所示 −

import React from "react";
class SimpleWebComponent extends React.Component {
   constructor(props) {
      super(props)
   }
   render() {
      return (
         <hello-message name="Peter"></hello-message>
      );
   }
}
export default SimpleWebComponent;

这里,组件的 render 方法中使用了 Web 组件 hello

接下来,打开 App 组件 (src/App.js),并使用 SimpleWebComponent 组件,如下所示 −

import './App.css'
import React from 'react';
import SimpleWebComponent from './Components/SimpleWebComponent'
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleWebComponent />
            </div>
         </div>
      </div>
   );
}
export default App;

这里我们有,

  • 从 react 包中导入 SimpleWebComponent 组件

  • 使用 SimpleWebComponent 组件并渲染 hello web 组件

最后,在浏览器中打开应用程序并检查最终结果。

Web Components React Application

总结

React 和 web 组件以很好的方式相互补充。每个都有自己的优点和缺点,可以通过分析其相对于应用程序的优缺点在单个应用程序中使用。

ReactJS - 日期选择器

React 通过第三方 UI 组件库提供表单组件。React 社区提供了大量的 UI / UX 组件,很难根据我们的需求选择合适的库。

Bootstrap UI 库是开发人员的热门选择之一,被广泛使用。 React Bootstrap (https://react-bootstrap.github.io/) 已将几乎所有 bootstrap UI 组件移植到 React 库,并且它对 date picker 组件也提供了最佳支持。

在本章中,让我们学习如何使用 react-bootstrap 库中的 date picker 组件。

什么是日期选择器?

日期选择器 允许开发人员轻松选择日期,而不必通过文本框输入正确的格式详细信息。HTML 输入元素具有类型属性,用于引用要输入到元素中的数据类型。其中一种类型是日期。在输入元素中设置类型将启用日期选择器。

<input type="date">

React bootstrap 提供 Form.Control 组件来创建各种输入元素。开发人员可以使用它来创建日期选择器控件。Form.Control 的一些有用属性如下 −

  • ref (ReactRef) − Ref 元素用于访问底层 DOM 节点

  • as (elementType) − 启用以指定除 *<input>* 之外的元素

  • disabled (boolean) −启用/禁用控制元素

  • htmlSize(数字) − 底层控制元素的大小

  • id(字符串) − 控制元素的 ID。如果未在此处指定,则使用父 *Form.Group* 组件的 *controlId*。

  • IsInValid(布尔值) − 启用/禁用与无效数据相关的样式

  • IsValid(布尔值) − 启用/禁用与有效数据相关的样式

  • plaintext(布尔值) − 启用/禁用输入并将其呈现为纯文本。与 *readonly* 属性一起使用

  • readOnly(布尔值) −启用/禁用控件的只读属性

  • size (sm | lg) − 输入元素的大小

  • type (string) − 要呈现的输入元素的类型

  • value (string | arrayOf | number) − 底层控件的值。由 *onChange* 事件操纵,初始值将默认为 *defaultValue* props

  • bsPrefix (string) − 用于自定义底层 CSS 类的前缀

  • onChange (boolean) − 触发 *onChange* 事件时要调用的回调函数

可以使用一个简单的日期控件组件,如下所示 −

<Form.Group className="mb-3" controlId="password">
   <Form.Label>Date of birth</Form.Label>
   <Form.Control type="date" />
</Form.Group>

应用 Date picker 日期选择器 组件

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,使用以下命令安装 bootstrap 库,

npm install --save bootstrap react-bootstrap

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。

// 删除 css 类

接下来,创建一个简单的日期组件 SimpleDatePicker (src/Components/SimpleDatePicker.js) 并呈现一个表单,如下所示 −

import { Form, Button } from 'react-bootstrap';
function SimpleDatePicker() {
   return (
      <Form>
         <Form.Group className="mb-3" controlId="name">
            <Form.Label>Name</Form.Label>
            <Form.Control type="name" placeholder="Enter your name" />
         </Form.Group>
         <Form.Group className="mb-3" controlId="password">
            <Form.Label>Date of birth</Form.Label>
            <Form.Control type="date" />
         </Form.Group>
         <Button variant="primary" type="submit">
            Submit
         </Button>
      </Form>
   );
}
export default SimpleDatePicker;

这里我们有,

  • 使用 Form.Control 和日期类型创建日期选择器控件。

  • 使用 Form component 创建基本表单组件。

  • 使用 Form.Group 对表单控件和标签进行分组。

接下来,打开 App 组件 (src/App.js),导入 bootstrap css 并呈现日期选择器,如下所示 −

import './App.css'
import "bootstrap/dist/css/bootstrap.min.css";
import SimpleDatePicker from './Components/SimpleDatePicker'
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleDatePicker />
            </div>
         </div>
      </div>
   );
}
export default App;

这里,

  • 使用 import 语句导入 bootstrap 类

  • 渲染我们新的 SimpleDatePicker 组件。

  • 包含 App.css 样式

最后,在浏览器中打开应用程序并检查最终结果。日期选择器将呈现如下图所示 −

Prop Types

摘要

React Bootstrap 日期选择器组件提供了创建日期选择器表单控件所需的选项。

ReactJS - Helmet

Web 文档的元信息对于 SEO 目的非常重要。文档的元信息通常使用 meta 标签在头部内指定。title 标签在提供有关文档的元信息方面也起着重要作用。头部部分也会有脚本和样式标签。Helmet 组件通过提供所有有效的头部标签,提供了一种管理文档头部部分的简便方法。 Helmet 将收集其中指定的所有信息并更新文档的 head 部分。

让我们在本章中学习如何使用 Helmet 组件。

安装 Helmet

在了解 Helmet 的概念和用法之前,让我们先学习如何使用 npm 命令安装 Helmet 组件。

npm install --save react-helmet

上述命令将安装 Helmet 组件,并准备在我们的应用程序中使用。

Helmet 的概念和用法

Helmet 接受所有有效的 head 标签。它接受纯 HTML 标签并在文档的 head 部分输出标签,如下所示 −

import React from "react";
import {Helmet} from "react-helmet";
class Application extends React.Component {
   render () {
      return (
         <div className="application">
            <Helmet>
               <title>Helmet sample application</title>
               <meta charSet="utf-8" />
               <meta name="description" content="Helmet sample program to understand the working of the helmet component" />
               <meta name="keywords" content="React, Helmet, HTML, CSS, Javascript" />
               <meta name="author" content="Peter" />
               <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            </Helmet>
            // ...
         </div>
      );
   }
};

此处,

  • title用于指定文档的标题

  • description元标记用于指定文档的详细信息

  • keywords用于指定文档的主要关键字。它将被搜索引擎使用。

  • author用于指定文档的作者

  • viewport用于指定文档的默认视口

  • charSet用于指定文档中使用的字符集。

head 部分的输出将如下所示 −

<head>
   <title>Helmet sample application</title>
   <meta charSet="utf-8" />
   <meta name="description" content="Helmet sample program to understand the working of the helmet component" />
   <meta name="keywords" content="React, Helmet, HTML, CSS, Javascript" />
   <meta name="author" content="Peter" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>

Helmet 组件可以在任何其他 React 组件内部使用,以更改标题部分。它也可以嵌套,这样当子组件被渲染时,标题部分就会发生变化。

<Parent>
   <Helmet>
      <title>Helmet sample application</title>
      <meta charSet="utf-8" />
      <meta name="description" content="Helmet sample program to understand the working of the helmet component" />
      <meta name="keywords" content="React, Helmet, HTML, CSS, Javascript" />
      <meta name="author" content="Peter" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   </Helmet>
   <Child>
      <Helmet>
         <title>Helmet sample application :: rendered by child component</title>
         <meta name="description" content="Helmet sample program to explain nested feature of the helmet component" />
      </Helmet>
   </Child>
</Parent>

此处,

  • 子组件中的头盔将更新头部部分,如下所示,

<head>
   <title>Helmet sample application :: rendered by child component</title>
   <meta charSet="utf-8" />
   <meta name="description" content="Helmet sample program to explain nested feature of the helmet component" />
   <meta name="keywords" content="React, Helmet, HTML, CSS, Javascript">
   <meta name="author" content="Peter">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>

应用 Helmet

让我们创建一个新的 React 应用程序来学习如何在本节中应用 Helmet 组件。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。

// 删除所有 css 类

接下来,创建一个简单的组件 SimpleHelmet (src/Components/SimpleHelmet.js) 并渲染一个 −

import React from "react";
import {Helmet} from "react-helmet";
class SimpleHelmet extends React.Component {
   render() {
      return (
         <div>
            <Helmet>
               <title>Helmet sample application</title>
               <meta charSet="utf-8" />
               <meta name="description" content="Helmet sample program to understand the working of the helmet component" />
               <meta name="keywords" content="React, Helmet, HTML, CSS, Javascript" />
               <meta name="author" content="Peter" />
               <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            </Helmet>
            <p>A sample application to demonstrate helmet component.</p>
         </div>
      )
   }
}
export default SimpleHelmet;

这里我们有,

  • 从 react-helmet 包导入 Helmet

  • 在组件中使用 Helmet 来更新 head 部分。

接下来,打开 App 组件 (src/App.js),并使用 SimpleHelmet 组件,如下所示 −

import './App.css'
import React from 'react';
import SimpleHelmet from './Components/SimpleHelmet'

function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleHelmet />
            </div>
         </div>
      </div>
   );
}
export default App;

这里我们有,

  • 从 react 包中导入 SimpleHelmet 组件

  • 使用 SimpleHelmet 组件

接下来,打开 index.html (public/index.html) 文件并删除元标记,如下所示 -

<!DOCTYPE html>
<html lang="en">
<head>
   <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
   <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
   <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
</head>
<body>
   <noscript>You need to enable JavaScript to run this app.</noscript>
   <div style="padding: 10px;">
      <div id="root"></div>
   </div>
</body>
</html>

这里,

  • title标签被移除

  • description、theme-color、viewport的meta标签被移除

最后在浏览器中打开应用,查看最终效果。

Applying Helmet

在开发工具中打开源码,可以看到如下图所示的html信息 −

<title>Helmet sample application</title>
<meta charset="utf-8" data-react-helmet="true">
<meta name="description" content="Helmet sample program to understand the working of the helmet component" data-react-helmet="true">
<meta name="keywords" content="React, Helmet, HTML, CSS, Javascript" data-react-helmet="true">
<meta name="author" content="Peter" data-react-helmet="true">
<meta name="viewport" content="width=device-width, initial-scale=1.0" data-react-helmet="true">

摘要

Helmet 组件是一个易于使用的组件,用于管理文档的头部内容,支持服务器端和客户端渲染。

ReactJS - 内联样式

React 提供了一种独特的方式,可以直接在 React 组件中编写 CSS 并在 JSX 中使用它。这个概念在 JS 中称为 CSS,与传统的样式用法相比,它具有许多优势。

让我们了解什么是内联样式以及如何在 React 组件中使用它。

内联样式的概念

CSS 使开发人员能够设计 Web 应用程序的 UI。React 为 CSS 提供一流的支持,并允许将 CSS 直接导入 React 应用程序。将 CSS 直接导入 React 组件就像导入包一样简单。

import './App.css'

但是,将 css 直接导入 Web 组件有一个主要缺点,即全局命名空间。如果类名存在冲突,全局样式可能会影响单个组件的样式。开发人员需要小心分配一些前缀以确保不会发生冲突。

另一种方法是允许 Javascript 管理 CSS,这称为 JS 中的 CSS。React 允许通过特殊的 CSS 语法在 JSX 中使用 CSS。React 为每个组件提供了一个样式 props,可用于指定内联样式。内联样式应在 Javascript 对象中提供。对象应遵循以下规则,

  • 对象键应为普通 CSS 属性的 CamelCased 版本。例如,background-color 应指定为 backgroundColor

{
    backgroundColor: "red"
}
  • 对象值应为 CSS 中相应对象键的允许值之一,且应为字符串格式。例如,font-size CSS 属性及其值 (12px) 应指定如下 −

{
   fontSize: "12px"
}

React 将处理冲突并正确呈现应用程序。

应用内联样式

让我们在本节中学习如何在 React 应用程序中应用内联样式。

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。

// 删除 css

接下来,创建一个简单的组件 SimpleStyle (src/Components/SimpleIcon.js),如下所示 −

import React from "react";
class SimpleStyle extends React.Component {
   displayStyle = {
      fontFamily: 'Times New Roman',
      fontSize: "24px",
      color: "red"
   }
   render() {
      return (
         <div>
            <div style={this.displayStyle}>
               Sample text to understand inline style (object as variable) in React component
            </div>
            <hr />
            <div style={{ fontFamily: 'Arial', fontSize: "24px", color: "grey"}}>
               Sample text to understand inline style (object as expression) in React component
            </div>
         </div>
      )
   }
}
export default SimpleStyle

这里我们有,

  • 使用变量 (displayStyle) 为第一个 div 设置样式。

  • 使用表达式为第二个 div 设置样式。

接下来,打开 App 组件 (src/App.js) 并使用 SimpleStyle 组件更新内容,如下所示 −

import './App.css'
import React from 'react';
import SimpleStyle from './Components/SimpleStyle'

function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleStyle />
            </div>
         </div>
      </div>
   );
}
export default App;

这里我们有,

  • 导入了 SimpleStyle 组件。

  • 使用 SimpleStyle 组件来呈现日历图标。

最后,在浏览器中打开应用程序。内容将呈现如下图所示 −

Inline Style

摘要

内联样式可帮助开发人员快速包含 CSS 样式,而无需担心 CSS 样式冲突。此外,语法与 CSS 非常相似,开发人员可以轻松使用该功能,而无需太多学习曲线。

ReactJS - PropTypes

JavaScript 是一种动态类型语言。这意味着 JavaScript 不需要声明或指定变量的类型。在程序执行(运行时)期间,JavaScript 检查分配给变量的值,然后推断变量的类型。例如,如果将变量 num 分配给 John,则推断 num 的类型为字符串。如果将同一变量 num 分配给 10,则推断 num 的类型为数字。

var num = 'John' // `num` 的类型为字符串
var num = 10 // `num` 的类型为数字

动态类型适用于脚本语言,因为它可以加快开发速度。动态类型的另一面是 JavaScript 引擎在开始执行程序之前不知道变量的类型。这会阻止 JavaScript 引擎查找或识别某些错误。例如,下面提到的代码不会执行并停止程序。

var num = 10
var name = 'John'
var result = num + name // 在运行时抛出错误

React 和类型

在 React 中,每个组件都有多个 props,每个 props 都应分配给具有正确类型的值。具有错误类型的组件 props 将引发意外行为。为了更好地理解问题,让我们创建一个新的应用程序并创建一个组件,该组件总结其属性并显示结果。

要创建一个新的 React 应用程序,请执行以下命令,

create-react-app myapp

创建应用程序后,在组件文件夹 (src/components/PropTypes/Sum.js) 下创建一个新组件 Sum

import React from 'react'
class Sum extends React.Component {
   render() {
      return <p>The sum of {this.props.num1} and {this.props.num2}
      is {parseInt(this.props.num1) + parseInt(this.props.num2)}</p>
   }
}
export default Sum

此处,Sum 组件接受两个数字,num1 和 num2,并打印给定两个数字的总和。如果为 props 提供了数值,则组件将正常工作。但是,如果 sum 组件附带字符串,则它将显示 NaN 作为两个属性的总和。

无法发现 num1 和 num2 props 的给定值格式不正确。让我们在根组件 (src/App.js) 中使用 Sum 组件,看看它是如何呈现的。

import Sum from "./components/PropTypes/Sum";
function App() {
   var num1 = 10
   var num2 = 200
   var name1 = "John"
   var name2 = "Peter"
   return (
      <div>
         <Sum num1={num1} num2={num2} />
         <Sum num1={name1} num2={name2} />
      </div>
   );
}
export default App;
PropTypes

PropTypes

React 社区提供了一个特殊的包 prop-types 来解决属性类型不匹配的问题。prop-types 允许通过组件内部的自定义设置 (propTypes) 指定组件属性的类型。例如,可以使用 PropTypes.number 选项指定数字类型的属性,如下所示。

Sum.propTypes = {
    num1: PropTypes.number,
    num2: PropTypes.number
}

一旦指定了属性的类型,React 将在应用程序的开发阶段抛出警告。让我们在示例应用程序中包含 propTypes,看看它如何帮助捕获属性类型不匹配问题。

使用节点包管理器 (npm) 安装 prop-types 包,如下所示 −

npm i prop-types --save

现在,指定 Sum 组件的属性类型,如下所示 −

import React from 'react'
import PropTypes from 'prop-types'
class Sum extends React.Component {
   render() {
      return <p>The sum of {this.props.num1} and {this.props.num2}
      is {parseInt(this.props.num1) + parseInt(this.props.num2)}</p>
   }
}
Sum.propTypes = {
   num1: PropTypes.number,
   num2: PropTypes.number
}
export default Sum

最后,使用以下命令运行应用程序 −

npm start

在您最喜欢的浏览器中打开应用程序,并通过开发人员工具打开 JavaScript 控制台。JavaScript 会发出警告,指出提供了意外类型,如下所示 −

PropTypes

propTypes 仅在开发阶段起作用,以消除由于额外检查 props 类型而导致的应用程序性能下降。这不会影响应用程序在生产/实时设置中的性能。

可用的验证器

prop-types 提供了大量现成的验证器。它们如下 −

  • PropTypes.array

  • PropTypes.bigint

  • PropTypes.bool

  • PropTypes.func

  • PropTypes.number

  • PropTypes.object

  • PropTypes.string

  • PropTypes.symbol

  • PropTypes.node - 任何可以渲染的内容

  • PropTypes.element - React 组件

  • PropTypes.elementType - React 组件的类型

  • PropTypes.instanceOf() - 实例指定的类

  • propTypes.oneOf(['Value1', 'valueN']) - Value 和 ValueN 之一

  • PropTypes.oneOfType([]) - 示例,PropTypes.oneOfType([PropTypes.number, PropTypes.bigint])

  • PropTypes.arrayOf() - 示例,PropTypes.arrayOf(PropTypes.number)

  • PropTypes.objectOf() - 示例, PropTypes.objectOf(PropTypes.number)

  • PropTypes.func.isRequired

  • propTypes.element.isRequired

  • PropTypes.any.isRequired

还可以创建自定义验证器并用于验证属性的值。假设组件具有电子邮件属性,并且该值应为有效的电子邮件地址。然后,可以编写验证函数并将其附加到电子邮件属性,如下所示 −

PropTypes
Sum.propTypes = {
   num1: PropTypes.number,
   num2: PropTypes.number,
   email: function(myProps, myPropName, myComponentName) {
      if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(myProps[myPropName])) {
         return new Error(
            'Invalid prop value `' + myProps[myPropName] + '` supplied to' +
            ' `' + myComponentName + '/' + myPropName + '`. Validation failed.'
         );
      }
   }
}

此处,

  • /^[^\s@]+@[^\s@]+\.[^\s@]+$/ 是一个简单的正则表达式电子邮件模式。

  • myProps 代表所有属性。

  • myPropName 代表当前正在验证的属性。

  • myComponentName 代表正在验证的组件的名称。

同样,可以使用以下函数签名创建自定义验证器并将其用于数组和对象属性

PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) { ... })

ReactJS - BrowserRouter

路由是前端应用程序中的重要概念之一。React 社区提供了一个出色的路由器库,名为 React Router。在本章中,我们将学习 React 路由器的概念以及如何在 React 应用程序中使用它。

路由器概念

路由器的主要目的是将给定的 url 匹配到 React 组件并呈现匹配的组件。除了匹配和呈现之外,路由器还应管理浏览器的历史记录,以便在浏览器中高效地向前和向后导航。

在了解路由器的工作原理之前,让我们了解 React 路由器库的一些有用组件。

BrowserRouterBrowserRouter 是顶级组件。它创建一个历史记录(导航历史记录),将初始位置(表示"用户所在的位置"的路由器对象)放入反应状态,并最终订阅位置 URL。

<BrowserRouter>
<!-- children -->
</BrowserRouter>

RoutesRoutes 将递归其子节点并构建路由器配置。它将配置的路由与位置进行匹配,并呈现第一个匹配的路由元素。

<BrowserRouter>
   <Routes>
      <Route path="/" element={<App />}>
         <Route index element={<Home />} />
            <Route path="admin" element={<Admin />}>
            <Route path="product" element={<ProductListing />} />
            <Route path="category" element={<CategoryListing />} />
            <Route index element={<Dashboard />} />
         </Route>
      </Route>
      <Route path="/login" element={<Login />}>
         <!-- more nested routes -->
      </Route>
      <!-- more routes -->
   </Routes>
</BrowserRouter>

此处,

  • / 路径映射到 App 组件。

  • / 路径的 index 组件映射到 Home 组件。

  • /admin 路径映射到 Admin 组件。

  • /admin 路径的 index 组件映射到 Dashboard 组件。

  • /admin/product 路径与 ProductListing 组件匹配。

  • /admin/category 路径与 CategoryListing 组件匹配。

  • /admin/dashboard 路径与 Dashboard 匹配组件。

RouteRoute 是实际的路由器配置。它可以嵌套到任何级别,类似于文件夹。

OutletOutlet 组件渲染一组匹配中的下一个匹配。

function Hello() {
   return (
      <div>Hello</div>
      <Outlet />
   )
}

这里,

  • 将 Outlet 组件放置在 hello 组件的底部。

  • Router 将在 Outlet 组件内呈现下一个匹配项。

Link − Link 组件类似于锚标记,用于导航目的。一旦用户单击它,它会根据其属性更改位置

<Link to="/admin" />

navigate() − 是一个用于导航目的的 API,类似于 Link 组件。

navigate("/admin")

路由器工作流程

让我们考虑一个 React 应用程序有两个五个页面/组件,如下所示 −

  • Home (/)

  • Contact (/contact)

  • Register (/register)

  • Admin (/admin)

  • AdminDashboard (/admin/dashboard)

路由器配置示例如下 −

<BrowserRouter>
   <Routes>
      <Route path="/" element={<App />}>
         <Route index element={<Home />} />
         <Route path="contact" element={<Contact />} />
         <Route path="register" element={<Register />} />
         <Route path="admin" element={<Admin />}>
         <Route path="dashboard" element={<AdminDashboard />} />
         <Route path="category" element={<CategoryListing />} />
         <Route index element={<AdminDashboard />} />
      </Route>
   </Routes>
</BrowserRouter>

让我们看看管理仪表板网址 (/admin/dashboard) 将如何被 React Router 匹配和呈现。

  • 首先,React 库将呈现我们的应用程序。由于我们的应用程序在呈现树的顶部有 BrowserRouter,因此它会被调用和呈现

  • BrowserRouter 组件创建历史记录,将初始位置置于状态中并订阅网址

  • Routes 组件将检查其所有子组件,构建路由器配置并最终呈现第一个匹配项 (/admin => )

  • Admin 组件将被呈现。它将有一个 Outlet 组件,如下所示 -

function Admin() {
   return (
      <div>
         <!-- Admin content -->
      </div>
   <Outlet />
   )
}
  • Outlet 组件在其自身内呈现下一个匹配项 (/admin/dashboard => )

  • 用户可以单击仪表板中的链接 (Link 组件),例如"/admin/category"

  • Link 组件调用 navigation("/admin/category") 方法

  • 历史记录 (对象) 更改 url 并通知 BrowserRouter

  • 由于 BrowserRouter 组件已订阅 url,BrowserRouter 组件将重新呈现并重复整个过程 (从 2 开始)

如何应用路由器

首先,创建一个新的 React 应用程序并使用下面的方法启动它命令。

create-react-app myapp
cd myapp
npm start

接下来,使用以下命令安装 react router 库,

npm install --save react-router-dom

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。

// 删除所有 css 类

接下来,在 src 下创建一个文件夹 Pages,并创建一个新的主页组件 Home (src/Pages/Home.js) 并呈现简单的主页内容,如下所示 −

function Home() {
    return <h3>Home</h3>
}
export default Home

接下来,创建一个新的问候页面组件 Greeting (src/Pages/Greeting.js) 并呈现简单的问候消息,如下所示 −

function Greeting() {
    return <h3>Hello World!</h3>
}
export default Greeting;

接下来,打开 App.js 文件并呈现一个 BrowserRoutes 组件,如下所示 −

import './App.css'
import React from 'react';

import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Layout from './Pages/Layout';
import Home from './Pages/Home';
import Greeting from './Pages/Greeting';

function App() {
   return (
      <BrowserRouter>
         <Routes>
            <Route path="/" element={<Layout />}>
               <Route index element={<Home />} />
               <Route path="greet" element={<Greeting />} />
            </Route>
         </Routes>
      </BrowserRouter>
   );
}
export default App;

这里,

  • BrowserRouter 是主要组件。它将具有路由器设置作为其子项,并根据路由器设置呈现整个应用程序。

  • Routes 是主要路由器组件。它包含单个路由器设置的列表。

  • Route 是实际的路由器组件,具有 Web 路径 (/home) 和组件 (Home) 之间的映射。

  • Route 可以嵌套以支持嵌套路径。

路由中定义的映射如下 −

  • / 映射到 Layout 组件。 Layout 组件将在下一步中创建。

  • /home 映射到 Home 组件,并嵌套在 / 路径下。

  • /greet 映射到 Greet 组件,并嵌套在 / 路径下。

接下来,创建一个布局组件 Layout (src/Pages/Layout.js)。布局组件的目的是通过导航链接展示整个应用程序。它是应用程序的主要组件,指向 / 路由。Layout 组件的源代码如下−

import { Outlet, Link } from "react-router-dom";
function Layout() {
   return (
      <>
         <nav>
            <ul>
               <li>
                  <Link to="/">Home</Link>
               </li>
               <li>
                  <Link to="/greet">Greeting</Link>
               </li>
            </ul>
         </nav>
         <Outlet />
      </>
   )
}
export default Layout;

这里,

  • 导入了LinkOutlet组件。

  • Link组件用于创建网页导航链接。

  • Link组件的to属性设置为父级BrowserRouter组件中定义的路由之一。

  • 使用路由器设置中可用的/和/greet路由。

  • Outlet组件用于在底部加载所选组件。在初始渲染期间,它将加载默认组件(home)。

  • 一旦用户点击网页链接,它将从to属性获取路由器路径,并通过路由器设置获取映射的组件。最后,它将在 Outlet 组件内渲染该组件。

接下来,创建一个新组件 PageNotAvailable (src/Pages/PageNotAvailable.js),用于在链接与任何路由器设置不匹配时显示。

import { Link } from "react-router-dom"
function PageNotAvailable() {
   return (
      <p>The page is not available. Please <Link to=
      "/">click here</Link> to go to home page.</p>
   )
}
export default PageNotAvailable

此处,Link 组件用于导航回主页。

接下来,更新 App 组件并在路由器设置中包含 PageNotAvailable 组件。

import './App.css'
import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Layout from './Pages/Layout';
import Home from './Pages/Home';
import Greeting from './Pages/Greeting';
import PageNotAvailable from './Pages/PageNotAvailable'
function App() {
   return (
      <BrowserRouter>
         <Routes>
            <Route path="/" element={<Layout />}>
               <Route index element={<Home />} />
               <Route path="greet" element={<Greeting />} />
               <Route path="*" element={<PageNotAvailable />} />
            </Route>
         </Routes>
      </BrowserRouter>
   );
}
export default App;

这里,

  • 当用户点击链接时,React Router 将尝试按照给定的顺序逐一匹配所点击的链接和路由器设置。如果匹配到链接,则 React Router 将停止并呈现匹配的组件。

  • * 模式将匹配所有链接。由于它被放置在最后一个条目中,因此它将匹配所有未定义/未知的链接。

接下来,更新 Layout 组件并添加一个不可用的链接以检查 PageNotAvailable 组件是否配置正确。

import { Outlet, Link } from "react-router-dom";
function Layout() {
   return (
      <>
         <nav>
            <ul>
               <li>
                  <Link to="/">Home</Link>
               </li>
               <li>
                  <Link to="/greet">Greeting</Link>
               </li>
               <li>
                  <Link to="/unknown">Unavailable page</Link>
               </li>
            </ul>
         </nav>
         <Outlet />
      </>
   )
}
export default Layout;

最后,在浏览器中打开应用程序并检查最终结果。应用程序将呈现如下图所示 −

BrowserRouter

用户可以使用导航链接导航到任何页面,如上图所示。

总结

React router 易于配置和使用。它没有那么多花哨的功能,但具有必要的功能,如链接、出口、路由和路由,可以创建带有导航链接的完整 Web 应用程序。

ReactJS - DOM

要运行 React 应用程序,需要将其自身附加到 Web 应用程序的主文档。React 提供了一个模块来访问应用程序并将其附加到文档的 DOM,该模块是 ReactDOM (react-dom)。

在本章中,我们将学习如何创建一个简单的 React 组件,并使用 reactDOM 模块将组件附加到文档中。

ReactDOM 用法

react-dom 是用于操作文档 DOM 的核心包。react-dom 允许将一个或多个 React 应用程序附加到文档。应将 react-dom 导入到应用程序中,如下所示 −

import * as ReactDOM from 'react-dom';

react-dom 提供了两种方法来操作 DOM,如下所示 −

createPortal() − 在 React 应用程序中创建一个门户。门户是一个特殊的 React 节点,它使主 React 应用程序能够将其子节点渲染到 DOM 组件自身层次结构之外的 DOM 中。

return ReactDOM.createPortal(
    this.props.children, // 子节点
    domNode // 根元素之外的 DOM 节点
);

让我们在接下来的章节中更详细地了解门户。

flushSync() − 立即刷新状态更改并更新 DOM。通常,react 会创建一个虚拟 dom,然后通过分析虚拟 dom 和真实 dom 之间的差异来更新真实 dom。更新频率由 react 内部确定。flushSync() 会中断并立即更新更改。

react-dom 提供两个子模块,一个用于服务器端应用程序,另一个用于客户端应用程序。模块如下 −

  • react-dom/server

  • react-dom/client

ReactDOMServer

服务器模块将用于在服务器中渲染 React 组件,模块可以按如下所示导入 −

import * as ReactDOMServer from 'react-dom/server';

ReactDOMServer 提供的一些方法如下 −

  • renderToPipeableStream() −将 React 组件渲染为其初始 HTML 并返回管道流。

  • renderToReadableStream() − 将 React 组件渲染为其初始 HTML 并通过 Promise 返回可读的 Web 流。

  • renderToStaticNodeStream() − 将 React 组件渲染为其初始 HTML 并返回输出 HTML 字符串的可读 NodeJS 流。它跳过了额外的标记(例如 data-reactroot),输出将与 renderToStaticMarkup() 相同。

  • renderToString() −将 React 组件渲染为其初始 HTML 并返回 HTML 字符串。

  • renderToStaticMarkup() −与 renderToString() 相同,只是它跳过了额外的标记,例如 data-reactroot。

ReactDOMClient

客户端模块将广泛应用于前端开发,可以导入到应用程序中,如下所示 −

import * as ReactDOM from 'react-dom/client';

ReactDOMClient 提供的一些方法如下 −

createRoot() − 创建一个根元素,以便稍后附加和渲染 React 组件。它接受一个 html 元素并返回一个 React 节点。 react 节点被称为应用程序的根节点。返回的 react 节点将有两个方法,render 用于渲染 react 组件,unmount 用于卸载 react 组件。

const root = createRoot(container);
root.render(element); // where element = document.getElementById('root-id')
root.umount();

hydrateRoot() −与 createRoot() 相同,但它与 react-dom/server 模块结合使用,以补充服务器中呈现的 React 组件。

应用 ReactDOMClient

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,在组件文件夹 (src/components/Hello.js) 下创建一个 React 组件 Hello。

import React from "react";
class Hello extends React.Component {
   constructor(props) {
      super(props)
   }
   render() {
      return (
         <div>Hello, {this.props.name}</div>
      );
   }
}
export default Hello;

接下来,打开index.html (public/index.html)并添加一个新容器(root2),如下所示 −

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="utf-8" />
      <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <meta name="theme-color" content="#000000" />
      <meta name="description" content="Web site created using create-react-app" />
      <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
      <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
      <title>React App</title>
   </head>
   <body>
      <noscript>You need to enable JavaScript to run this app.</noscript>
      <div style="padding: 10px;">
         <div id="root"></div>
         <div id="root2"></div>
      </div>
   </body>
</html>

接下来,打开 index.js (src/index.js) 并将我们的 hello 组件附加到 rootroot2 容器中,如下所示 −

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import Hello from './Components/Hello';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
   <React.StrictMode>
      <Hello name="Main root container" />
   </React.StrictMode>
);
const root2 = ReactDOM.createRoot(document.getElementById('root2'));
root2.render(
   <React.StrictMode>
      <Hello name="Another root container" />
   </React.StrictMode>
);
reportWebVitals();`

最后,在浏览器中打开应用程序并检查结果。React 组件将附加到根元素,如下所示 −

ReactDOMClient

摘要

ReactDOM 通过在客户端和服务器环境中将 React 组件附加到 HTML 文档中,提供了为 React 应用程序创建入口点的能力。

ReactJS - 轮播

React 通过第三方 UI 组件库提供轮播组件。React 社区提供了大量的 UI / UX 组件,很难选择适合我们需求的库。Bootstrap UI 库是开发人员的热门选择之一,被广泛使用。 React Bootstrap (https://react-bootstrap.github.io/) 已将几乎所有 bootstrap UI 组件移植到 React 库,并且它对 Carousel 组件也提供了最佳支持。

在本章中,我们将学习如何使用 react-bootstrap 库中的 Carousel 组件。

什么是 Carousel?

Carousel 基本上是一个幻灯片,循环播放一系列内容,并支持丰富的动画。它接受一系列图像作为其主要内容。它还接受每张幻灯片的标题内容。它有按钮/指示器,用于从当前内容导航到下一个/上一个内容。可以根据需求配置暂停和显示内容的时间长度。

Carousel 轮播组件

Carousel 轮播组件允许开发人员在 Web 应用程序中使用引导设计创建简单的轮播。轮播组件接受两个组件,

  • Carousel.Item

轮播组件接受多个Carousel.Item项。每个Carousel.Item 都是一张幻灯片,可以接受一张图片。示例代码如下 −

<Carousel>
   <Carousel.Item>
      <img
         className="d-block w-100"
         src="react_bootstrap_carousel_slide1.png"
         alt="First slide"
      />
   </Carousel.Item>
   <Carousel.Item>
      <img
         className="d-block w-100"
         src="react_bootstrap_carousel_slide2.png"
         alt="Second slide"
      />
   </Carousel.Item>
</Carousel>
  • Carousel.Caption

Carousel.Caption 是一个特殊组件,用于显示幻灯片的简短描述,应包含在 Carousel.Item 组件内。示例代码如下 −

<Carousel.Item>
   <img
      className="d-block w-100"
      src="react_bootstrap_carousel_slide1.png"
      alt="First slide"
   />
   <Carousel.Caption>
      <h3>React Bootstrap</h3>
      <p>React component library providing bootstrap components</p>
   </Carousel.Caption>
</Carousel.Item>

Carousel 组件接受一小组属性来配置功能,具体如下,

  • controls (boolean)

  • 启用/禁用控件,例如上一个/下一个按钮

  • keyboard (boolean)

  • 启用键盘控制

  • touch (boolean)

  • 启用/禁用触摸控制

  • indicators (boolean)

  • 启用/禁用轮播底部的指示器

  • nextIcon (React node)

  • 自定义下一个图标的选项

  • nextLabel (string)

  • 自定义下一个标签的选项

  • prevIcon (React Node)

  • 自定义上一个图标的选项

  • prevLabel (string)

  • 自定义上一个标签的选项

  • interval (number)

  • 两张幻灯片之间暂停和播放的时长

  • activeIndex (number)

  • 表示要显示的幻灯片的索引号

  • slide (boolean)

  • 启用/禁用自动滑动功能

  • variant (dark)

  • 启用轮播设计的不同变体。深色选项将轮播主题从亮变为暗

  • bsPrefix (string)

  • 用于自定义底层 CSS 类的前缀

  • onSelect (function)

  • 可以附加一个函数来处理 onSelect 事件

  • onSlide (function)

  • 可以附加一个函数来处理 onSlide 事件

Carousel.Item 组件接受一些 props 来配置功能,如下所示:

  • interval(数字)

  • 单个幻灯片的暂停时间

  • bsPrefix(字符串)

  • 用于自定义底层 CSS 类的前缀

应用 Carousel 组件

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,使用以下命令安装 bootstrap 和 react-bootstrap 库:

npm install --save bootstrap react-bootstrap

接下来,创建一个简单的轮播组件 SimpleCarousel (src/Components/SimpleCarousel.js) 并渲染一个轮播,如下所示 −

import { Carousel } from 'react-bootstrap';
function SimpleCarousel() {
   return (
      <Carousel fade indicators={false} controls={false}>
         <Carousel.Item>
            <img
               className="d-block w-100"
               src="react_bootstrap_carousel_slide1.png"
               alt="First slide"
            />
            <Carousel.Caption>
               <h3>React Bootstrap</h3>
               <p>React component library providing bootstrap components</p>
            </Carousel.Caption>
         </Carousel.Item>
         <Carousel.Item>
            <img
               className="d-block w-100"
               src="react_bootstrap_carousel_slide2.png"
               alt="Second slide"
            />
            <Carousel.Caption>
               <h3>Button</h3>
               <p>React Bootstrap button component</p>
            </Carousel.Caption>
         </Carousel.Item>
         <Carousel.Item>
            <img
               className="d-block w-100"
               src="react_bootstrap_carousel_slide3.png"
               alt="Third slide"
            />
            <Carousel.Caption>
               <h3>Carousel</h3>
               <p>React bootstrap Carousel component</p>
            </Carousel.Caption>
         </Carousel.Item>
      </Carousel>
   );
}
export default SimpleCarousel;

这里,

  • 导入 Carousel 组件并添加单个 Carousel 组件。

  • 在 Carousel 组件中使用淡入淡出属性来更改动画类型

  • 在 Carousel 组件中使用指示器属性来删除指示器

  • 在 Carousel 组件中使用控件属性来删除控件

  • 添加了三个 Carousel.Item 项目并使用了三张图片。

  • 在每个 Carousel.Item 组件中添加 Carousel.Caption 并设置每张幻灯片的标题。

接下来,打开 App.css (src/App.css) 并删除所有样式。

// 删除所有默认样式

接下来,打开 App 组件 (src/App.js),导入 bootstrap css 并使用我们新的轮播组件更新内容,如下所示 −

import './App.css'
import "bootstrap/dist/css/bootstrap.min.css";
import SimpleCarousel from './Components/SimpleCarousel'
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div style={{ width: "400px", height: "400px", backgroundColor: "skyblue" }}>
               <SimpleCarousel />
            </div>
         </div>
      </div>
   );
}
export default App;

这里,

  • 使用 import 语句导入 bootstrap 类

  • 渲染我们新的 SimpleCarousel 组件。

  • 包含 App.css 样式

最后,在浏览器中打开应用程序并检查最终结果。轮播组件将呈现如下图所示 −

Carousel

添加控件和指示器

让我们更新我们的组件以包含用于导航下一张和上一张幻灯片的控件以及用于识别当前幻灯片位置的指示器。

首先,打开我们的轮播应用程序并更新 SimpleCarousel 组件,如下所示−

import { Carousel } from 'react-bootstrap';
function SimpleCarousel() {
   return (
      <Carousel fade indicators={true} controls={true}>
      <Carousel.Item>
      // ...

这里,

  • 使用 indicators 属性来启用指示器

  • 使用 controls 属性来启用控件

接下来,在浏览器中打开应用程序并检查最终结果。轮播组件将使用控件和指示器呈现,如下所示 −

Carousel

摘要

React-bootstrap 轮播组件提供了创建干净、简单的轮播组件所需的所有选项。

ReactJS - 图标

Web 图标是 Web 应用程序中的重要资产。开发人员在多个地方广泛使用它来更好地可视化上下文。例如,菜单可以通过菜单图标轻松识别。Web 图标历史悠久,在其悠久的历史中有多种实现方式。

最初,图标是标准尺寸的简单图像,如 24x24、32x32、48x48 等,后来,多个图标被设计为单个图像,称为图标冲刺,通过 CSS 定位属性在网站中使用。然后,字体用于容纳多个图标,并通过 CSS font-family 属性使用。列表中最新的是 SVG 图标。SVG 图标以 SVG 格式设计和保存,并通过 img 标签或内联 SVG 选项在网站中使用。

React 提供了一个基于社区的图标库,称为 React icons,它提供了来自不同图标库的大量图标。让我们在本章中学习如何使用 React 图标库。

React 图标 (React-icon) 库

React 图标库收集了来自不同供应商的数千个图标并将其包装为 React 组件。开发人员可以像在项目中包含 React 组件一样简单地使用它来使用特定图标。 React icons 提供的图标集小列表如下 −

  • Bootstrap 图标

  • Material design 图标

  • Font Awesome

  • Devicons

  • Boxicons

  • Ant Design 图标

  • Github Octicons 图标

  • VS Code 图标

React icons 提供了更多的图标集,我们可以在他们的网站上查看所有图标(https://react-icons.github.io/react-icons/)

安装 react 图标库

在 Web 应用程序中安装 React 图标库就像使用 npm 安装包一样简单,如下所示 −

npm install react-icons --save

使用 react 图标组件

库中的每个图标都会有一个相关的 react 组件。开发人员可以从 React 图标库网站找到他们需要的图标组件,并在他们的 Web 应用程序中使用它。让我们看看如何使用来自 react 图标库的 Material Design 集中的日历图标。Material Design 中的日历图标组件的名称是 MdCalendarToday。Material Design 图标集的包是 react-icons/md。开发者需要导入包并在相关位置使用该组件,如下所示 −

import { MdCalendarToday } from "react-icons/md";
// ...
// ...
class SimpleIcon extends React.Component {
   render() {
      return <div>This icons <MdCalendarToday /> is calendar icon imported from react icon library</div>
   }
}

开发者可以通过CSS改变图标的​​颜色和大小。

class SimpleIcon extends React.Component {
   render() {
      return <div>This icons <MdCalendarToday style={{backgroundColor: "red", size: "24px"}}/>
      is calendar icon imported from react icon library</div>
   }
}

应用 react 图标库

让我们通过开发一个应用程序来学习 forwardRef 的概念。

首先,创建一个新的 react 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,安装 react 图标库,如下所示 −

npm install react-icons --save

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。

// 删除 css

接下来,创建一个简单的组件, SimpleIcon (src/Components/SimpleIcon.js)如下所示 −

import React from "react";
import { MdCalendarToday } from "react-icons/md";
class SimpleIcon extends React.Component {
   render() {
      return <div>This icons <MdCalendarToday style={{ color: "red", size: "24px" }} />
      is calendar icon imported from react icon library</div>
    }
}
export default SimpleIcon

这里,

  • 导入 react-icons/md 库。

  • 使用 MdCalendarToday 组件渲染日历图标。

  • 使用内联样式更改图标的颜色和大小。

接下来,打开 App 组件 (src/App.js) 并使用 SimpleIcon 组件更新内容,如下所示 −

import './App.css'
import React from 'react';
import SimpleIcon from './Components/SimpleIcon'
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleIcon />
            </div>
         </div>
      </div>
   );
}
export default App;

这里,

  • 导入了 SimpleIcon 组件。

  • 使用 SimpleIcon 组件来呈现日历图标。

最后,在浏览器中打开应用程序。日历图标将呈现如下图所示 −

安装 React 图标

摘要

React 图标库通过从不同来源收集各种图标并将其放在一个地方并以简单易行的方式提供,从而帮助开发人员。

ReactJS - 表单组件

React 通过第三方 UI 组件库提供表单组件。React 社区提供了大量的 UI / UX 组件,很难选择适合我们需求的库。Bootstrap UI 库是开发人员的热门选择之一,被广泛使用。React Bootstrap (https://react-bootstrap.github.io/) 已将几乎所有 bootstrap UI 组件移植到 React 库,并且它对 form 组件也提供了最佳支持。

让我们在本章中学习如何使用 react-bootstrap 库中的表单组件。

什么是表单组件?

表单编程是 Web 应用程序的亮点功能之一。它用于在前端收集来自用户的信息,然后将其传递到服务器端进行进一步处理。收集到的信息将在发送到服务器之前在前端进行验证。HTML 具有不同的输入标签,如文本、复选框、单选按钮等,以从用户那里收集不同类型的信息。

React bootstrap 提供了几乎所有基于 bootstrap 的表单组件。它们如下 −

Form

Form 组件用于呈现基本的 html 表单(form)。它是最顶层的表单组件。Form 组件的一些有用的 props 如下 −

  • ref (ReactRef) − Ref 元素用于访问底层 DOM 节点。

  • as (elementType) −启用以指定除 *<form>* 之外的元素。

  • validated (boolean) − 指定正在验证表单。将值切换为 true 将显示表单中设置的验证样式。

可以使用一个简单的表单组件,如下所示。 −

<Form>
   <!-- Use form control-->
</Form>

Form.Control

Form.Control 组件用于通过其 type 属性呈现各种输入元素。Form.Control 组件的一些有用属性如下 −

  • ref (ReactRef) − 引用元素以访问底层 DOM 节点。

  • as (elementType) − 启用以指定除 *<input>* 之外的元素。

  • disabled (boolean) − 启用/禁用控制元素。

  • htmlSize (number) − 底层控制元素的大小。

  • id (string) − 控制元素的 ID。如果未在此处指定,则使用父 *Form.Group* 组件的 *controlId*。

  • IsInValid(布尔值) − 启用/禁用与无效数据相关的样式。

  • IsValid(布尔值) − 启用/禁用与有效数据相关的样式。

  • plaintext(布尔值) − 启用/禁用输入并将其呈现为纯文本。与 *readonly* 属性一起使用。

  • readOnly(布尔值) − 启用/禁用控件的 readonly 属性。

  • size(sm | lg) − 输入元素的大小。

  • type(字符串) −要呈现的输入元素的类型。

  • value (string | arrayOf | number) − 底层控件的值。由 *onChange* 事件操纵,初始值将默认为 *defaultValue* props

  • bsPrefix (string) − 用于自定义底层 CSS 类的前缀。

  • onChange (boolean) − 触发 *onChange* 事件时调用的回调函数。

可以使用一个简单的表单控件组件,如下所示 −

<>
   <Form.Control type="text" size="lg" placeholder="Large text" />
   <br />
   <Form.Control type="text" placeholder="Normal text" />
   <br />
   <Form.Control type="text" size="sm" placeholder="Small text" />
</>
Form Component

Form.Label

Form.Label 组件用于呈现 html 标签组件 ()。Form.Label 组件的一些有用 props 如下 −

  • ref (ReactRef) − Ref 元素用于访问底层 DOM 节点。

  • as (elementType) − 允许指定除 *<label>* 之外的元素。

  • htmlFor (string) − 用于指定为其创建特定标签的输入元素。如果未指定 *htmlFor*,则它将使用顶级 *Form.Group* 组件的 *controlId*。

  • column (boolean | sm | lg) − 使用 *<Col>* 组件呈现标签以进行布局。

  • visuallyHidden (boolean) − 以视觉方式隐藏标签,但仍允许辅助技术使用。

  • bsPrefix (string) − 用于自定义底层 CSS 类的前缀。

Form.Group

Form.Group 组件用于对表单控件和标签进行分组。它将用于根据其标签对控件进行布局。 Form.Group 组件的一些有用 props 如下 −

  • ref (ReactRef) − Ref 元素用于访问底层 DOM 节点。

  • as (elementType) − 启用以指定除 *<form>* 之外的元素。

  • controlId (string) − Id 用于引用控件和标签的组。如果控件没有 *Id* props,它将用作组内表单控件的 id。

可以使用一个简单的表单组以及表单标签,如下所示 −

<Form.Group controlId="formFile" className="mb-3">
   <Form.Label>Upload file</Form.Label>
   <Form.Control type="file" />
</Form.Group>
Form 表单组件

Form.Text

Form.Text 组件用于显示表单控件的帮助消息(*)。Form* 组件的一些有用属性如下 −

  • ref (ReactRef) − Ref 元素用于访问底层 DOM 节点。

  • as (elementType) − 启用以指定除 *<form>* 之外的元素。

  • muted (boolean) − 应用 *text-muted* 类。

  • bsPrefix (string) −用于自定义底层 CSS 类的前缀。

一个简单的表单文本组件可以按如下方式使用。 −

<Form.Label htmlFor="pwd">Password</Form.Label>
<Form.Control
   type="password"
   id="pwd"
   aria-describedby="passwordHelpMessage"
/>
<Form.Text id="passwordHelpMessage" muted>
   Please set password within 8 - 12 characters long. Use minimum of 1 digit,
   1 special character and 1 capital letter. Try to use strong password.
</Form.Text>
Form 表单组件

Form.Select

Form.Select组件用于渲染选择元素(select)。Form.Select组件的一些有用属性如下 −

  • disabled (boolean) − 启用/禁用控制元素。

  • htmlSize (number) − 底层控制元素的大小。

  • IsInValid (boolean) − 启用/禁用与无效数据相关的样式。

  • IsValid (boolean) −启用/禁用与有效数据相关的样式。

  • size (sm | lg) − 输入元素的大小。

  • value (string | arrayOf | number) − 底层控件的值。由 *onChange* 事件操纵,初始值默认为 *defaultValue* props。

  • bsPrefix (string) − 用于自定义底层 CSS 类的前缀。

  • onChange (boolean) − 触发 *onChange* 事件时调用的回调函数。

可以使用一个简单的表单选择组件,如下所示 −

<Form.Select aria-label="Select category">
   <option value="sm">Small</option>
   <option value="lg">Large</option>
   <option value="xl">Extra large</option>
</Form.Select>
表单组件

Form.Check

Form.Check 组件用于渲染 html 表单中的复选框 () 和单选按钮 ()。Form 组件的一些有用属性如下 −

  • ref (ReactRef) − 引用元素以访问底层 DOM 节点。

  • as (elementType) − 启用以指定除 *<input>* 之外的元素

  • disabled (boolean) − 启用/禁用控制元素。

  • id (string) − 控制元素的 ID。如果未在此处指定,则使用父 *Form.Group* 组件的 *controlId*。

  • children(节点) −自定义 *FormCheck* 内容的呈现。

  • title(字符串) − 底层 *FormCheckLabel* 的标题属性

  • type(radio | checkbox | switch) − 要呈现的输入元素的类型。

  • value(字符串 | arrayOf | number) − 底层控件的值。由 *onChange* 事件操纵,初始值默认为 *defaultValue* 属性。

  • label(节点) − 控件的标签。

  • feedback(节点) −验证过程中要呈现的反馈消息。

  • feedbackTooltip(布尔值) − 启用/禁用以工具提示形式显示的反馈消息。

  • IsInValid(布尔值) − 启用/禁用与无效数据相关的样式。

  • IsValid(布尔值) − 启用/禁用与有效数据相关的样式。

  • inline(布尔值) − 启用/禁用以水平方式布局的控件。

  • reverse(布尔值) − 启用/禁用子项的反向布局。

  • bsPrefix(字符串) −用于自定义底层 CSS 类的前缀。

  • bsSwitchPrefix (字符串) − 用于自定义开关控制的底层 CSS 类的前缀。

可以使用一个简单的表单检查组件,如下所示 −

<Form.Group controlId="gender" className="mb-3">
   <Form.Label>Select your gender</Form.Label>
   <div className="mb-3">
      <Form.Check
         type='radio'
         id='Male'
         label='Male'
         name='gender'
      />
      <Form.Check
         type='radio'
         id='Female'
         label='Female'
         name='gender'
      />
   </div>
</Form.Group>

此处,Form.Check 被分组到 Form.Group 组件下。

Form Component

Form.Check.Label

Form.Check.Label 组件用于为 Form.Check 组件的底层输入呈现标签。它将作为 Form.Check 组件的子项包含在内。Form.Check.Input 组件的一些有用 props 如下 −

  • htmlFor (string) − 用于指定为其创建特定标签的输入元素。如果未指定 *htmlFor*,则它将使用顶级 *Form.Group* 组件的 *controlId*。

  • bsPrefix (string) − 用于自定义底层 CSS 类的前缀

Form.Check.Input

Form.Check.Input 组件用于呈现 Form.Check 组件的底层输入。它将作为 Form.Check 组件的子项包含在内。Form.Check.Input 组件的一些有用属性如下 −

  • as (elementType) − 启用以指定除 *<input>* 之外的元素。

  • id (string) − 控制元素的 Id。如果未在此处指定,则使用父 .*Form.Group* 组件的 *controlId*。

  • type (radio | checkbox | switch) − 要呈现的输入元素的类型。

  • IsInValid (boolean) − 启用 / 禁用与无效数据相关的样式。

  • IsValid (boolean) − 启用 / 禁用与有效数据相关的样式。

  • type (radio | checkbox) − 要呈现的输入元素的类型。

  • bsPrefix (string) − 用于自定义底层 CSS 类的前缀。

可以使用简单的表单检查输入和标签组件,如下所示 −

<Form.Group controlId="gender" className="mb-3">
<Form.Label>Select your favorite programming language</Form.Label>
   <div className="mb-3">
      <Form.Check
         type='checkbox'
         id='java-lang'
         name='language'
      >
         <Form.Check.Input type='checkbox' isValid />
         <Form.Check.Label>Java</Form.Check.Label>
      </Form.Check>
      <Form.Check
         type='checkbox'
         id='c-lang'
         name='language'
      >
         <Form.Check.Input type='checkbox' isValid />
         <Form.Check.Label>C</Form.Check.Label>
      </Form.Check>
      <Form.Check
         type='checkbox'
         id='javascript-lang'
         name='language'
      >
         <Form.Check.Input type='checkbox' isValid />
         <Form.Check.Label>Javascript</Form.Check.Label>
      </Form.Check>
   </div>
</Form.Group>
Form Component

Form.Range

Form.Range 组件用于在 html 表单中呈现范围输入控件。Form.Range 组件的一些有用属性如下 −

  • disabled (boolean) − 启用/禁用控件元素。

  • id (string) − 控件元素的 ID。如果未在此处指定,则使用父 *Form.Group* 组件的 *controlId*。

  • value (string | arrayOf | number) − 底层控件的值。由 *onChange* 事件操纵,初始值将默认为 *defaultValue* 属性。

  • bsPrefix (string) −用于自定义底层 CSS 类的前缀。

可以使用一个简单的表单范围组件,如下所示 −

<Form.Label>Select by range</Form.Label>
<Form.Range value="25"/>
Form Component

InputGroup

InputGroup 组件用于对多个输入和文本组件进行分组,并以简单易行的方式生成新的高级组件。InputGroup 组件的一些有用属性如下 −

  • as (elementType) −启用以指定除 *<div>* 之外的元素。

  • hasValidation (boolean) − 当输入组同时包含输入和反馈元素时使用。

  • size (sm | lg) − 控件的大小。它将由组件内部处理。

  • bsPrefix (string) − 用于自定义底层 CSS 类的前缀。

InputGroup.Text

InputGroup.Text 组件 用于在 InputGroup.Text 组件内呈现文本。Form 组件的一些有用 props 如下 −

  • id (string) −节点的 ID。

可以使用一个简单的输入组和文本组件,如下所示 −

<Form.Group controlId="email" className="mb-3">
   <Form.Label>Enter your email address</Form.Label>
   <InputGroup className="mb-3">
      <Form.Control
         aria-label="Email address"
         aria-describedby="domain"
      />
      <InputGroup.Text id="domain">@tutorialspoint.com</InputGroup.Text>
   </InputGroup>
</Form.Group>
InputGroup

应用表单组件

首先,创建一个新的 React 应用程序并使用以下命令启动它。

create-react-app myapp
cd myapp
npm start

接下来,使用以下命令安装 bootstrap 和 react-bootstrap 库,

npm install --save bootstrap react-bootstrap

接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。

// 删除所有 css类

接下来,创建一个简单的表单组件 SimpleForm (src/Components/SimpleForm.js) 并呈现一个表单,如下所示−

import { Form, Button } from 'react-bootstrap';
function SimpleForm() {
   return (
      <Form>
         <Form.Group className="mb-3" controlId="email">
            <Form.Label>Email address</Form.Label>
            <Form.Control type="email" placeholder="Enter email" />
            <Form.Text className="text-muted">
               This email address will be used for communication purpose.
            </Form.Text>
         </Form.Group>
         <Form.Group className="mb-3" controlId="password">
            <Form.Label>Password</Form.Label>
            <Form.Control type="password" placeholder="Password" />
         </Form.Group>
         <Form.Group className="mb-3" controlId="formBasicCheckbox">
            <Form.Check type="checkbox" label="Save password" />
         </Form.Group>
         <Button variant="primary" type="submit">
            Submit
         </Button>
      </Form>
   );
}
export default SimpleForm;

这里,

  • 使用 Form.Control 分别获取用户名和密码。

  • 使用 Button 组件提交表单

  • 使用 Form.Group 将表单控件组件及其相关标签组件分组

接下来,打开 App 组件 (src/App.js),导入 bootstrap css 并使用 bootstrap 按钮更新内容,如下所示 −

import './App.css'
import "bootstrap/dist/css/bootstrap.min.css";
import SimpleForm from './Components/SimpleForm'
function App() {
   return (
      <div className="container">
         <div style={{ padding: "10px" }}>
            <div>
               <SimpleForm />
            </div>
         </div>
      </div>
   );
}
export default App;

这里,

  • 使用 import 语句导入 bootstrap 类

  • 渲染我们新的 SimpleForm 组件。

  • 包含 App.css 样式

最后,在浏览器中打开应用程序并检查最终结果。表单将呈现如下图所示 −

Applying Form Component

摘要

Bootstrap 表单组件提供了设计美观表单所需的所有选项。它利用 bootstrap CSS 框架并提供易于使用的 props 来对表单进行大量自定义。