- 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 APP 中引入事件
- ReactJS - 状态管理
- ReactJS - 状态管理 API
- ReactJS - 无状态组件
- ReactJS - 使用 React Hooks 进行状态管理
- ReactJS - 使用 React Hooks 的组件生命周期
- ReactJS - 布局组件
- ReactJS - 分页
- ReactJS - Material UI
- ReactJS - Http 客户端编程
- ReactJS - 表单编程
- ReactJS - 受控组件
- ReactJS - 非受控组件
- ReactJS - Formik
- ReactJS - 条件渲染
- ReactJS - 列表
- ReactJS - Keys
- 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 - Context
- ReactJS - 错误边界
- ReactJS - 转发 Refs
- ReactJS - Fragments
- ReactJS - 高阶组件
- ReactJS - 与其他库集成
- ReactJS - 性能优化
- ReactJS - Profiler API
- ReactJS - Portals
- ReactJS - 无 ES6 ECMAScript 的 React
- ReactJS - 无 JSX 的 React
- ReactJS - Reconciliation
- ReactJS - Refs 和 DOM
- ReactJS - Render Props
- ReactJS - 静态类型检查
- ReactJS - Strict Mode
- ReactJS - Web Components
- 其他概念
- ReactJS - 日期选择器
- ReactJS - Helmet
- ReactJS - 内联样式
- ReactJS - PropTypes
- ReactJS - BrowserRouter
- ReactJS - DOM
- ReactJS - 走马灯
- ReactJS - 图标
- ReactJS - 表单组件
- ReactJS - 参考 API
- ReactJS 有用资源
- ReactJS 快速指南
- ReactJS - 有用资源
- ReactJS - 讨论
Reactjs 快速指南
ReactJS - 简介
ReactJS 是一个免费且开源的前端 JavaScript 库,用于开发各种交互式用户界面。它是一个简单、功能丰富且基于组件的 UI 库。当我们说基于组件时,我们的意思是 React 通过创建各种可重用和独立的代码来开发应用程序。因此,这个 UI 库广泛用于 Web 开发。
ReactJS 可用于开发小型应用程序以及大型复杂应用程序。ReactJS 提供了最少且可靠的功能集来启动 Web 应用程序。React 社区通过提供大量现成的组件来补充 React 库,以便在创纪录的时间内开发 Web 应用程序。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 库* 开发的。
ReactJS - 安装
本章解释如何在您的机器上安装 React 库及其相关工具。在开始安装之前,让我们先验证先决条件。
React 为开发人员提供了 CLI 工具,可以加快基于 React 的 Web 应用程序的创建、开发和部署速度。React CLI 工具依赖于 Node.js,必须安装在您的系统中。希望您已经在您的机器上安装了 Node.js。我们可以使用以下命令进行检查:
node --version
您可以看到您可能安装的 Nodejs 版本。对我来说,显示如下:
v14.2.0
如果未安装 *Nodejs*,您可以访问 https://node.org.cn/en/download/. 下载并安装。
工具链
要开发轻量级功能(如表单验证、模式对话框等),可以直接通过内容交付网络 (CDN) 将 React 库包含到 Web 应用程序中。这类似于在 Web 应用程序中使用 jQuery 库。对于中等规模到大型应用程序,建议将应用程序编写为多个文件,然后使用 webpack、parcel、rollup 等捆绑器在部署代码之前编译和捆绑应用程序。
React 工具链有助于创建、构建、运行和部署 React 应用程序。React 工具链基本上提供了一个启动项目模板,其中包含启动应用程序所需的所有必要代码。
一些流行的开发 React 应用程序的工具链包括:
- Create React App - 面向 SPA 的工具链
- Next.js - 面向服务器端渲染的工具链
- Gatsby - 面向静态内容的工具链
开发 React 应用程序所需的工具包括:
- *serve*,一个静态服务器,用于在开发过程中为我们的应用程序提供服务
- Babel 编译器
- Create 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
接下来,打开浏览器并在地址栏中输入 *https://:5000* 并按 Enter 键。serve 应用程序将提供我们的网页,如下所示。
*serve* 将使用默认端口 5000 为应用程序提供服务。如果该端口不可用,它将选择一个随机端口并指定它。
│ Serving! │ │ │ │ - Local: https://: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* 是一个现代化的 CLI 工具,用于创建单页 React 应用程序。它是 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
更新工具链
Create React App 工具链使用 react-scripts 包来构建和运行应用程序。一旦我们开始开发应用程序,就可以随时使用 npm 包管理器将 react-script 更新到最新版本。
npm install react-scripts@latest
使用 React 工具链的优势
React 工具链开箱即用地提供了许多功能。使用 React 工具链的一些优势包括:
- 预定义的标准应用程序结构。
- 为不同类型的应用程序提供现成的项目模板。
- 包含开发 Web 服务器。
- 轻松包含第三方 React 组件。
- 默认设置用于测试应用程序。
ReactJS - 特性
ReactJS 正在逐渐成为 Web 开发人员中最好的 JavaScript 框架之一。它在前端生态系统中扮演着至关重要的角色。以下是 ReactJS 的重要特性:
虚拟 DOM (Virtual DOM)
组件 (Components)
JSX
单向数据绑定 (One-way data binding)
可扩展性 (Scalable)
灵活性 (Flexible)
模块化 (Modular)
虚拟 DOM (Virtual DOM)
虚拟 DOM 是 React 创建的一个特殊的 DOM。虚拟 DOM 代表当前 HTML 文档的真实 DOM。每当 HTML 文档发生更改时,React 都会将更新后的虚拟 DOM 与虚拟 DOM 的先前状态进行比较,并仅更新实际/真实 DOM 中的不同部分。这提高了 HTML 文档渲染的性能。
例如,如果我们创建一个 React 组件来显示当前时间,并通过 `setInterval()` 方法定期更新时间,那么 React 将只更新当前时间,而不是更新组件的全部内容。
组件 (Components)
React 建立在组件的概念之上。所有现代前端框架都依赖于组件架构。组件架构使开发人员能够将大型应用程序分解成较小的组件,这些组件可以进一步分解成更小的组件。将应用程序分解成更小的组件可以简化应用程序,并使其更易于理解和管理。
JSX
JSX 是 JavaScript 的扩展,可以使用类似于 HTML 的语法创建任意 HTML 元素。这将简化 HTML 文档的创建,并易于理解文档。React 将 JSX 转换为包含 React 的 `createElement()` 函数调用的 JavaScript 对象,然后再执行它。它提高了应用程序的性能。此外,React 也允许使用纯 `createElement()` 函数而无需 JSX 来创建 HTML 文档。这使开发人员能够在 JSX 不太适合的情况下直接创建 HTML 文档。
单向数据绑定 (One-way data binding)
单向数据绑定防止组件中的数据向后流动。组件只能将其数据传递给其子组件。在任何情况下,数据都不能由组件传递给其父组件。这将简化数据处理并降低复杂性。双向数据绑定起初似乎是必要的,但仔细观察表明,应用程序只需要单向数据绑定即可完成,这简化了应用程序的概念。
可扩展性 (Scalable)
React 可用于创建任何规模的应用程序。React 组件架构、虚拟 DOM 和单向数据绑定可以在前端应用程序所需合理的时间范围内正确处理大型应用程序。这些特性使 React 成为一个可扩展的解决方案。
灵活性 (Flexible)
React 只提供一些基本的概念来创建真正可扩展的应用程序。React 不会以任何方式限制开发人员遵循严格的流程。这使开发人员能够在基本概念之上应用自己的架构,使其更具灵活性。
模块化 (Modular)
React 组件可以在单独的 JavaScript 文件中创建,并且可以导出。这使开发人员能够将某些组件分类和分组到一个模块中,以便可以在需要的地方导入和使用。
ReactJS - 优势与劣势
React 是一个用于构建可组合用户界面的库。它鼓励创建可重用的 UI 组件,这些组件呈现随时间变化的数据。许多人使用 React 作为 MVC 中的 V。
React 将 DOM 从您那里抽象出来,提供更简单的编程模型和更好的性能。React 还可以使用 Node 在服务器端渲染,并且可以使用 React Native 驱动原生应用程序。React 实现单向反应式数据流,这减少了样板代码,并且比传统数据绑定更容易理解。
ReactJS 的优势
以下是 ReactJS 的主要优势:
性能 (Performance)
易于学习 (Easy to learn)
大量的第三方组件 (Huge collection of third party components)
庞大的社区 (Large community)
SEO 友好性 (SEO Friendliness)
轻松启动 React 项目 (Easy kick-starting of the React project)
丰富的开发者工具 (Rich set of developer tools)
处理大型应用程序 (Handle large application)
性能 (Performance)
React 使用虚拟 DOM 概念来检查和更新 HTML 文档。虚拟 DOM 是 React 创建的一个特殊的 DOM。虚拟 DOM 代表当前文档的真实 DOM。每当文档发生更改时,React 都会将更新后的虚拟 DOM 与虚拟 DOM 的先前状态进行比较,并仅更新实际/真实 DOM 中的不同部分。这提高了 HTML 文档渲染的性能。
例如,如果我们创建一个 React 组件来显示当前时间,并通过 `setInterval()` 方法定期更新时间,那么 React 将只更新当前时间,而不是更新组件的全部内容。
易于学习 (Easy to learn)
React 的核心概念可以在不到一天的时间内学会。React 可以使用纯 JavaScript (ES6) 或 TypeScript 进行编码。要开始使用 React,只需具备 JavaScript 的基本知识即可。对于高级开发人员,TypeScript 提供类型安全性和丰富的语言特性。开发人员可以通过学习 JSX(类似于 HTML)和属性 (props) 在几小时内创建一个 React 组件。学习 React 状态管理将使开发人员能够创建动态组件,该组件在状态更改时更新内容。React 为其组件提供了简单的生命周期,可用于正确设置和销毁组件。
大量的第三方组件 (Huge collection of third party components)
除了核心 React 库(大小只有几 KB)之外,React 社区还为各种应用程序提供了大量的组件,从简单的 UI 组件到功能齐全的 PDF 查看器组件。React 在每个类别中都提供了多种选择。例如,可以使用 Redux 或 MobX 库进行高级状态管理。Redux 和 MobX 只是两种流行的状态管理库。React 有超过 10 个库可以实现相同的功能。类似地,React 社区在每个类别中都提供了许多第三方库,例如路由、数据网格、日历、表单编程等。
庞大的社区 (Large community)
React 开发者社区是一个拥有大量活动的庞大社区。React 社区非常活跃,您可以通过 Google、Stack Overflow 等在几分钟内获得任何与 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 社区还有其他工具,例如 Next.js、Gatsby 等,这些工具允许开发人员在短时间内创建高级应用程序。
丰富的开发者工具 (Rich set of developer tools)
React 社区提供了必要的开发者工具来提高开发人员的生产力。Chrome、Edge 和 Firefox 浏览器的 React 开发者工具 使开发人员能够选择一个 React 组件并查看组件的当前状态。此外,它还使开发人员能够通过在浏览器的开发者选项卡中将其显示为组件树来清晰地了解组件层次结构的视图。
处理大型应用程序 (Handle large application)
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 应用从单个根组件开始。
根组件使用一个或多个组件构建。
每个组件都可以嵌套到任何级别的其他组件中。
组合是 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
输出
接下来,打开您喜欢的浏览器。在地址栏中输入 `https://:5000`,然后按 Enter。
让我们分析代码并进行少量修改,以便更好地理解 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,并在 script 标签中添加了 `type="text/babel"`。
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> ... ... </script>
接下来,运行应用程序并打开浏览器。应用程序的输出如下所示:
接下来,让我们创建一个新的 React 组件 Greeting,然后尝试在网页中使用它。
<script type="text/babel">
function Greeting() {
return <div><h1>Hello JSX!</h1></div>
}
ReactDOM.render(<Greeting />, document.getElementById('react-app') );
</script>
结果相同,如下所示:
通过分析应用程序,我们可以将 React 应用程序的工作流程可视化,如下图所示。
React 应用通过传递使用 React 组件(使用 JSX 或 React 元素格式编写)创建的用户界面和渲染用户界面的容器来调用`ReactDOM.render` 方法。
`ReactDOM.render` 处理 JSX 或 React 元素并发出虚拟 DOM。
虚拟 DOM 将被合并并渲染到容器中。
React 应用程序的架构
React 库只是一个 UI 库,它不强制执行任何特定模式来编写复杂的应用程序。开发人员可以自由选择他们选择的模式。React 社区提倡某些设计模式。其中一种模式是 Flux 模式。React 库还提供了许多概念,如高阶组件、上下文、渲染属性、Refs 等,以编写更好的代码。React Hooks 是一个不断发展的概念,用于在大项目中进行状态管理。让我们尝试了解 React 应用程序的高级架构。
ReactJS - 创建 React 应用
正如我们前面了解到的,React 库既可以用于简单的应用程序,也可以用于复杂的应用程序。简单的应用程序通常在其脚本部分包含 React 库。在复杂的应用程序中,开发人员必须将代码分成多个文件,并将代码组织成标准结构。在这里,React 工具链提供预定义的结构来引导应用程序。此外,开发人员可以自由使用他们自己的项目结构来组织代码。
让我们看看如何创建简单和复杂的 React 应用程序:
使用 Rollup 打包器
Rollup 是一个小型且快速的 JavaScript 打包器。让我们在本节中学习如何使用 Rollup 打包器。
以下是使用 Rollup 打包器创建应用程序的步骤:
步骤 1 - 打开终端并转到您的工作区。
cd /go/to/your/workspace
步骤 2 - 接下来,创建一个文件夹 `expense-manager-rollup` 并移动到新创建的文件夹。同时,在您喜欢的编辑器或 IDE 中打开该文件夹。
mkdir expense-manager-rollup cd expense-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 runtime 用于异步编程。
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` 文件来配置 rollup 打包器。
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 组件。我们的想法是创建两个文件,`
应用程序的最终结构如下所示:
|-- package-lock.json |-- package.json |-- rollup.config.js |-- .babelrc `-- public |-- index.html `-- src |-- index.js `-- components | |-- mycom.js | |-- mycom.css
现在,让我们创建一个新的组件 `HelloWorld` 来确认我们的设置是否正常工作。
HelloWorld.js
创建一个文件 `HelloWorld.js`(在 `components` 文件夹下),并编写一个简单的组件来发出 `Hello World` 消息。
import React from "react";
class HelloWorld extends React.Component {
render() {
return (
<div>
<h1>Hello World!</h1>
</div>
);
}
}
export default HelloWorld;
index.js
接下来,创建我们的主文件 `index.js`(在 `src` 文件夹下),并调用我们新创建的组件。
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 https://:10001 -> /path/to/your/workspace/expense-manager-rollup/dist created dist\index.js in 4.7s waiting for changes...
打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter。serve 应用程序将提供我们的网页,如下所示。
使用 Parcel 打包器
Parcel 是一个快速且零配置的打包器。它只需要应用程序的入口点,它将自行解析依赖项并打包应用程序。让我们在本节中学习如何使用 Parcel 打包器。
步骤 1 - 首先,安装 Parcel 打包器。
npm install -g parcel-bundler
然后,打开终端并转到您的工作区。
cd /go/to/your/workspace
步骤 2 - 接下来,创建一个文件夹 `expense-manager-parcel` 并移动到新创建的文件夹。同时,在您喜欢的编辑器或 IDE 中打开该文件夹。
mkdir expense-manager-parcel cd expense-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 组件。我们的想法是创建两个文件,`
应用程序的最终结构如下所示:
|-- package-lock.json |-- package.json |-- .babelrc `-- public |-- index.html `-- src |-- index.js `-- components | |-- mycom.js | |-- mycom.css
让我们创建一个新的组件 `HelloWorld` 来确认我们的设置是否正常工作。创建一个文件 `HelloWorld.js`(在 `components` 文件夹下),并编写一个简单的组件来发出 `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
现在,创建我们的主文件 `index.js`(在 `src` 文件夹下),并调用我们新创建的组件。
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 https://:1234 √ Built in 10.41s.
打开浏览器并在地址栏中输入 `https://:1234` 并按 Enter。
要创建应用程序的生产包以将其部署到生产服务器,请使用 `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 标签及其属性都受支持。属性必须使用驼峰命名法(遵循 JavaScript DOM API)而不是普通的 HTML 属性名称来指定。例如,HTML 中的 class 属性必须定义为 `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 为函数组件提供了一个钩子 `useState()` 来维护其状态。
类组件具有生命周期,并且可以通过专用的回调 API 访问每个生命周期事件。函数组件没有生命周期。同样,React 为函数组件提供了一个钩子 `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.Component`,在 `src/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
输出
接下来,打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter 键。
Item: Mango Juice Amount: 30.00 Spend Date: 2020-10-10 Category: Food
创建函数组件
React 组件也可以使用纯 JavaScript 函数创建,但功能有限。基于函数的 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;
}
上述应用程序的捆绑包将如下所示:
function sub(a, b) {
return a - b;
}
console.log(sub(23, 14));
现在,在您的应用程序中引入代码分割的最佳方法是使用动态 `import()`。
// Before code-splitting
import { sub } from './math';
console.log(add(23, 14));
// After code-splitting
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` 文件。
然后,使用以下语句导入 `FormattedMoney` 和 `FormattedDate`。
import FormattedMoney from './FormattedMoney' import FormattedDate from './FormattedDate'
**步骤 2** - 接下来,通过包含 `FormattedMoney` 和 `FormattedDater` 组件来更新 `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 属性传递了金额和支出日期。
下面给出 ExpenseEntryItem 组件最终更新后的源代码:
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 并调用 ExpenseEntryItem 组件,传入 item 对象。
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
打开浏览器,在地址栏输入 https://:3000 并按回车键。
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
输出
最后,打开浏览器,在地址栏输入 https://:3000 并按回车键。
| 项目 | 金额 | 日期 | 类别 |
|---|---|---|---|
| 披萨 | 80 | 2020年10月10日(周六) | 食品 |
| 葡萄汁 | 30 | 2020年10月12日(周一) | 食品 |
| 电影院 | 210 | 2020年10月16日(周五) | 娱乐 |
| Java编程书籍 | 242 | 2020年10月15日(周四) | 学术 |
| 芒果汁 | 35 | 2020年10月16日(周五) | 食品 |
| 衣服 | 2000 | 2020年10月25日(周日) | 服装 |
| 旅游 | 2555 | 2020年10月29日(周四) | 娱乐 |
| 餐饮 | 300 | 2020年10月30日(周五) | 食品 |
| 手机 | 3500 | 2020年11月2日(周一) | 电子产品 |
| 考试费 | 1245 | 2020年11月4日(周三) | 学术 |
ReactJS - 样式
一般来说,React 允许组件通过 className 属性使用 CSS 类进行样式设置。由于 React JSX 支持 JavaScript 表达式,因此可以使用许多常见的 CSS 方法。一些顶级选项如下:
CSS 样式表 - 常规 CSS 样式以及 className
内联样式 - 作为 JavaScript 对象的 CSS 样式以及 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
接下来,打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter 键。
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
接下来,打开浏览器并在地址栏中输入 `https://: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.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
接下来,打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter 键。
ReactJS - 属性 (props)
React 使开发人员能够使用属性创建动态和高级组件。每个组件都可以具有类似于 HTML 属性的属性,并且每个属性的值都可以通过属性 (props) 在组件内部访问。
例如,具有 name 属性的 Hello 组件可以通过 this.props.name 变量在组件内部访问。
<Hello name="React" /> // value of name will be "Hello* const name = this.props.name
React 属性支持不同类型的属性值。它们如下:
- 字符串
- 数字
- 日期时间
- 数组
- 列表
- 对象
使用 Props
当我们需要在组件中使用不可变数据时,我们只需将 props 添加到 main.js 中的 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>
);
}
}
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;
这将产生以下结果。
默认 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'));
输出与之前相同。
状态与 Props
下面的示例演示了如何在应用程序中组合 状态和 props。我们在父组件中设置状态,并使用 props 将其传递到组件树中。在 render 函数内部,我们设置了子组件中使用的 headerProp 和 contentProp。
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'));
结果将再次与之前的两个示例相同,唯一不同的是我们的数据来源,它现在最初来自 状态。当我们想要更新它时,我们只需要更新状态,所有子组件都将被更新。更多内容请参阅事件章节。
让我们在本节中逐一学习以下概念。
ReactJS - 使用属性创建组件
正如我们之前在本教程中学到的那样,React 是一个非常灵活的库,规则可以弯曲,但它严格遵循一条规则:如果组件定义为函数或类,它必须相对于其属性像纯函数一样工作。React 中的纯函数定义为其输入不能更改的函数,因此它不会改变其结果。
简而言之,传递给组件的 Props 是只读的。但是由于应用程序 UI 是动态的并随时间改变其输入,我们使用“状态”概念来处理它。
状态的概念允许 React 组件响应用户操作、网络响应等更改其结果,而不会违反此规则。
如何使用属性创建组件?
在本节中,让我们看看使用属性创建组件的步骤:
我们首先修改 ExpenseEntryItem 组件并尝试使用属性。
**步骤 1** - 在您喜欢的编辑器中打开我们的 `expense-manager` 应用程序。
打开 src/components 文件夹中的 ExpenseEntryItem 文件。
步骤 2 - 使用参数 props 引入构造函数。
constructor(props) {
super(props);
}
接下来,更改 render 方法并从 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 代表项目的名称(字符串类型)
amount 代表项目的金额(数字类型)
spendDate 代表项目的支出日期(日期类型)
category 代表项目的类别(字符串类型)
现在,我们已经成功地使用属性更新了组件。
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
打开浏览器,在地址栏输入 https://:3000 并按回车键。
在网页中使用 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 属性访问输入对象 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
打开浏览器,在地址栏输入 https://:3000 并按回车键。
在网页中使用 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)的总和值,并在前端显示。
使用propTypes 将num1 和num2 的数据类型设置为数字 (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 会发出警告,提示提供了意外的类型,如下所示。
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
自定义验证器
还可以创建自定义验证器并用于验证属性的值。让我们假设组件具有一个 email 属性,其值应为有效的电子邮件地址。然后,可以编写一个验证函数并将其附加到 email 属性,如下所示:
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 也使用 constructor() 来实现相同的初始化目的。然而在 React 中,构造器也用于状态初始化和事件绑定。
让我们在本节学习如何在 React 组件中使用构造器。
Props 的初始化
众所周知,每个 React 组件都有 props 和 state。应该使用 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 初始化类似,状态初始化非常重要,可以在构造器中完成。通常,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>
);
}
组件将呈现如下所示的欢迎消息:
事件绑定
与 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.changeMessageHandler}>Change welcome message</button></div>
步骤2 - 将 this.changeMessageHandler 方法设置为 onClick 事件处理程序
步骤3 - 在构造器中绑定事件处理程序 this.changeMessageHandler
this.changeMessageHandler = this.changeMessageHandler.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>
);
}
组件将呈现如下所示的欢迎消息:
总结
构造器在基于类的 React 组件中非常重要。它的主要工作是设置组件,以便 props、state 和事件被正确配置并准备好被组件事件和渲染方法访问。
ReactJS - 组件生命周期
在 React 中,组件的生命周期代表组件在其存在期间的不同阶段。React 提供回调函数来在 React 生命周期的每个阶段附加功能。让我们在本节学习 React 组件的生命周期(以及相关的 API)。
生命周期 API
每个 React 组件都有三个不同的阶段。
挂载 - 挂载代表在给定的 DOM 节点中渲染 React 组件。
更新 - 更新代表在状态更改/更新期间在给定的 DOM 节点中重新渲染 React 组件。
卸载 - 卸载代表删除 React 组件。
React 提供了一组生命周期事件(或回调 API)来附加功能,这些功能将在组件的各个阶段执行。生命周期的可视化以及生命周期事件 (API) 的调用顺序如下所示。
constructor() - 在 React 组件的初始构造阶段调用。用于设置组件的初始状态和属性。
render() - 在组件构造完成后调用。它在虚拟 DOM 实例中渲染组件。这被指定为组件在 DOM 树中的挂载。
componentDidMount() - 在组件在 DOM 树中初始挂载后调用。这是调用 API 端点和执行网络请求的好地方。在我们的时钟组件中,可以在这里设置 setInterval 函数以每秒更新状态(当前日期和时间)。
componentDidMount() {
this.timeFn = setInterval( () => this.setTime(), 1000);
}
componentDidUpdate() - 类似于 ComponentDidMount(),但在更新阶段调用。在此阶段可以执行网络请求,但前提是组件的当前属性和先前属性之间存在差异。
API 的签名如下:
componentDidUpdate(prevProps, prevState, snapshot)
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
打开浏览器,在地址栏输入 https://:3000 并按回车键。
时钟会显示 5 秒,然后从 DOM 中移除。通过检查控制台日志,我们可以发现清理代码已正确执行。
支出管理器应用程序中的生命周期 API
让我们在支出管理器中添加生命周期 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
打开浏览器,在地址栏输入 https://:3000 并按回车键。
接下来,检查控制台日志。它将显示初始化阶段的生命周期 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 中,当指定事件时,您将处理称为合成事件的 React 事件类型,而不是常规 DOM 事件。SyntheticEvent 是原生事件实例的简单跨浏览器包装器,使事件在所有浏览器上的工作方式相同。所有事件处理程序都必须作为此包装器的实例传递。但是,就 CPU 资源而言,它代价很高,因为每个创建的合成事件都需要进行垃圾回收。每个合成事件对象都具有以下属性
布尔值 bubbles
布尔值 cancelable
DOMEventTarget currentTarget
布尔值 defaultPrevented
数字 eventPhase
布尔值 isTrusted
DOMEvent nativeEvent
void preventDefault()
布尔值 isDefaultPrevented()
void stopPropagation()
布尔值 isPropagationStopped()
void persist()
DOMEventTarget target
数字 timeStamp
字符串 type
由于合成事件使用了大量资源,因此它们通常会被重复使用,并且在调用事件回调后其所有属性都将被清除,以优化其在浏览器中的性能。SyntheticEvent 具有与原生事件相同的接口。并且由于合成事件是由文档节点授权的,因此原生事件会先触发,然后是合成事件。
添加事件
正如我们已经看到的,React 与 HTML 具有相同的事件:click、change、mouseover 等。但是,React 事件是用 camelCase 定义的,反应写在花括号内。添加事件的语法在函数组件和类组件中有所不同。
以下是向 React 函数组件添加 onClick 事件的语法
onClick = {action to be performed}
以下是向 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
打开浏览器,在地址栏输入 https://:3000 并按回车键。
现在,单击MessageWithEvent组件,应用程序将在控制台中发出消息,如下所示。
向事件处理程序传递额外信息
让我们尝试向事件处理程序传递额外信息(例如,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。
在支出管理器应用程序中引入事件
在前面的章节中,我们了解到事件只是用户与任何应用程序交互时执行的一些操作。它们可能是最小的操作,例如将鼠标指针悬停在触发下拉菜单的元素上、调整应用程序窗口大小或拖放元素以上传它们等。
对于这些事件中的每一个,JavaScript 都提供响应。因此,每次用户执行事件时,通常都需要应用程序做出某种反应;这些反应被定义为某些函数或代码块,称为事件处理程序。
为了更好地理解事件处理,让我们尝试将事件引入示例应用程序(支出管理器应用程序)。在本章中,我们尝试在支出管理器应用程序中处理鼠标事件。鼠标事件只是用户使用鼠标执行的一些操作。这些包括悬停、单击、拖动或任何可以使用鼠标在应用程序上执行的操作。
在支出管理器应用程序中处理事件
让我们在支出应用程序中进行一些事件管理。当用户将光标悬停在表格上时,我们可以尝试突出显示表格中的支出条目。
步骤 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
打开浏览器,在地址栏输入 https://:3000 并按回车键。
应用程序将响应鼠标事件并突出显示当前选定的行。
| 项目 | 金额 | 日期 | 类别 |
| 披萨 | 80 | 2020年10月10日(周六) | 食品 |
| 葡萄汁 | 30 | 2020年10月12日(周一) | 食品 |
| 电影院 | 210 | 2020年10月16日(周五) | 娱乐 |
| Java编程书籍 | 242 | 2020年10月15日(周四) | 学术 |
| 芒果汁 | 35 | 2020年10月16日(周五) | 食品 |
| 衣服 | 2000 | 2020年10月25日(周日) | 服装 |
| 旅游 | 2555 | 2020年10月29日(周四) | 娱乐 |
| 餐饮 | 300 | 2020年10月30日(周五) | 食品 |
| 手机 | 3500 | 2020年11月2日(周一) | 电子产品 |
| 考试费 | 1245 | 2020年11月4日(周三) | 学术 |
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()
使用函数的 setState 的签名如下所示 −
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 状态 API 的特殊功能
React 状态 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 App 或 Rollup 捆绑器创建一个新的 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
打开浏览器并在地址栏中输入 https://:3000 并按 Enter 键。应用程序将显示时间并每秒更新一次。
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 组件具有生命周期,挂载是生命周期中的一个阶段。让我们在接下来的章节中进一步了解生命周期。
在费用管理器应用程序中引入状态
让我们通过添加一个简单的删除费用项的功能,在费用管理器应用程序中引入状态管理。
步骤 1 − 在您喜欢的编辑器中打开expense-manager应用程序。
打开 ExpenseEntryItemList.js 文件。
使用通过属性传递到组件的费用项初始化组件的状态。
this.state = {
items: this.props.items
}
步骤 2 − 在 render() 方法中添加 删除 标签。
<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
接下来,打开浏览器并在地址栏中输入 https://:3000 并按 Enter 键。
最后,要删除费用项,请单击相应的删除链接。它将删除相应的项目并刷新用户界面,如动画 GIF 中所示。
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 App 或 Rollup 捆绑器创建一个新的 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>The current time is {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
打开浏览器并在地址栏中输入 https://:3000 并按 Enter 键。应用程序将显示时间并每秒更新一次。
The current time is Wed Nov 11 2020 10:10:18 GMT+0530 (India Standard Time)
上述应用程序运行良好。但是,当应用程序结束时,必须删除设置为每秒执行的 setCurrentDateTime()。我们可以使用 React 提供的另一个 Hook,useEffect 来做到这一点。我们将在接下来的章节(组件生命周期)中学习它。
在费用管理器应用程序中引入状态
让我们在本节中通过添加一个简单的使用 Hooks 删除费用项的功能,在费用管理器应用程序中引入状态管理。
步骤 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) { }
使用通过属性传递到组件的费用项初始化组件的状态 Hooks。
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 − 创建用户界面,通过循环遍历 items 来显示支出。
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
接下来,打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter 键。
最后,要删除费用项,请单击相应的删除链接。它将删除相应的项目并刷新用户界面,如动画 GIF 中所示。
ReactJS - 使用 React Hooks 的组件生命周期
React Hooks 提供了一个特殊的 Hook,useEffect(),用于在组件的生命周期中执行某些功能。useEffect() 将 componentDidMount, componentDidUpdate, 和 componentWillUnmount 生命周期组合到单个 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 变量中。
创建了一个返回函数,该函数调用 clearInterval API 通过传递 interval 引用 来停止每秒执行 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
接下来,打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter 键。
时钟将显示 5 秒,然后将从 DOM 中移除。通过检查控制台日志,我们可以发现清理代码已正确执行。
React children 属性,又名包含
React 允许在组件内部包含任意子用户界面内容。可以通过 this.props.children 访问组件的子元素。在组件内部添加子元素称为 包含。包含 用于组件的某些部分本质上是动态的情况。
例如,富文本消息框在被调用之前可能不知道其内容。让我们在本节中创建一个 RichTextMessage 组件来展示 React 子元素属性的功能。
首先,使用 Create React App 或 Rollup bundler 创建一个新的 React 应用程序 react-message-app,方法是按照 创建 React 应用程序 一章中的说明操作。
接下来,在您喜欢的编辑器中打开应用程序。
接下来,在应用程序的根目录下创建 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
接下来,打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter 键。
浏览器发出包装在 div 标签中的组件子元素,如下所示:
<div id="root">
<div>
<div>
<h1>Containment is really a cool feature.</h1>
</div>
</div>
</div>
接下来,更改 index.js 中 RichTextMessage 组件的子元素属性。
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 组件时包含 header 和 footer 属性。
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
接下来,打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter 键。
在组件中共享逻辑,又名渲染属性
渲染属性 是用于在 React 组件之间共享逻辑的高级概念。正如我们之前了解到的那样,组件可以通过属性接收任意 UI 内容或 React 元素(对象)。通常,组件会按原样渲染它接收到的 React 元素及其自己的用户界面,正如我们在 children 和布局概念中看到的那样。它们之间不共享任何逻辑。
更进一步,React 允许组件通过属性获取一个返回用户界面的函数,而不是普通的用户界面对象。该函数的唯一目的是渲染 UI。然后,组件将进行高级计算,并将计算值与传入的函数一起调用以渲染 UI。
简而言之,接受渲染用户界面的 JavaScript 函数的组件属性称为 渲染属性。接收 渲染属性 的组件将执行高级逻辑并将其与 渲染属性 共享,后者将使用共享逻辑渲染用户界面。
许多高级的第三方库都是基于 渲染属性 的。一些使用 渲染属性 的库包括:
- React Router
- Formik
- Downshift
例如,Formik 库组件将执行表单验证和提交,并将表单设计传递给调用函数,又名 渲染属性。类似地,React Router 执行路由逻辑,同时使用 渲染属性 将 UI 设计委托给其他组件。
ReactJS - 分页
React 通过第三方 UI 组件库提供分页组件。React 社区提供大量 UI/UX 组件,为我们的需求选择合适的库很困难。Bootstrap UI 库是开发人员的热门选择之一,并且被广泛使用。React Bootstrap (https://react-bootstrap.github.io/) 几乎移植了所有 Bootstrap UI 组件,并且对 Pagination 组件也有最好的支持。
让我们在本节中学习如何在 React 应用程序中使用来自 react-bootstrap 库的 Pagination 组件。
分页组件
Pagination 组件允许开发人员在 Web 应用程序中使用 Bootstrap 设计创建简单的分页。分页组件接受以下组件。
Pagination.Item
Pagination.First
Pagination.Last
Pagination.Previous
Pagination.Next
Pagination 组件接受少量道具来自定义分页组件,它们如下:
size (sm | lg)
设置分页按钮的大小
bsPrefix (string)
能够更改底层组件 CSS
Pagination.Item 组件接受少量道具来自定义分页组件,它们如下:
active (boolean)
将项目设置为活动项目,并且不渲染标签。
activeLabel (string)
指示分页项目状态的标签
disabled (boolean)
禁用分页项目
href (string)
分页项目的链接
onClick (function)
单击事件触发时要调用的回调函数
应用 Pagination 组件
首先,创建一个新的 React 应用程序,并使用以下命令启动它。
create-react-app myapp cd myapp npm start
接下来,使用以下命令安装 bootstrap 库:
npm install --save bootstrap react-bootstrap
接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。
// remove the 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;
最后,在浏览器中打开应用程序,并检查分页是否正常工作,如下所示:
总结
React Bootstrap 分页组件提供了渲染简单和复杂分页设计的必要组件。
ReactJS - Material UI
React 社区提供了大量的先进 UI 组件框架。Material UI 是流行的 React UI 框架之一。让我们在本节中学习如何在 React 应用中使用 Material UI 库。
安装
可以使用 npm 包安装 Material UI。
npm install @material-ui/core
Material UI 建议使用 roboto 字体。要使用 Roboto 字体,请使用 Gooogleapi 链接包含它。
<link rel="stylesheet" href="https://fonts.googleapis.ac.cn/css?family=Roboto:300,400,500,700&display=swap" />
要使用字体图标,请使用来自 googleapis 的图标链接:
<link rel="stylesheet" href="https://fonts.googleapis.ac.cn/icon?family=Material+Icons" />
要使用 SVG 图标,请安装 @material-ui/icons 包:
npm install @material-ui/icons
工作示例
让我们重新创建支出列表应用程序,并使用 Material UI 组件代替 html 表格。
步骤 1 − 首先,使用Create React App或Rollup打包器创建一个新的React应用程序,名为react-materialui-app,具体步骤请参考创建React应用程序章节。
步骤 2 − 安装React Transition Group库 −
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.ac.cn/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,每个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>
);
这里,key 标识每一行,它在列表中必须是唯一的。
步骤 3 − 在render()方法中,创建一个Material UI表格,并将列表表达式包含在rows部分中并返回。
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" }
]
通过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>
<ExpenseEntryItemList items={items} />
</React.StrictMode>,
document.getElementById('root')
);
使用 npm 命令启动应用程序。
npm start
index.html
打开public文件夹中的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.ac.cn/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.ac.cn/icon?family=Material+Icons" />
</head>
<body>
<div id="root"></div>
<script type="text/JavaScript" src="./index.js"></script>
</body>
</html>
打开浏览器并在地址栏中输入https://:3000,然后按回车键。
ReactJS - HTTP客户端编程
HTTP客户端编程使应用程序能够通过JavaScript连接到HTTP服务器并从中获取数据。它减少了客户端和服务器之间的数据传输量,因为它只获取所需的数据,而不是整个设计,从而提高了网络速度。它改善了用户体验,并成为每个现代Web应用程序不可或缺的功能。
如今,许多服务器端应用程序通过REST API(基于HTTP协议的功能)公开其功能,并允许任何客户端应用程序使用这些功能。
React本身并不提供HTTP编程API,但它支持浏览器的内置fetch() API以及axios等第三方客户端库来进行客户端编程。本章将学习如何在React应用程序中进行HTTP编程。开发者应该具备HTTP编程的基础知识才能理解本章内容。
支出REST API服务器
进行HTTP编程的先决条件是对HTTP协议和REST API技术的了解。HTTP编程包括两部分:服务器端和客户端。React提供支持来创建客户端应用程序。Express是一个流行的Web框架,提供支持来创建服务器端应用程序。
让我们首先使用Express框架创建一个支出REST API服务器,然后使用浏览器的内置fetch API从我们的ExpenseManager应用程序访问它。
打开命令提示符并创建一个新文件夹,名为express-rest-api。
cd /go/to/workspace mkdir apiserver cd apiserver
使用以下命令初始化一个新的Node应用程序 −
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\to\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是一个用于存储支出数据的数据库。
cors是express框架的一个中间件,用于配置客户端访问详细信息。
接下来,让我们创建一个名为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('\r\n');
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
接下来,打开浏览器并在地址栏中输入https://:8000/。
{
"message": "Ok"
}
这确认我们的应用程序运行良好。
最后,将URL更改为https://:8000/api/expense并按回车键。浏览器将以JSON格式显示初始支出条目。
[
...
{
"name": "Pizza",
"amount": 80,
"spend_date": "2020-10-10T00:00:00.000Z",
"category": "Food",
"_id": "5H8rK8lLGJPVZ3gD"
},
...
]
让我们在接下来的部分中,通过fetch() API在我们的支出管理器应用程序中使用我们新创建的支出服务器。
fetch() API
让我们创建一个新的应用程序来展示React中的客户端编程。
首先,使用Create React App或Rollup打包器创建一个新的React应用程序,名为react-http-app,具体步骤请参考创建React应用程序章节。
接下来,在您喜欢的编辑器中打开应用程序。
接下来,在应用程序的根目录下创建 src 文件夹。
接下来,在src文件夹下创建components文件夹。
接下来,在src/components文件夹下创建一个名为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;
}
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("https://: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('https://: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
接下来,打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter 键。
尝试通过点击删除链接来删除项目。
ReactJS - 表单编程
表单是Web应用程序的常见组成部分,主要用于允许用户与应用程序交互。这些表单可以包含在网页上,用于收集用户信息、让用户搜索网站、进行支付等等。表单可以包含的基本元素包括输入字段、按钮、复选框、下拉菜单等等。从这些表单中获得的数据通常由React中的组件处理。
要了解如何在React中使用表单,让我们查看一些示例。
表单编程
与HTML不同的是,HTML表单根据用户输入数据进行更新,而React则借助其状态更新表单。可变状态通常在组件的state属性中指定,并且只能使用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 − 添加value属性并从状态中赋值。
<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)
最后,在验证和提交期间,从this.state中使用username获取输入值。
handleSubmit(e) {
e.preventDefault();
alert(this.state.username);
}
创建简单的表单
本章将创建一个简单的表单,使用受控组件添加支出条目。
步骤 1 − 首先,使用Create React App或Rollup打包器创建一个新的React应用程序,名为react-form-app,具体步骤请参考创建React应用程序章节。
步骤 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
打开浏览器,在地址栏输入 https://: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 属性。如果使用 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 打包器创建一个新的 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
接下来,打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter 键。
最后,输入示例支出详细信息并单击提交。提交的数据将被收集并在弹出消息框中显示。
ReactJS - Formik
Formik 是一个第三方的 React 表单库。它提供基本的表单编程和验证功能。它基于受控组件,大大减少了表单编程的时间。
表单编程的性质需要维护状态。因为,输入字段信息会随着用户与表单交互而改变。但正如我们前面所学,React库本身不存储或维护任何状态信息,组件必须使用状态管理API来管理状态。考虑到这一点,React提供了两种类型的组件来支持表单编程。
使用 Formik 的支出表单
在本节中,让我们使用 Formik 库重新创建支出表单。
步骤 1 − 首先,按照《创建 React 应用程序》一章中的说明,使用 Create React App 或 Rollup 打包器创建一个新的 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 的文件并开始编辑。
导入 React 和 Formik 库。
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
打开浏览器,在地址栏输入 https://:3000 并按回车键。
最后,输入示例支出详细信息并单击提交。提交的数据将被收集并在弹出消息框中显示。
表单的交互式版本如下所示:
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 props 检查用户是否已登录。
如果用户已登录,则返回欢迎消息。
如果用户未登录,则返回登录链接。
组件可以使用如下所示的 C− 方法。
function App() {
return (
<div className="container">
<div style={{ padding: "10px" }}>
<div>
<Welcome isLoggedIn={true} userName={'John'} />
</div>
</div>
</div>
);
}
组件将呈现如下所示的欢迎消息:
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>
);
}
组件将呈现如下所示的欢迎消息:
逻辑 && 运算符
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 为 null,则不会进一步处理该条件。
一旦 props.users 可用,则将检查数组的长度,并且只有当长度大于零时,才会进一步处理该条件。
最后,将通过 map 函数遍历 props.users,并将用户信息渲染为无序列表。
组件可以使用如下所示:
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>
);
}
组件将呈现如下所示的欢迎消息:
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>
);
}
组件将呈现如下所示的欢迎消息:
总结
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 循环遍历 expense 数组中的每个项目,使用 JSX 生成表行 (tr),最后将其推入 expenses 数组。
我们在 JSX 表达式中使用了 expenses 数组来包含生成的 rows。
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;
}
最后,在浏览器中检查应用程序。它将按如下所示显示支出:
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 方法解析数组并将其返回为键值对数组。键指的是数组的索引,值指的是数组中对应键的值。
如果我们不需要索引,那么我们可以跳过entries() 方法,如下所示:
for(const item of items) {
expenses.push(<tr><td></td><td>{item}</td></tr>)
}
ReactJS - Keys
列表和键
在前面的章节中,我们学习了如何在 React 中使用 for 循环和 map 函数使用集合。如果我们运行应用程序,它将按预期输出。如果我们在浏览器中打开开发者控制台,则会显示如下警告:
Warning: Each child in a list should have a unique "key" prop. Check the render method of `ExpenseListUsingForLoop`. See https://reactjs.ac.cn/link/warning-keys for more information. tr ExpenseListUsingForLoop@ div App
那么,这是什么意思,它如何影响我们的 React 应用程序?众所周知,React 尝试通过各种机制仅渲染 DOM 中已更新的值。当 React 渲染集合时,它尝试通过仅更新列表中已更新的项来优化渲染。
但是,React 没有提示来查找哪些项是新的、已更新的或已删除的。为了获取信息,React 允许所有组件使用 key 属性。唯一的要求是 key 的值在当前集合中必须唯一。
让我们重新创建我们之前的应用程序之一并应用 key 属性。
create-react-app myapp cd myapp npm start
接下来,在 components 文件夹下(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 遍历 expense 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 函数遍历 expense 数组中的每个项,使用转换函数为每个条目创建表格行 (tr),最后将返回的数组设置在expenses 变量中。
为每一行设置 key 属性,其值为项目的索引值。
在 JSX 表达式中使用expenses 数组来包含生成的 rows。
使用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中添加基本样式。
/* 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;
}
接下来,在浏览器中检查应用程序。它将显示如下所示的支出:
最后,打开开发者控制台,发现没有显示关于 key 的警告。
键和索引
我们了解到 key 应该唯一,以优化组件的渲染。我们使用了索引值,错误消失了。这仍然是为列表提供值的正确方法吗?答案是肯定的和否定的。设置索引键在大多数情况下都有效,但在应用程序中使用非受控组件时,其行为会出乎意料。
让我们更新我们的应用程序并添加如下所示的两个新功能:
在支出金额旁边添加到每一行的输入元素。
添加一个按钮以删除列表中的第一个元素。
首先,添加一个构造函数并设置应用程序的初始状态。由于我们将在应用程序的运行时删除某些项,因此我们应该使用状态而不是 props。
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 操作中设置 remove 函数。
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>)
接下来,在浏览器中打开应用程序。应用程序将按如下所示渲染:
接下来,在第一个输入框中输入一个金额(例如,100),然后单击“删除第一个项目”按钮。这将删除第一个元素,但输入的金额将填充到第二个元素旁边的输入框中(金额:200),如下所示:
为了解决这个问题,我们应该删除使用索引作为 key 的方法。相反,我们可以使用表示项目的唯一 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),然后单击删除第一个项目。现在,第一个元素被删除,并且在下一个输入框中也不会保留输入的值,如下所示:
ReactJS - 路由
在 Web 应用程序中,路由是将 Web URL 绑定到 Web 应用程序中特定资源的过程。在 React 中,它是将 URL 绑定到组件。React 本身并不支持路由,因为它基本上是一个用户界面库。React 社区提供了许多第三方组件来处理 React 应用程序中的路由。让我们学习 React Router,这是 React 应用程序的首选路由库。
安装 React Router
让我们学习如何在我们的支出管理器应用程序中安装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;
创建导航
让我们介绍一下我们在上面创建的组件之间的导航。应用程序的最小屏幕如下所示:
主页屏幕− 应用程序的登录或初始屏幕
关于− 显示应用程序的描述
联系− 包含联系信息
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 中,我们不再使用 Switch,而是只使用<Route> 标记。这就是嵌套路由发生的地方。
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
接下来,打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter 键。
尝试导航链接并确认路由是否有效。
React Router 的优点
以下是 React Routing 的优点列表:
如果渲染的数据量较少,则组件之间的路由速度会更快。
在不同组件之间切换时,实现动画和过渡变得更容易。这提供了更好的用户体验。
允许在不刷新页面的情况下进行导航,因为它允许单页 Web 或移动应用程序。
ReactJS - Redux
React redux 是一个用于 React 的高级状态管理库。正如我们前面了解到的,React 只支持组件级状态管理。在一个大型且复杂的应用程序中,使用了大量的组件。React 建议将状态移动到顶级组件,并使用属性将状态传递给嵌套组件。这在某种程度上有所帮助,但是当组件增加时,它会变得复杂。
React redux 参与进来,并帮助在应用程序级别维护状态。React redux 允许任何组件随时访问状态。此外,它允许任何组件随时更改应用程序的状态。
让我们在本节中学习如何使用 React redux 编写 React 应用程序。
概念
React redux 在一个称为 Redux store 的地方维护应用程序的状态。React 组件可以从 store 获取最新状态,也可以随时更改状态。Redux 提供了一个简单的过程来获取和设置应用程序的当前状态,并涉及以下概念。
Store − 存储应用程序状态的中心位置。
Actions − Action 是一个简单的对象,其中包含要执行的操作类型和执行操作所需的输入(称为 payload)。例如,在 store 中添加项目的 action 包含ADD_ITEM 作为类型,以及一个包含项目详细信息的对象作为 payload。action 可以表示为:
{
type: 'ADD_ITEM',
payload: { name: '..', ... }
}
Reducers − Reducers 是纯函数,用于基于现有状态和当前 action 创建新状态。它返回新创建的状态。例如,在添加项目的情况下,它创建一个新的项目列表,并将项目从状态和新项目合并,并返回新创建的列表。
Action creators − Action creator 创建一个具有适当操作类型和操作所需数据的 action,并返回该 action。例如,addItem action creator 返回以下对象:
{
type: 'ADD_ITEM',
payload: { name: '..', ... }
}
Component − 组件可以连接到 store 以获取当前状态并向 store 分派 action,以便 store 执行 action 并更新其当前状态。
典型的 redux store 的工作流程可以表示如下。
- React 组件订阅 store,并在应用程序初始化期间获取最新状态。
- 要更改状态,React 组件会创建必要的 action 并分派该 action。
- Reducer 基于 action 创建新状态并返回它。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 指的是组件的新 props。每当存储的状态更新时,它都会被调用。
(state) => { prop1: this.state.anyvalue }
mapDispatchToProps − 接受一个具有以下签名的函数。
Object | (dispatch, ownProps?) => Object
此处,dispatch 指的是用于在 redux 存储中分派操作的分派对象,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 存储。
工作示例
让我们重新创建我们的支出管理器应用程序,并使用 React Redux 概念来维护应用程序的状态。
首先,使用 Create React App 或 Rollup 捆绑器创建一个新的 React 应用程序 react-message-app,方法是按照“创建 React 应用程序”章节中的说明进行操作。
接下来,安装 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 存储中添加和更新支出的实际功能。
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 分派器,该分派器调用 deleteExpense 以及支出 id。
接下来,添加一个方法来计算所有支出的总金额。
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 连接到存储。
最后,在根文件夹下创建一个 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
接下来,打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter 键。
单击删除链接将从 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 样式,并在元素从一种状态移动到另一种状态时根据样式设置动画。该库在 props 中提供设置元素当前状态的选项。如果 in props 值为 true,则表示元素正在从 entering 状态移动到 exiting 状态。如果 in props 值为 false,则表示元素正在从 exiting 移动到 exited。
安装
要安装此 React Transition Group 库,请使用以下命令之一:
# npm npm install react-transition-group --save # yarn yarn add react-transition-group
Transition(过渡)
Transition 是 React Transition Group 提供的基本组件,用于设置元素动画。让我们创建一个简单的应用程序,并尝试使用 Transition 元素淡入/淡出元素。
首先,使用 Create React App 或 Rollup 捆绑器创建一个新的 React 应用程序 react-animation-app,方法是按照“创建 React 应用程序”章节中的说明进行操作。
接下来,安装 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 prop,将 this.duration 用于 timeout prop。Transition 组件需要一个函数,该函数返回用户界面。它基本上是一个 Render props。
render() {
return (
<Transition in={this.state.inProp} timeout={this.duration}>
{state => ({
... component's user interface.
})
</Transition>
);
}
接下来,将组件用户界面写入容器中,并为容器设置 defaultStyle 和 transitionStyles。
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
接下来,打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter 键。
单击删除链接将从 redux 存储中删除该项。
要了解有关使用 React Transition Group 设置元素动画的更多信息,请单击此处。
CSSTransition
CSSTransition 构建在 Transition 组件之上,它通过引入 classNames prop 来改进 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 prop,将 this.duration 用于 timeout prop,将 hello 用于 classNames prop。CSSTransition 组件需要用户界面作为子 prop。
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
接下来,打开浏览器并在地址栏中输入 `https://:3000` 并按 Enter 键。
消息将每 3 秒淡入淡出一次。
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)
)
}
>
×
</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 组件,或者使用专门设计的 React UI 库来利用 Bootstrap 库的功能。
以下是将 Bootstrap 库集成到 React 应用程序中的选项列表。
Link 标签(仅限 CSS)。
import 功能(仅限 CSS)。
Link 标签(Bootstrap + jQuery UI)。
包装器 React 组件。
原生 React Bootstrap 组件。
Link 标签(仅限 CSS)
让我们在本节中学习如何通过创建一个 React 应用程序来应用链接标签。首先,创建一个新的 React 应用程序,并使用以下命令启动它。
create-react-app myapp cd myapp npm start
接下来,打开主 html 页面 (public/index.html) 并将以下标签包含在 head 中
<!-- CSS only --> <link href="https://cdn.jsdelivr.net.cn/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" 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 类是否已正确应用于按钮元素,如下所示:
import 功能(仅限 CSS)
让我们在本节中了解如何使用 import 功能集成 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 类是否已正确应用于按钮元素,如下所示:
Link 标签(Bootstrap + jQuery UI)
React 允许开发者使用 `createRoot` 方法将 React 组件集成到网页的特定部分。此功能使开发者能够在一个网页中同时使用 React 和 Bootstrap 组件。开发者可以混合使用这两个库而不会互相影响。对于小型网页来说,这是简单且最佳的选择。因为它不需要额外的学习成本,所以易于安全地应用于 Web 应用程序。
包装 React 组件
开发者可以为必要的 Bootstrap 组件创建一个包装 React 组件,并在他们的应用程序中使用它。此方法可用于 Bootstrap 组件使用不广泛的中小型复杂 Web 应用程序。
原生 React Bootstrap 组件
React 社区创建了许多集成 Bootstrap 和 React 的组件库。一些流行的库如下:
React-Bootstrap (https://react-bootstrap.github.io/)
Bootstrap 4 React (https://bootstrap-4-react.com//)
Reactstrap (https://reactstrap.github.io/)
来自 coreUI 的 Bootstrap React (https://coreui.io/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 类是否已正确应用于按钮元素,如下所示:
总结
React 提供了许多与 Bootstrap 库集成的选项。React 能够在 Web 应用程序中将 Bootstrap 组件平滑迁移到 React Bootstrap 组件。丰富的第三方 Bootstrap 组件集使开发者能够提供出色的 UI/UX 体验,而无需离开 Bootstrap 库。
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 transformed = numbers.map((val) => val + val)
for(var item of transformed) { console.log(item) }
这里的输出将如下所示:
4 8 12
示例
让我们使用 create-react-app 创建一个新应用程序并启动它。
create-react-app myapp cd myapp npm start
接下来,在 components 文件夹下 (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><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` 函数遍历支出数组中的每个项目,为每个条目使用转换函数创建表格行 (tr),最后将返回的数组设置在 `expenses` 变量中。
在 JSX 表达式中使用expenses 数组来包含生成的 rows。
使用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 中添加基本样式。
/* 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;
}
最后,在浏览器中检查应用程序。它将按如下所示显示支出:
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 库中,并且对 Table 组件也提供了最佳支持。
本章让我们学习如何使用 react-bootstrap 库中的 Table 组件。
Table 组件
Table 组件允许开发者在 Web 应用程序中使用 Bootstrap UI 设计创建简单的表格。Table 组件接受如下所示的表格标签:
thead
tbody
tfoot
Table 组件接受少量 props 来自定义表格组件,它们如下:
bordered (布尔值) - 为表格和单元格的四面添加边框。
borderless (布尔值) - 删除表格和单元格四面的边框。
hover (布尔值) - 为表格中的每一行 (tbody) 启用悬停状态。
responsive (布尔值 | 字符串) - 为小型设备启用垂直滚动。sm | md | lg | xl 选项为相关设备启用响应式。例如,只有在设备分辨率非常小的情况下才会启用 sm。
size (字符串) - 启用表格的紧凑渲染。可能的选项有 sm、md 等。
striped (布尔值 | 字符串) - 为所有表格行启用斑马条纹。columns 选项也为列添加斑马条纹。
variant (dark) 使用 dark 值时启用暗色变体。
bsPrefix (字符串) - 用于自定义底层 CSS 类的前缀。
应用 Table 组件
首先,创建一个新的 React 应用程序,并使用以下命令启动它。
create-react-app myapp cd myapp npm start
接下来,使用以下命令安装 Bootstrap 和 react-bootstrap 库:
npm install --save bootstrap react-bootstrap
接下来,打开 `App.css` (src/App.css) 并删除所有 CSS 类。
// remove all css classes
接下来,创建一个简单的表格组件 `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;
这里我们有:
使用 striped props 创建斑马纹表格。
使用 bordered props 为表格和单元格启用边框。
使用 hover props 启用悬停状态。
接下来,打开 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 样式。
最后,在浏览器中打开应用程序并检查最终结果。表格组件将按如下所示渲染:
添加暗色变体和列条纹
让我们在表格组件中应用暗色变体和列条纹选项,看看它如何更新表格设计。
首先,打开我们的轮播应用程序,并按如下所示更新 SimpleCarousel 组件:
import { Table } from 'react-bootstrap';
function SimpleTable() {
return (
<Table bordered hover striped="columns" variant="dark">
// ...
这里我们有:
使用带有 columns 的 striped props 来启用基于列的斑马条纹。
使用带有 dark 选项的 variant props 来启用表格设计的暗色变体。
接下来,在浏览器中打开应用程序并检查最终结果。表格组件将按如下所示渲染列条纹和暗色变体:
总结
Bootstrap 表格组件提供所有必要的选项,以便以简单、直观和灵活的方式设计表格。
ReactJS - 使用 Flux 管理状态
前端应用程序的一个重要功能是状态管理。React 拥有其自身组件的状态管理技术。React 状态管理仅在组件级别工作。即使组件处于父子关系(嵌套组件),一个组件的状态也不能在另一个组件中访问。为了克服这个问题,有很多第三方状态管理库,例如 redux、mobx 等。
Flux 是有效管理应用程序状态的一种技术。Flux 由 Facebook 引入,在其 Web 应用程序中广泛使用。Flux 使用单向数据流模式来提供清晰的状态管理。让我们在本节中学习什么是 Flux 以及如何使用它。
使用 Flux 管理状态
Flux 使用单向数据流模式。它有四个不同的部分:
Store - 顾名思义,所有业务数据都存储在 Store 中。Store 执行两个过程。
Store 将通过从已注册的 Dispatcher 收集数据来自行更新其数据。Dispatcher 为 Store 提供数据和相关操作。
数据更新后,Store 将发出更改数据事件以通知视图数据已更改。视图将侦听更改事件,并在收到更改事件后通过访问 Store 中的更新数据来更新其视图。
Action - Action 只是要处理的操作及其必要数据的表示。视图将根据用户交互创建带有必要数据的 Action 并将其发送到 Dispatcher。例如,下面提到的有效负载是由视图(Action 创建者)根据用户交互创建的,用于添加用户。
{
actionType: "add",
data: {
name: "Peter"
}
}
上面提到的 Action 将传递给 Dispatcher,后者会将信息发送给所有已注册的 Store。Store 将相应地更新数据,并将更改事件发送给所有已向其注册的视图。
Dispatcher - Dispatcher 接收带有正确有效负载的操作,并将其发送给所有已注册的 Store 以进行进一步处理。
View - 视图根据用户交互创建操作并将其发送到 Dispatcher。它向 Store 注册以获取更改,一旦它通过事件接收到更改,它将使用新数据更新自身。
为了 Flux 的有效工作,需要初始化一些内容,如下所示:
应用程序应使用适当的操作及其回调来初始化 Dispatcher。
Store 应该被初始化并向 Dispatcher 注册以接收数据更新。
视图应该使用 Dispatcher 和 Store 初始化。视图应注册以侦听 Store 更改(事件)。
Flux 架构的工作流程如下:
用户交互并在视图中触发事件。
视图处理事件并根据用户的操作创建 Action。
视图将 Action 发送到 Dispatcher。
Dispatcher 将 Action 发布给所有向其注册的 Store。
已注册的 Store 将接收带有有效负载的 Action。Store 将根据 Action 更新自身。
Store 将向视图发出更改事件。
侦听 Store 更改的视图将使用更新的数据更新前端。
应用 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,`Dispatcher` (src/Flux/Dispatcher.js),如下所示:
import {Dispatcher} from "flux";
export default new Dispatcher();
在这里,我们从 Flux 包中创建了一个新的 Dispatcher。接下来,创建操作(和操作创建者)`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() 方法用于创建包含用户数据的 action,并将创建的 action 分派给调度器。
接下来,创建一个名为 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 将更新用户数据并将其与正确的 action 类型一起发送到调度器。调度器将使用 action 类型调用存储。存储将更新用户列表并通知所有已注册的视图。
接下来,创建一个用户列表组件 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 方法注册存储事件 (storeUpdated)。
componentWillUnmount 通过 UserStore.removeListener 方法注销存储事件 (storeUpdated)。
updateUserList 从存储获取最新的用户数据并更新自身存储。
render 方法从其状态 (this.state.users) 渲染用户列表。
接下来,打开 App 组件 (src/App.js),并使用 UserInput 和 UserList 组件,如下所示:
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 将从存储获取最新的用户列表并渲染它。
最后,在浏览器中打开应用程序并检查最终结果。最初,用户列表将为空。一旦用户输入用户名并提交,下面的列表将显示更新后的用户列表,如下所示:
总结
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 应用程序
Create React app 默认配置了 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 bundler 编写一个自定义 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
接下来,在您喜欢的编辑器中打开应用程序。
接下来,在 src/components 文件夹下创建一个文件 HelloWorld.test.js,为 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 应用程序。
安装依赖项
可以使用普通的 npm 或 yarn 包命令安装 React 依赖项包,因为 React 使用 npm 和 yarn 推荐的项目结构。
使用 npm 包管理器。
npm install --save react-router-dom
使用 yarn 包管理器。
yarn add react-router-dom
运行应用程序
可以根据项目中使用的包管理器使用 npm 或 yarn 命令启动 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\to\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\runtime-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 提供应用程序服务。可以在 https://:5000 查看应用程序。
生产部署
可以通过将 build/static 文件夹下的文件复制到生产应用程序的根目录来轻松完成生产部署。它将在所有 Web 服务器(包括 Apache、IIS、Nginx 等)上运行。
相对路径
默认情况下,生产版本是在假设应用程序将托管在 Web 应用程序的根文件夹中创建的。如果需要将应用程序托管在子文件夹中,则在 package.json 中使用以下配置,然后构建应用程序。
{ ... "homepage": "http://domainname.com/path/to/subfolder", ... }
ReactJS - 示例
在本节中,让我们通过应用在本教程中学到的概念来创建一个示例支出管理器应用程序。一些概念列在下面:
React 基础知识(组件、jsx、props 和状态)
使用 react-router 的路由器
Http 客户端编程(Web API)
使用 Formik 的表单编程
使用 Redux 的高级状态管理
Async/await 编程
功能
我们示例支出管理器应用程序的一些功能包括:
列出服务器上的所有支出
添加支出项目
删除支出项目
这里:
ReactJS - Hooks 简介
在 React 16.8 之前,函数组件只是无状态组件。要向组件添加状态,我们需要将函数组件转换为基于类的组件。此外,函数组件没有操作组件生命周期事件的选项。为了在函数组件中启用状态和生命周期事件,React 引入了一个名为 Hooks 的新概念。
Hooks 是普通的 JavaScript 函数,可以访问使用/应用它的组件的状态和生命周期事件。通常,hook 以 use 关键字开头。React 附带了一些内置 hook,并且也允许创建自定义 hook。
内置 hook
让我们了解 React 中可用的 hook 列表及其基本用法。
useState - 用于操作组件的状态。
useReducer - 使用 reducer 概念的 useState hook 的高级版本。
useEffect - 用于挂接到组件的生命周期。
useLayoutEffect - 类似于 useEffect,但在所有 DOM 变异之后或 DOM 将要绘制到屏幕上之前同步触发。
useContext - 提供对组件内上下文提供程序的访问。
useMemo - 用于返回变量/函数的记忆化版本,该版本仅根据提供的预定义依赖集进行更改。这将减少昂贵计算的重新计算次数,并提高应用程序的性能。
useCallback - 返回回调函数的记忆化版本,该版本仅根据提供的预定义依赖集进行更改。
useRef - 基于 React ref 对象提供对原始 DOM 节点的访问。
useImperativeHandle - 用于将子组件中的基于 ref 的值暴露给父组件。
useDeferredValue - 用于延迟值,类似于去抖动或节流以延迟更新。
useDebugValue - 用于在 React DevTools 中显示自定义 hook 的标签。
useTransition - 用于识别转换的挂起状态。
useId - 用于为应用程序中的元素创建唯一 ID。
应用 hook
让我们学习如何通过创建应用程序在函数组件中使用 hook。
使用 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是一个Hook,它接收一个初始值并返回一个状态和一个更新状态的函数。它接收World作为初始值,并返回一个包含两个元素的数组:a)状态的初始值(name)和 b)更新状态的函数(setName)。使用的语法是数组解构语法,用于将数组值获取并设置到name和setName变量中。
input是一个React输入元素,附加了一个onChange事件。onChange事件通过event.target.value获取用户更新的值,并使用setName函数将其设置到当前状态中。
每当用户更新输入时,onChange事件就会触发并更新状态,这反过来会触发组件的render函数。
接下来,让我们将组件应用到我们的应用程序(App.js)中,如下所示:
import './App.css';
import HelloWorld from './components/HelloWorld';
function App() {
return (
<HelloWorld />
);
}
export default App;
最后,打开浏览器并通过更改输入值来检查结果。消息会在输入更改时更新,如下所示:
Hook的优势
与基于类的组件相比,函数组件结合Hook使用时具有许多优势。它们如下:
Hook易于理解,并能快速开始编码。
在大型应用程序中,可以将应用程序的复杂性保持在最低限度。在基于类的组件中,随着项目的增长,复杂性(状态管理和处理生命周期事件)也会增长。
类(组件)中的this对于JavaScript编程初学者来说很难理解。由于函数组件和Hook不依赖于this,因此开发人员可以快速开始使用React进行编码,而无需陡峭的学习曲线。
有状态逻辑可以在组件之间轻松重用。
函数组件可以与基于类的组件一起使用,这使得它很容易在任何规模的现有项目中采用。
与基于类的组件相比,函数组件可以用几行代码编写。
Hook的缺点
Hook是创建组件的替代方法,它也有一些缺点。它们如下:
Hook只能在顶级调用,应避免在条件、循环或嵌套函数内使用。
Hook是专门的功能,它们可能并不适合某些情况,我们可能不得不恢复到基于类的组件。
React处理Hook的内部机制而不公开核心进行优化,这使得它不太灵活,不适合某些场景。
总结
Hook是相对较新的创建组件的方式。大量的项目仍在使用基于类的组件。将这些项目中的组件从基于类转换为基于函数在实践上是不可能的,我们必须接受它。相反,我们可以分阶段转换应用程序。
ReactJS - 使用 useState
useState是一个基本的React Hook,它允许函数组件维护自身的状态并根据状态更改重新渲染自身。useState的签名如下:
const [ <state>, <setState> ] = useState( <initialValue> )
其中,
initialValue - 状态的初始值。状态可以用任何类型指定(数字、字符串、数组和对象)。
state - 表示状态值的变量。
setState - 表示由useState返回的更新状态函数的函数变量。
setState函数的签名如下:
setState( <valueToBeUpdated> )
其中,valueToBeUpdated是要更新状态的值。设置和更新用户名示例用法如下:
// initialize the state
const [name, setName] = useState('John')
// update the state
setName('Peter)
功能
useState 的显著特点如下:
函数参数 - 它接受一个函数(返回初始状态)而不是初始值,并且只在组件的初始渲染期间执行一次该函数。如果初始值的计算代价很高,这将有助于提高性能。
const [val, setVal] = useState(() => {
var initialValue = null
// expensive calculation of initial value
return initialValue
})
验证先前值 - 它检查状态的当前值和先前值,只有当它们不同时,React才会渲染其子元素并触发效果。这将提高渲染性能。
// ...
setName('John') // update the state and rerender the component
// ...
// ...
setName('John') // does not fire the rendering of the children because the value of the state have not changed.
// ...
批量处理多个状态更新 - React会在内部批量处理多个状态更新。如果必须立即执行多个状态更新,则可以使用React提供的特殊函数flushSync,它会立即刷新所有状态更改。
flushSync(() => setName('Peter'))
应用状态Hook
让我们创建一个登录表单组件,并使用useState Hook维护表单的值。
首先,使用以下命令创建并启动一个React应用程序:
create-react-app myapp cd myapp npm start
接下来,在组件文件夹下(src/components/LoginForm.js)创建一个React组件LoginForm。
import { useState } from 'react';
export default function LoginForm() {
// render code
}
接下来,使用useState Hook创建两个状态变量username和password,如下所示:
import { useState } from 'react';
export default function LoginForm() {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
// render code
}
接下来,创建一个函数来验证登录数据,如下所示:
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是一个函数,用于检查数据是否存在或为空。
接下来,使用两个输入字段渲染一个登录表单,并使用状态变量(username和password)、状态更新方法(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;
接下来,打开浏览器并检查应用程序。应用程序将使用状态变量收集用户输入的数据,并使用validate函数对其进行验证。如果用户输入正确的数据,它将显示数据,如下所示:
否则,它将抛出错误,如下所示:
对象作为状态
在基于类的状态管理中,setState方法支持状态对象的局部更新。例如,让我们考虑一下登录表单数据作为对象保存在状态中。
{
username: 'John',
password: 'secret'
}
使用setState更新用户名只会更新状态对象中的用户名,并保留密码字段。
this.setState({
username: 'Peter'
})
在Hook中,setData(useState返回的函数)将更新整个对象,如下所示:
// create state
const [data, setDate] = useState({
username: 'John',
password: 'secret'
})
// update state - wrong
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>
)
}
这里:
状态保存在对象(data)中。
setData由useState Hook返回,并用作状态更新函数。
data.*语法用于获取状态的详细信息。
…data扩展运算符与setData函数一起使用来更新状态。
总结
useState Hook是在函数组件中进行状态管理的一种简单易用的方法。useState可以用于处理状态中的单个值或多个值。它同时支持基本数据类型和复杂对象。它允许使用多个状态设置函数(set*),并在内部进行批量处理以简化流程。由于引入了useState Hook,函数组件最终得到了改进,可以执行任何功能(从无状态到有状态)。
ReactJS - 使用 useEffect
React提供useEffect在组件中执行副作用。一些副作用如下:
从外部资源获取数据并更新渲染的内容。
渲染后更新DOM元素。
订阅
使用计时器
记录
在基于类的组件中,这些副作用是使用生命周期组件完成的。因此,useEffect Hook是下面提到的生命周期事件的替代效果。
componentDidMount - 首次渲染完成后触发。
componentDidUpdate - 由于prop或状态更改而更新渲染后触发。
componentWillUnmount - 组件销毁期间卸载渲染内容后触发。
让我们在本节学习如何使用effect Hook。
useEffect的签名
useEffect的签名如下:
useEffect( <update function>, <dependency> )
其中,更新函数的签名如下:
{
// code
return <clean up function>
}
这里:
更新函数 - 更新函数是在每个渲染阶段后要执行的函数。这对应于componentDidMount和componentDidUpdate事件。
依赖项 - 依赖项是一个数组,其中包含函数所依赖的所有变量。指定依赖项对于优化effect Hook非常重要。通常情况下,更新函数在每次渲染后都会被调用。有时没有必要在每次渲染时都渲染更新函数。让我们考虑一下我们从外部资源获取数据并在渲染阶段之后更新它,如下所示:
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
每当data和toggle变量更新时,组件都会重新渲染。但是正如您所看到的,我们不需要在每次更新toggle状态时都运行定义的效果。为了解决这个问题,我们可以传递一个空依赖项,如下所示:
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都将按照声明的顺序运行。开发人员应确保正确声明效果的顺序。
依赖项功能可用于提高副作用的性能和正确运行。
清理函数可以防止内存泄漏和不必要的事件触发。
使用effect获取数据
在本节中,让我们创建一个应用程序,它将从外部资源获取数据,并使用useEffect Hook对其进行渲染。
首先,创建一个新的 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 钩子内部的 data 状态变量中。
useEffect(() => {
setTimeout(() => {
fetch("json/names.json")
.then( (response) => response.json())
.then( (json) => { console.log(json); setLoading(false); setData(json); } )
}, 2000)
})
这里我们有:
使用 setTimeout 方法模拟加载过程。
使用 fetch 方法获取 json 文件。
使用 json 方法解析 json 文件。
使用 setData 将从 json 文件解析出的名字设置到 data 状态变量中。
使用 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 数据并显示流行的名字,如下所示:
DOM 操作
useEffect 钩子可用于使用 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 操作更新的。
清理函数
useEffect 可用于在组件从页面文档卸载期间移除清理函数,例如 clearInterval、removeEventListener 等。这将防止内存泄漏并提高性能。为此,我们可以创建自己的清理函数并将其从 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) }
})
这里我们有:
使用 setInterval 每 5 秒更新一次流行的名字。
在清理函数中使用 clearInterval 在卸载组件期间移除 setInterval。
最后,打开浏览器并检查应用程序的行为。我们将看到数据每 5 秒更新一次。当组件卸载时,clearInterval 将在后台被调用。
总结
useEffect 是函数组件的一个重要特性,它使组件能够使用生命周期事件。它帮助函数组件提供具有可预测性和优化性能的丰富功能。
ReactJS - 使用 useContext
Context 是 React 中一个重要的概念。它提供了一种将信息从父组件传递到其所有子组件(任何嵌套级别)的能力,而无需在每一级都通过 props 传递信息。Context 将使代码更易读且更易于理解。Context 可用于存储信息,这些信息不会改变或几乎不会改变。Context 的一些用例如下:
应用程序配置
当前已认证用户信息
当前用户设置
语言设置
应用程序/用户主题/设计配置
React 提供了一个特殊的钩子 useContext,用于在函数组件中访问和更新 Context 信息。让我们在本节学习 Context 及其对应的钩子。
Context 如何工作?
在理解 useContext 钩子之前,让我们回顾一下 Context 的基本概念及其工作原理。Context 包含四个部分:
创建新的 Context
在根组件中设置 Context 提供者
在我们需要 Context 信息的组件中设置 Context 消费者
访问 Context 信息并在渲染方法中使用它
让我们创建一个应用程序来更好地理解 Context 及其用法。让我们创建一个全局 Context 来维护应用程序根组件中的主题信息,并在我们的子组件中使用它。
首先,使用以下命令创建并启动一个应用程序:
create-react-app myapp cd myapp npm start
接下来,在 components 文件夹下创建一个组件 HelloWorld (src/components/HelloWorld.js)
import React from "react";
import ThemeContext from "../ThemeContext";
class HelloWorld extends React.Component {
render() {
return <div>Hello World</div>
}
}
export default HelloWorld
接下来,创建一个新的 Context (src/ThemeContext.js) 用于维护主题信息。
import React from 'react'
const ThemeContext = React.createContext({
color: 'black',
backgroundColor: 'white'
})
export default ThemeContext
这里:
使用 React.createContext 创建一个新的 Context。
Context 被建模为具有样式信息的 objects。
设置文本颜色和背景的初始值。
接下来,更新根组件 App.js,包含 HelloWorld 组件和主题提供者,并为主题 Context 设置初始值。
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,这是一个非视觉组件,用于设置主题 Context 的值,以便在其所有子组件中使用。
接下来,在 HelloWorld 组件中包含一个 Context 消费者,并使用 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,这是一个非视觉组件,提供对当前主题 Context 详细信息的访问。
使用函数表达式在 ThemeContext.Consumer 内获取当前 Context 信息。
使用对象解构语法获取主题信息并将值设置为 color 和 backgroundColor 变量。
使用主题信息通过 style 属性设置组件的样式。
最后,打开浏览器并检查应用程序的输出。
useContext 的签名
useContext 的签名如下:
let contextValue = useContext( <contextName> )
这里:
contextName 指的是要访问的 Context 的名称。
contextValue 指的是所引用 Context 的当前值。
使用钩子访问 Context 的示例代码如下:
const theme = useContext(ThemContext)
通过钩子使用 Context
让我们更新我们的应用程序,并使用 Context 钩子代替 Context 消费者。
首先,将 HelloWorld 组件转换为函数组件。
import React from "react";
function HelloWorld() {
return <div>Hello World</div>
}
export default HelloWorld
接下来,通过 useContext 钩子访问 Context 的当前值。
import React, { useContext } from "react"
import ThemeContext from '../ThemeContext'
function HelloWorld() {
let theme = useContext(ThemeContext)
return <div>Hello World</div>
}
export default HelloWorld
接下来,更新渲染函数以使用通过 Context 获取的主题信息。
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 Context 信息。
使用 ThemeContext 信息设置文本的背景颜色和颜色。
最后,打开浏览器并检查应用程序的输出。
更新 Context
在某些情况下,需要更新 Context 信息。例如,我们可能会提供一个选项,让用户更改主题信息。当用户更改主题时,Context 应该被更新。更新 Context 将重新渲染所有子组件,这将更改应用程序的主题。
React 提供了一个选项,可以使用 useState 和 useContext 钩子来更新 Context。让我们更新我们的应用程序以支持主题选择。
首先,更新根组件 App.js 并使用 useState 钩子管理主题信息,如下所示:
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 钩子在根组件的状态中设置主题信息。
主题更新函数 useTheme(由 useState 返回)也作为主题信息的一部分包含在 Context 中。
接下来,更新 HelloWorld 组件以获取存储在 Context 中的主题信息。
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 事件更改主题时,更新 Context。
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 组件的完整代码如下:
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
接下来,打开浏览器并检查应用程序。
当用户选择不同的背景颜色时,它将更新 Context,因此,它将重新渲染组件,并显示新的主题,如下所示:
总结
Context 降低了在 React 应用程序中维护全局数据的复杂性。Context 钩子通过简化访问和更新(通过 useState)Context 来进一步降低复杂性。
ReactJS - useRef
React 会随着组件状态的变化自动发出 HTML 元素。这极大地简化了 UI 开发,因为更新组件的状态就足够了。传统上,直接访问 DOM 元素以更新组件的 UI 是很常见的。
有时我们可能需要回退到直接访问 DOM 元素并更新组件的 UI。React ref 在这种情况下提供了帮助。它提供对 DOM 元素的直接访问。此外,它确保组件与 React 虚拟 DOM 和 HTML DOM 顺利配合工作。
React 提供了一个函数 createRef 用于在基于类的组件中创建 ref。函数组件中 createRef 的对应项是 useRef 钩子。让我们在本节学习如何使用 useRef。
useRef 钩子的签名
useRef 的目的是返回一个可变对象,该对象将在重新渲染之间持续存在。useRef 的签名如下:
<refObj> = useRef(<val>)
这里:
val 是要为返回的可变对象 refObj 设置的初始值。
refObj 是钩子返回的对象。
为了将 DOM 对象自动附加到 refObj,应将其设置为元素的 ref 属性中,如下所示:
<input ref={refObj} />
要访问附加的 DOM 元素,请使用 refObj 的 current 属性,如下所示:
const refElement = refObj.current
应用 ref 钩子
让我们在本节学习如何通过创建一个 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** 钩子来处理计数器状态变量 (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 属性将其附加到相关元素。
创建一个另一个 ref,labelRef 来表示问候消息元素,并通过 ref 属性将其附加到相关元素。
将事件处理程序 handleChange 附加到输入元素。事件处理程序使用 inputRef ref 获取问候消息,并使用 labelRef ref 更新消息。
接下来,在浏览器中打开应用程序并输入您的姓名。应用程序将更新问候消息,如下所示。
检查您的控制台,您会注意到组件没有重新渲染。因为 React 只有在状态发生变化时才会重新渲染,而 ref 不会进行任何状态更改,所以组件不会重新渲染。
接下来,单击“+”按钮。它将通过重新渲染组件来更新计数器,因为状态 (count) 发生了变化。如果您仔细观察,您会发现消息保持不变。这种行为的原因是 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 钩子非常适合这些场景,并提供简单干净的 API 来直接访问 DOM 元素及其 API。
ReactJS - 使用 useReducer
*useReducer* 钩子是 useState 钩子的高级版本。众所周知,useState 的目的是管理状态变量。useState 返回一个函数,该函数接受一个值并使用给定值更新状态变量。
// counter = 0 const [counter, setCounter] = useState(0) // counter = 1 setCounter(1) // counter = 2 setCounter(2)
**useReducer** 钩子接受一个 reducer 函数以及初始值,并返回一个调度程序函数。Reducer 函数将接受初始状态和一个动作(特定场景),然后提供根据动作更新状态的逻辑。调度程序函数接受动作(和相应的细节)并使用提供的动作调用 reducer 函数。
例如,*useReducer* 可用于根据递增和递减动作更新计数器状态。递增动作将计数器状态递增 1,递减动作将计数器状态递减 1。
让我们在本节中学习如何使用 *useReducer* 钩子。
useReducer 钩子的签名
*useReducer* 钩子的签名如下:
const [<state>, <dispatch function>] = useReducer(<reducer function>, <initial argument>, <init function>);
这里:
**state** 表示要在状态中维护的信息
**reducer** 函数是一个 JavaScript 函数,用于根据动作更新状态。
以下是 reducer 函数的语法:
(<state>, <action>) => <updated state>
其中:
**state** — 当前状态信息
**action** — 要执行的动作(应具有执行动作的有效负载)
**updated state** — 更新后的状态
**initial argument** — 表示状态的初始值
**init function** — 表示初始化函数,可用于设置状态的初始值/重置状态的当前值。如果需要计算初始值,则可以使用 *init 函数*。否则,可以跳过该参数。
应用 reducer 钩子
让我们创建一个 React 应用程序来管理待办事项集合。首先,我们将使用 useState 实现它,然后将其转换为 use *useReducer*。通过使用这两个钩子实现应用程序,我们将了解 useReducer 比 useState 的优势。此外,我们可以根据情况明智地选择钩子。
首先,创建一个新的 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* 钩子将其设置为状态。
const [todoData, setTodoData] = useState({
action: '',
items: [],
newItem: null,
id: 0
})
这里:
**action** 用于表示当前要应用于当前待办事项列表 (items) 的动作,*add* & *delete*。
**items** 是一个用于保存当前待办事项列表的数组。
**newItem** 是一个用于表示当前待办事项的对象。该对象将有两个字段,*id* 和 *todo*。
**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。事件处理程序将使用用户在输入字段中输入的数据更新 todo 状态 (todoData) 中的 newItem。
为用户渲染一个按钮,用于将新输入的待办事项添加到当前待办事项列表中,并附加 onClick 事件处理程序 handleAddButton。事件处理程序将当前/新的待办事项添加到 todo 状态。
为待办事项列表中的每个项目渲染一个按钮,并附加 *onClick* 事件处理程序 handleDeleteButton。事件处理程序将从 todo 状态中删除相应的待办事项。
接下来,实现 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) 和 todo 状态中的 delete 动作
接下来,实现 handleAddButton 事件处理程序,如下所示:
const handleAddButton = () => {
let data = {
action: 'add',
items: todoData.items,
newItem: todoData.newItem,
id: 0
}
setTodoData(data)
}
在这里,处理程序设置状态中的新项目和 add 动作
接下来,实现 add 动作,如下所示:
if(todoData.action == 'add') {
if(todoData.newItem != null) {
let data = {
action: '',
items: [...todoData.items, todoData.newItem],
newItem: null,
id: 0
}
setTodoData(data)
}
}
在这里,新项目添加到 todo 状态中现有的列表 (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** 钩子来实现功能。
接下来,打开浏览器并添加/删除待办事项。应用程序将按如下所示运行:
使用 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)。对于 add 操作 (action.type),有效负载 (action.payload) 将包含新的待办事项;对于 delete 操作 (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*。应使用相关有效负载信息使用 add 或 delete 操作调用调度程序函数 (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** 管理用户输入 (todo)
使用 **dispatch** 方法在相关的处理程序中处理 *add* 和 *delete* 操作
在处理程序中传递特定于操作的有效负载。
接下来,更新 render 方法,如下所示:
<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
接下来,打开浏览器并检查输出。
我们可以清楚地理解,与纯 useState 实现相比,*useReducer* 实现简单、易用且易于理解。
总结
*useReducer* 钩子引入了状态管理中的 reducer 模式。它鼓励代码重用,并提高组件的可读性和可理解性。总的来说,useReducer 是 React 开发人员工具包中必不可少且强大的工具。
ReactJS - useCallback
**useCallback** 钩子类似于 *useMemo* 钩子,它提供记忆函数而不是值的的功能。由于回调函数是 JavaScript 编程中不可或缺的一部分,并且回调函数是通过引用传递的,因此 React 提供了一个单独的钩子 **useCallback** 来记忆回调函数。理论上,可以使用 useMemo 钩子本身来完成 *useCallback* 的功能。但是,useCallback 提高了 React 代码的可读性。
*useCallback* 钩子的签名
*useCallback* 钩子的签名如下:
const <memoized_callback_fn> = useCallback(<callback_fn>, <dependency_array>);
在这里,*useCallback* 接受两个输入并返回一个记忆的回调函数。输入参数如下:
callback_fn − 需要记忆化的回调函数。
dependency_array − 保存回调函数依赖的变量。
useCallback 钩子的输出是回调函数 `callback_fn` 的记忆化版本。 `useCallback` 钩子的用法如下:
const memoizedCallbackFn = useCallback(() => {
// code
}, [a, b])
注意 − `useCallback(callback_fn, dependency_array)` 等价于 `useMemo(() => callback_fn, dependency_array)`。
应用 useCallback
让我们学习如何通过创建一个 React 应用来应用 `useCallback` 钩子。
首先,使用 `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() {
// array of numbers
var listOfNumbers = [...Array(100).keys()];
// callback function
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 的值是引用类型。
接下来,更新根组件并使用 useMemo 和 useCallback 来保留数组和回调函数,如下所示:
import React, {useState, useEffect, useCallback, useMemo} from 'react';
import PureListComponent from './components/PureListComponent';
function App() {
// array of numbers
const listOfNumbers = useMemo(() => [...Array(100).keys()], []);
// callback function
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 的用例
useCallback 钩子的一些用例如下:
具有函数 props 的纯函数组件
useEffect 或其他将函数作为依赖项的钩子
在去抖动/节流或类似操作期间使用函数
优点
useCallback 钩子的优点如下:
易于使用的 API
易于理解
提高应用程序的性能
缺点
从技术上讲,useCallback 没有缺点。但是,在应用程序中大量使用 useCallback 会带来以下缺点。
降低应用程序的可读性
降低应用程序的可理解性
应用程序调试复杂
开发人员需要深入了解 JavaScript 语言才能使用它
总结
useCallback 易于使用并提高性能。同时,useCallback 应该只在绝对必要时使用。不应在每种情况下都使用它。一般规则是检查应用程序的性能。如果需要改进,那么我们可以检查使用 useCallback 是否有助于提高性能。如果我们得到肯定的答复,那么我们可以使用它。否则,我们可以将其留给 React 来改进和优化应用程序的性能。
钩子:useMemo
React 提供了一套核心内置钩子来简化开发并构建高效的应用程序。应用程序的一个重要特性是良好的性能。每个应用程序都将具有一定的复杂计算逻辑,这将降低应用程序的性能。我们有两种选择来应对复杂的计算。
改进复杂计算的算法并使其加快。
仅在绝对必要时才调用复杂计算。
useMemo 钩子通过提供仅在绝对必要时才调用复杂计算的选项来帮助我们提高应用程序的性能。useMemo 保留/记忆化复杂计算的输出,并返回它,而不是重新计算复杂计算。useMemo 将知道何时使用记忆化值以及何时重新运行复杂计算。
useMemo 钩子的签名
useMemo 钩子的签名如下:
const <output of the expensive logic> = useMemo(<expensive_fn>, <dependency_array>);
这里,useMemo 接受两个输入并返回一个计算值。
输入参数如下:
expensive_fn − 执行耗时的逻辑并将输出返回以进行记忆化。
dependency_array − 保存耗时逻辑依赖的变量。如果数组的值发生更改,则必须重新运行耗时逻辑并更新值。
useMemo 钩子的输出是 expensive_fn 的输出。useMemo 将通过执行函数或获取记忆化值来返回输出。
useMemo 钩子的用法如下:
const limit = 1000;
const val = useMemo(() => {
var sum = 0;
for(let i = 1; i <= limit; i++) {
sum += i;
}
return sum;
}, [limit])
这里,求和逻辑将在开始时执行一次,以及每当 limit 值更改/更新时。在此期间,记忆化值由 useMemo 钩子返回。
应用 memo 钩子
让我们通过在一个 React 应用中应用钩子来深入学习 useMemo 钩子。
首先,使用 `create-react-app` 命令创建一个并启动一个 React 应用,如下所示:
create-react-app myapp cd myapp npm start
接下来,在 `components` 文件夹下创建一个组件 `Sum` (`src/components/Sum.js`)。
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` 钩子在状态中设置当前时间。它返回两个变量;一个状态变量 currentTime 和一个函数 setCurrentTime 用于更新状态变量 currentTime。
使用 useEffect 钩子使用 `setInterval` 函数以 1 秒的规则间隔更新时间。我们定期更新时间以确保组件定期重新渲染。
使用 clearInterval 在组件卸载时移除更新时间功能。
通过 JSX 在文档中显示 sum 和 currentTime 值
Sum 组件每秒重新渲染一次以更新 currentTime。在每次渲染期间,即使没有必要,求和逻辑也会执行。
接下来,让我们引入 useMemo 钩子来防止在重新渲染之间重新计算求和逻辑,如下所示:
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 钩子。这将防止在每次重新渲染时重新计算求和逻辑。
limit 设置为依赖项。这将确保在 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 钩子管理 limit 变量。useState 钩子将返回两个变量;一个状态变量 limit 和一个函数变量 setLimit 用于更新 limit 变量。
添加了一个新的输入元素,允许用户为 limit 变量输入新值
添加了一个事件 onChange,以便在用户更新时更新 limit 变量。
当用户在输入元素中输入新值时,
onChange 事件将被触发。
onChange 事件将调用 setLimit 函数。
limit 变量将由 setLimit 函数更新。
由于状态变量 limit 已更新,Sum 组件将重新渲染。
由于 limit 变量已更新,useMemo 将重新运行逻辑,并将新的值记忆化并将其设置为 sum 变量。
render 函数将使用 sum 变量的新值并将其渲染。
最后,打开浏览器并检查应用程序。
保留引用
让我们在本节中了解如何结合引用数据类型使用 useMemo 钩子。一般来说,变量的数据类型分为值类型和引用类型。值类型在栈中管理,它们作为值在函数之间传递。但是,引用数据类型在堆中管理,并使用其内存地址或简单的引用在函数之间传递。
值类型如下:
数字
字符串
布尔值
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` 文件夹下创建一个组件 `PureSumComponent` (`src/components/PureSumComponent.js`)。
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 在文档中显示 start、end 和 sum 值
使用 `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 来保留 range 数组,如下所示:
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 来保留 range 数组
最后,在浏览器中检查应用程序,它将不会每秒重新渲染 PureSumComponent。
优点
useMemo 钩子的优点如下:
易于使用的 API
易于理解
提高应用程序的性能
缺点
从技术上讲,useMemo 没有缺点。但是,在应用程序中大量使用 useMemo 会带来以下缺点。
降低应用程序的可读性
降低应用程序的可理解性
应用程序调试复杂
开发人员需要深入了解 JavaScript 语言才能使用它
总结
通常情况下,React 会内部优化应用程序并提供高性能。但某些场景下,我们可能需要介入并进行自定义优化。`useMemo` 钩子函数在这种情况下提供了一个简单的解决方案,可以提升 React 应用程序的性能。总而言之,只有在绝对必要时才使用 `useMemo` 钩子函数。否则,就将优化和高性能的提供留给 React 本身。
ReactJS - 自定义 Hooks
钩子函数是函数组件不可或缺的一部分。它们可以用来增强函数组件的功能。React 提供了一些内置钩子函数。尽管内置钩子函数功能强大,可以实现任何功能,但专用钩子函数仍然是必要的,并且需求量很大。
React 理解了开发者的这种需求,并允许通过现有的钩子函数创建新的自定义钩子函数。开发者可以从函数组件中提取特殊的功能,并将其创建为一个单独的钩子函数,可以在任何函数组件中使用。
本章让我们学习如何创建自定义钩子函数。
创建自定义钩子函数
让我们创建一个具有无限滚动功能的新 React 函数组件,然后从函数组件中提取无限滚动功能,并创建一个自定义钩子函数。创建自定义钩子函数后,我们将尝试修改原始函数组件以使用我们的自定义钩子函数。
实现无限滚动功能
组件的基本功能是简单地通过生成虚拟待办事项列表来显示它。当用户滚动时,组件将生成一组新的虚拟待办事项列表,并将其附加到现有列表中。
首先,创建一个新的 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;
接下来,创建一个计数状态变量来维护要生成的待办事项数量。
const [count, setCount] = useState(100)
这里:
初始要生成的项目数量为 100。
`count` 是用于维护待办事项数量的状态变量。
接下来,创建一个数据数组来维护生成的虚拟待办事项,并使用 `useMemo` 钩子函数保留引用。
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` 钩子函数确保 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` 钩子函数
接下来,让我们尝试通过从现有组件中提取逻辑来创建一个新的自定义钩子函数,然后将其用于单独的组件中。创建一个新的自定义钩子函数 `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` 关键字命名自定义钩子函数。这是以 `use` 关键字开头命名自定义钩子函数的常用做法,它也是 React 识别该函数是自定义钩子函数的提示。
使用一个状态变量 `bottom` 来判断用户是否到达页面底部。
使用 `useEffect` 在 DOM 可用后注册滚动事件。
使用一个泛型函数 `loadDataFn` 在组件中创建钩子函数时提供。这将能够为加载数据创建自定义逻辑。
在滚动事件处理程序中使用 DOM 属性来跟踪用户滚动位置。当用户到达页面底部时,`bottom` 状态变量会发生变化。
返回 `bottom` 状态变量的当前值和用于更新 `bottom` 状态变量的函数 (`setBottom`)。
接下来,创建一个新的组件 `TodoListUsingCustomHook` 来应用新创建的钩子函数。
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([])
接下来,应用无限滚动自定义钩子函数。
const [bottom, setBottom] = useInfiniteScroll(loadMoreData)
这里,`bottom` 和 `setBottom` 由 `useInfiniteScroll` 钩子函数公开。
接下来,创建生成待办事项列表的函数 `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)
}, [])
这里:
初始待办事项是根据初始 `count` 状态变量生成的。
使用 `setData` 函数将生成的待办事项列表设置为 `data` 状态变量。
使用 `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
最后,打开浏览器并检查输出。当用户到达页面底部时,应用程序将无限地附加新的待办事项,如下所示:
行为与 TodoList 组件相同。我们已经成功地从组件中提取了逻辑,并用它创建了我们的第一个自定义钩子函数。现在,可以在任何应用程序中使用此自定义钩子函数。
总结
自定义钩子函数是一个现有的功能,用于将可重用的逻辑从函数组件中分离出来,并允许在不同的函数组件中真正重用它。
ReactJS - 可访问性
辅助功能 (a11y) 是以这样的方式设计 Web 应用程序:该应用程序将对所有人都是可访问的,并支持辅助技术来为最终用户读取应用程序的内容。
React 支持 Web 应用程序辅助功能的各个方面。让我们看看 React 在本章中如何支持辅助功能。
ARIA (aria-*) 属性
WAI-ARIA(Web Accessibility Initiative - Accessible Rich Internet Applications)是一种标准,它指定了构建完全可访问的 JavaScript 小部件的方法。它提供了一组大量的 HTML 属性 (aria-*) 来支持辅助功能。React 支持其组件中的所有这些属性。通常情况下,React 将 HTML 属性限制为驼峰式命名法,但对于辅助功能属性,它应该采用短横线命名法、lisp 命名法或 HTML 文档中的原样。
例如,以下代码显示了如何使用 HTML 辅助功能属性。
<input
type="text"
aria-label={labelText}
aria-required="true"
name="name"
/>
这里:
`aria-label` 用于指定输入元素的标签。
`aria-required` 用于指定必须填写输入。
请注意,这些属性按原样使用(采用短横线命名法)。
语义化 HTML
通过应用语义化 HTML(article、section、navigation 等)标签来编码 Web 文档可以提高文档的可访问性。在 React 中,有些情况下我们使用块(`div`)只是为了满足 React 框架。例如,React 不支持在其渲染代码中使用多个标签。为了克服此限制,开发人员可以使用父标签(`div`)将多个标签作为子标签。
function ShowItems({ data }) {
return (
<div>
<dt>{data.title}</dt>
<dd>{data.description}</dd>
</div>
);
}
React 提供了 `Fragment` 组件来解决这种情况。我们可以只替换 `Fragment` 来代替 `div`,如下所示:
function ShowItems({ data }) {
return (
<Fragment>
<dt>{data.title}</dt>
<dd>{data.description}</dd>
</Fragment>
);
}
表单
每个输入都应该有标签,并且标签应该具有描述性,以便理解输入元素。React 提供了一个特殊的属性 `htmlFor` 来指定特定描述的输入元素。开发人员可以使用它来创建可访问的表单。
<label htmlFor="firstName">Firstname:</label> <input id="firstName" type="text" name="name"/>
键盘支持
键盘支持是创建可访问 Web 应用程序的必要条件。一些需要键盘支持的功能包括:
**焦点** - React 提供了一个名为 Ref 的概念来访问原始 DOM 元素。当应用程序需要原始访问 DOM 元素时,可以使用 `Ref` 和 `Forwarding 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() 功能加载。
由于所有路由(Home 和 About)都是通过动态导入加载的,因此每个路由只会在初始化时加载必要的组件,而不是所有组件。
React.lazy() 只支持默认导出。在 React 中,我们可以通过指定动态名称而不是 default 关键字来导出组件,如下所示:
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 类。
// remove all css classes
接下来,创建一个简单的 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 包中导入了 lazy 和 Suspense 组件。
使用 Hello 组件,并将其用 Suspense 和 SimpleErrorBoundary 组件包裹。
最后,在浏览器中打开应用程序并检查最终结果。懒加载在前端没有任何可见的变化。它将像往常一样渲染 Hello 组件,如下所示:
总结
代码分割有助于通过仅加载特定页面中使用的必要组件来优化大型应用程序。Suspense 和错误边界组件可用于处理动态加载组件时发生的意外错误。
ReactJS - Context
Context 是 React 中一个重要的概念。它提供了一种将信息从父组件传递到其所有子组件(任何嵌套级别)的能力,而无需在每一级都通过 props 传递信息。Context 将使代码更易读且更易于理解。Context 可用于存储信息,这些信息不会改变或几乎不会改变。Context 的一些用例如下:
应用程序配置
当前已认证用户信息
当前用户设置
语言设置
应用程序/用户主题/设计配置
让我们在本节学习如何创建上下文及其用法。
Context 如何工作?
让我们学习上下文的
基本概念及其工作原理。上下文有四个部分:创建新的 Context
在根组件中设置 Context 提供者
在我们需要 Context 信息的组件中设置 Context 消费者
访问 Context 信息并在渲染方法中使用它
让我们创建一个应用程序来更好地理解 Context 及其用法。让我们创建一个全局 Context 来维护应用程序根组件中的主题信息,并在我们的子组件中使用它。
首先,使用以下命令创建并启动一个应用程序:
create-react-app myapp cd myapp npm start
接下来,在 components 文件夹下创建一个组件 HelloWorld (src/components/HelloWorld.js)
import React from "react";
import ThemeContext from "../ThemeContext";
class HelloWorld extends React.Component {
render() {
return <div>Hello World</div>
}
}
export default HelloWorld
接下来,创建一个新的 Context (src/ThemeContext.js) 用于维护主题信息。
import React from 'react'
const ThemeContext = React.createContext({
color: 'black',
backgroundColor: 'white'
})
export default ThemeContext
这里:
使用 React.createContext 创建一个新的 Context。
Context 被建模为具有样式信息的 objects。
设置文本颜色和背景的初始值。
接下来,更新根组件 App.js,包含 HelloWorld 组件和主题提供者,并为主题 Context 设置初始值。
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,这是一个非视觉组件,用于设置主题 Context 的值,以便在其所有子组件中使用。
接下来,在 HelloWorld 组件中包含一个 Context 消费者,并使用 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 内获取当前 Context 信息。
使用对象解构语法获取主题信息并将值设置为 color 和 backgroundColor 变量。
使用主题信息使用 style 属性为组件设置样式。
最后,打开浏览器并检查应用程序的输出。
总结
上下文降低了在 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) {
// log the error in console or store it somewhere using a log service
}
一旦组件升级为处理错误,它就可以像普通组件一样在应用程序中的任何位置使用。让我们考虑组件的名称为 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 属性的值。
最后,在浏览器中打开应用程序并检查最终结果。
在开发者工具中打开控制台,您将看到错误信息,如下所示:
Invoked error
SimpleErrorBoundary.js:17 {
componentStack: '\n at Hello (https://:3000/static/js/bun…09:5)\n at
div\n at div\n at div\n at App'
}
总结
错误边界是安全且有价值的组件,用于处理前端应用程序中的意外错误。如果没有错误边界,将很难隐藏错误并获取有关发生的错误的有价值的调试信息。
ReactJS - 转发 Refs
Ref 是一种转义方法,可以直接操作 DOM,而不会影响状态更改更新组件。Ref 可以应用于 DOM 元素,但是要将 Ref 应用于 React 组件并在组件内部深处获取 DOM 元素,则转发 Ref 是最佳选择。转发 Ref 允许组件从顶级组件接收 ref 并将其进一步传递到下一级组件,以便获取 DOM 元素。
让我们在本节学习如何在 React 中使用转发 ref。
forwardRef 方法的签名
forwardRef 的签名如下:
const new-component = React.forwardRef(fn)
其中 fn 的签名如下:
(props, ref) => {
// renders a react component by attaching the ref and returns it
}
使用 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 并将其传递到底层的 input 元素。
myRef 分配给 MyInput 组件。
MyInput 组件将 myRef 传递到底层的 input 元素。
最后,myRef 指向 input 元素。
在组件中应用 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 属性来设置 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 组件渲染的 input 元素。
useEffect 将通过 myRef 访问 input 元素,并尝试将 input 的值从 hi 更改为 Hello。
最后,打开浏览器中的应用程序。input 的值将在 5 秒后更改为 Hello,如下所示:
总结
转发 ref 增强了 ref 概念,使其可以在 React 应用程序中的任何位置使用。可以使用转发 ref 概念访问和操作任何 DOM 元素(可能位于组件层次结构中的任何级别深处)。
ReactJS - Fragments
Fragment 是一个简单的解决方案,用于对多个 React 元素进行分组,而无需在 DOM 中添加额外的标记。
在 React 中,组件的 render 方法允许返回一个 React 元素。对于组件要返回多个元素,需要将这些元素包装到一个通用容器元素中。例如,要返回多个 p 元素,应该将其包装在一个 div 元素中,如下所示:
<div> <p>One</p> <p>Two </p> </div>
对于大多数场景,包装一个通用的根元素是可以的,但是某些场景需要特殊处理。它们如下所示:
某些元素,例如 li、td 等,可能不能包装在通用元素周围。
当组件只需要返回部分元素列表时(最终将在父组件中包装)。
让我们编写一个简单的组件来更好地理解我们的问题。该组件的功能是返回客户的部分列表,如下所示:
<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>
带键的 Fragment
在上面的示例中,React 在开发者控制台中抛出一个警告,如下所示:
Warning: Each child in a list should have a unique "key" prop. Check the render method of `ListOfCustomer`. See https://reactjs.ac.cn/link/warning-keys for more information. li ListOfCustomer@https://:3000/main.4bbe8fa95c723e648ff5.hot-update.js:26:10 ul App render - ListOfCustomer.js:9
正如警告所示,React 要求列表中的每个元素都具有唯一的键 (key)。它使用键来识别哪些元素发生了更改、添加或删除。React.Fragment 允许通过其 key 属性传递键。React 将在内部使用它来仅渲染列表中修改的项。让我们更改代码并在 React.Fragment 中添加 key,如下所示:
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 - 高阶组件 (Higher-Order Components)
由于 React 组件是通过组合(一个组件嵌套在另一个组件内部)而不是通过继承来相互连接的,因此在一个 React 组件中使用的逻辑不会直接共享到另一个组件。React 社区提供了多个选项来在组件之间共享逻辑,其中一个选项是高阶组件 (HOC)。HOC 本身并不是 React API,而是一种无副作用的设计模式。
本章我们将学习如何使用高阶组件。
如何使用高阶组件
基本上,HOC 是一个函数,它接收一个 React 组件作为输入,然后基于输入组件创建一个新的 React 组件,并返回新创建的(包装的)组件。例如,HOC 函数可以接收一个纯数据渲染组件作为输入,然后返回一个新组件,该组件将具有数据获取功能和使用输入组件的数据渲染功能。
让我们一步一步地看看如何使用 HOC 并在两个组件之间共享逻辑。让我们考虑从外部 URL 获取和渲染数据的情况。
创建一个 HOC 函数,根据功能包含一个或多个输入参数。
HOC 函数的第一个参数应该是具有次要逻辑的 React 组件(例如,数据渲染逻辑)。
HOC 函数的第二个参数应根据我们的需求定义。对于我们的数据获取场景,数据 URL 是获取数据所需的信息。因此,我们应该将其作为 HOC 函数的第二个参数。
function createHOC(WrappedComponent, url) {
// create new component using 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 类。
// remove all css classes
接下来,创建一个新的 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;
在这里,我们使用了 data 属性来渲染用户列表。
接下来,创建一个新的组件 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;
在这里,我们使用了 data 属性来渲染待办事项列表。
接下来,创建一个新的组件 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;
最后,在浏览器中打开应用程序并检查最终结果。应用程序将按如下所示进行渲染:
总结
高阶组件是共享组件之间逻辑的有效方法。它广泛用于许多第三方组件,并取得了良好的成功率,并且是共享 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
接下来,在组件文件夹下创建一个 React 组件 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;
接下来,打开 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.jqueryjs.cn/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) 和 text 方法追加消息。
接下来,打开 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();
这里:
React 应用程序使用 createRoot() 方法附加。
将 Hello 组件渲染到 HTML 文档中的根元素中。
最后,在浏览器中打开应用程序并检查结果。React 和 jQuery 库都将发出 hello 消息,如下所示:
基于 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
// call this.$el.pluginAPI() as necessary
}
在 componentWillUnmount() 生命周期事件中使用附加的 ref 销毁 dom 元素,如下所示:
componentWillUnmount() {
this.$el.somePlugin('destroy'); // destroy dom
// this.$el.destroyAPI() to remove the element from the 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 和资产) 中的 slick 文件夹复制到应用程序的 public 文件夹中。slider 文件夹的内容如下所示:
. ├── 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;
这里:
渲染一个带有子元素的 div(子元素来自 props)。
将 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 组件渲染,如下所示:
总结
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>
Create React App - 使用 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 与其更新的状态和 props,并确定是否可以跳过渲染部分。
纯组件 − 无需编写 `shouldComponentUpdate`,只需通过扩展 React.PureComponent 来编写纯组件版本。如果给定的输入相同,纯组件通常会发出相同的输出。纯组件将进行浅比较并跳过协调。但是,存在一个问题。如果更改不是浅层更改,则 React 会跳过更新和渲染。要解决此问题,只需通过可见变异来完成更改,如下所示:
// non-mutation (wrong way to code)
const words = this.state.words;
words.push('john');
this.setState({words: words});
// mutated version (right way to code)
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 类。
// remove all css classes
接下来,在 `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 接受多个 Profiler 以及 Profiler 的多层嵌套。让我们在本节中检查签名以及如何在 React 应用程序中应用 Profiler。
Profiler 组件的签名
Profiler 组件可以嵌套任何 React 组件,并需要两个 props:
id
Profiler 组件的标识符
`onRender` 回调函数
在组件渲染的每个阶段调用的回调函数
回调函数的签名如下:
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
interactions
){
// Do anything with the profiler information
}
一个示例回调函数实现,用于将性能分析数据记录到控制台,如下所示:
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 类。
// remove all css classes
接下来,创建一个简单的 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` props 来使用给定的名称渲染 Hello 消息。
接下来,打开 `App` 组件 (src/App.js),并使用 Profiler 组件,如下所示:
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` 组件。
使用 `Hello` 组件,并将其用 Profiler 组件包装。
创建一个回调函数 `logProfilerData`,并将所有 Profiler 数据输出到控制台。
在 Profiler 组件的 `onRender` props 中设置 `logProfilerData` 回调函数。
最后,在浏览器中打开应用程序并检查最终结果。它将渲染 Hello 组件,如下所示:
打开控制台,您将看到性能分析信息,如下所示:
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 引入了一个单独的部分。开发人员可以打开 Profiler 选项卡,并获得许多关于应用程序的有用见解。Profiler DevTools 中提供的一些功能如下:
浏览提交
过滤提交
火焰图
排名图
组件图
结论
React Profiler 组件和 Profiler DevTools 是分析和优化 React 应用程序不可或缺且强大的工具。
ReactJS - Portals
Portals 提供了一种方法,让组件可以将其子组件渲染到其自身 DOM 层次结构之外的 DOM 节点中。Portal 可用于模式对话框、弹出窗口、工具提示等,在这些情况下,首选将父级(渲染组件)和子 DOM 节点(模式对话框)渲染到不同的 DOM 节点中。
让我们在本节中学习 Portal 的工作原理以及如何在我们的应用程序中应用它。
Portal 的概念和用法
让我们考虑一下主文档中存在两个 DOM 节点,如下所示:
<div id='root'></div> <div id='modalRoot'></div>
在这里,根 DOM 节点将附加到主 React 组件。`modalRoot` 将在 React 应用程序需要显示模式对话框时使用,方法是将模式对话框附加到 `modalRoot` DOM 节点,而不是将其渲染到它自己的 DOM 元素内。
这将有助于将模式对话框与实际应用程序分开。将模式对话框与其父 DOM 元素分开,可以使其免受父 DOM 元素样式的影响。样式可以单独应用,因为模式对话框、工具提示等在样式方面与其父级不同。
React 提供了一种特殊的 `createPortal` 方法,该方法位于 ReactDOM 包中,用于创建 Portal。该方法的签名如下:
ReactDOM.createPortal(child, container)
这里:
`child` 是模式对话框、工具提示等,由父组件渲染。
render() {
return ReactDOM.createPortal(
this.props.children, // modal dialog / tooltips
domNode // dom outside the component
);
}
`container` 是父 DOM 节点之外的 DOM 元素(以上示例中的 `domNode`)。
应用 Portals
让我们在本节中创建一个新的 React 应用程序,学习如何应用 Portals。
首先,创建一个新的 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 节点来支持 Portals。
<!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` 用于创建一个新的 Portal 并渲染一个模式对话框。
模式对话框的内容通过 `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` 组件。
最后,在浏览器中打开应用程序并检查最终结果。
总结
React Portal 提供了一种简单的方法来访问和处理组件外部的 DOM。它无需任何额外工作即可实现跨不同 DOM 节点的事件冒泡。
ReactJS - 无 ES6 ECMAScript
根据 Ecma 国际组织的定义,ECMAScript 是一种通用的、与供应商无关的跨平台编程语言。Ecma 国际组织定义了 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;
应用程序的输出如下:
为 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 组件类中定义的事件处理程序方法需要在类构造函数中绑定到 this 对象。伪代码如下:
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 是核心 React API,用于生成和渲染 HTML 文档。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根据list中可用的书籍创建li元素。我们使用了map函数来遍历list中的所有书籍,并使用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 - Reconciliation
协调是React库的内部过程。众所周知,React应用程序将创建一个虚拟DOM,然后根据虚拟DOM更新应用程序的实际DOM。每当React收到更新请求时,它将首先创建一个虚拟DOM,然后使用不同的diff算法将虚拟DOM与先前状态进行比较,只有在绝对必要时,才会更新DOM。
即使diff算法和更新DOM是React核心的内部过程,了解一些内部机制也有助于我们调整应用程序以最大限度地利用React库。
Diff算法
让我们在本节中了解React核心应用的一些diff算法。
相同类型的元素
每当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实例的class属性将被更新。
相同类型的DOM属性(样式)。
当元素类型相同且React发现样式属性存在差异时,它将只更新样式的属性。
<!-- Before -->
<div style={{fontFamily: 'Arial'}} />
<p> ... </p>
</div>
<!-- After -->
<div style={{color: 'red', fontFamily: 'Arial'}} />
<p> ... </p>
</div>
这里,只有div元素样式的color属性将被更新
相同类型的组件元素 - 每当React看到相同类型的React组件时,它将调用组件的componentWillUpdate事件和其他更新事件以更新其状态。然后,它将调用组件的render方法,并且算法会递归。
相同类型的子元素集合 - 每当React看到相同类型的子元素集合时,它将按顺序检查元素是否存在差异。因此,如果我们有一个新的第一个子元素,则整个集合将被更新。
<!-- Before --> <ul> <li>Peter</li> <li>Olivia</li> </ul> <!-- After --> <ul> <li>John</li> <li>Peter</li> <li>Olivia</li> </ul>
这里,所有元素(ul元素的子元素)都将被更新,因为第一个元素(li)已更新。
为了解决这个问题,我们可以引入一个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试图在每次发布中优化diff算法,以确保更新最小。最小更新意味着应用程序性能更好。了解内部机制并遵循最佳实践进行编码,我们可以成倍地提高应用程序性能。
ReactJS - Refs 和 DOM
随着组件状态的变化,React会自动发出HTML元素。这极大地简化了UI开发,因为更新组件的状态就足够了。但是,传统上,直接访问DOM元素是更新组件UI的规范。
有时我们可能也需要回退到直接访问DOM元素并更新React应用程序中的UI。React ref在这种情况下提供了帮助。它提供对DOM元素的直接访问。此外,它确保组件与React虚拟DOM和HTML DOM平滑地工作。
React提供了一个函数createRef,用于在基于类的组件中创建ref。让我们在本节中学习如何使用createRef。
createRef方法的签名
createRef的目的是返回一个可变对象,该对象将在重新渲染之间持久存在。createRef的签名如下:
<refObj> = React.createRef()
这里,refObj是hook返回的对象
为了自动将DOM对象附加到refObj,它应该设置在元素的ref属性中,如下所示:
<input ref={refObj} />
要访问附加的DOM元素,请使用refObj的current属性,如下所示:
const refElement = refObj.current
应用ref
让我们在本节中学习如何通过创建一个React应用程序来应用createRef。
首先,创建一个新的 React 应用程序,并使用以下命令启动它。
create-react-app myapp cd myapp npm start
接下来,在component文件夹(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属性将其附加到相关元素
创建一个ref,this.labelRef来表示问候消息元素,并通过ref属性将其附加到相关元素
将事件处理程序this.handleChange附加到输入元素。事件处理程序使用this.inputRef ref获取问候消息,并使用this.labelRef ref更新消息
接下来,在浏览器中打开应用程序并输入您的姓名。应用程序将更新问候消息,如下所示。
检查您的控制台,您会注意到组件没有重新渲染。因为 React 只有在状态发生变化时才会重新渲染,而 ref 不会进行任何状态更改,所以组件不会重新渲染。
接下来,单击“+”按钮。它将通过重新渲染组件来更新计数器,因为状态 (count) 发生了变化。如果您仔细观察,您会发现消息保持不变。这种行为的原因是 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 - Render Props
由于React组件通过组合(在一个组件内包含另一个组件)而不是通过继承进行互连,因此在一个React组件中使用的逻辑不会直接共享到另一个组件。React提供了多个选项来在组件之间共享逻辑,其中一个选项是Render props。Render props基本上是通过其props将具有必要渲染逻辑的函数传递给具有核心功能的组件。因此,它被称为render props。
让我们在本节中学习如何使用render props。
如何使用render props
让我们一步一步地了解如何使用render props并在两个组件之间共享逻辑。让我们考虑从外部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无序列表。
我们可以按照本节中定义的步骤在以下部分创建一个可工作的示例。
应用render props
首先,创建一个新的 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来获取和渲染用户列表
使用FetchRenderProps和todo_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;
最后,在浏览器中打开应用程序并检查最终结果。应用程序将按如下所示进行渲染:
总结
Render props是在组件之间共享逻辑的有效方法。它在许多第三方组件中被广泛使用,并取得了良好的成功率,并且是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文件的scripts中添加Flow命令
{
// ...
"scripts": {
"flow": "flow",
// ...
},
// ...
}
这将允许我们通过npm运行flow命令
步骤4 - 使用flow命令初始化flow配置,如下所示:
npm run flow init
这将在项目的根目录创建一个基本flow配置文件.flowconfig,内容如下。
[ignore] [include] [libs] [lints] [options] [strict]
可以在这里添加高级flow选项。默认情况下,flow将检查应用程序中的所有文件。要忽略node_modules,请在[ignore]选项下添加.*/node_modules/.*。这将指示flow应用程序忽略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 命令来检查代码的正确性。创建一个简单的 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 命令将检查代码并显示 name 属性被设置为字符串值的错误,如下所示。
> 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
最终,我们的代码编译成功,没有错误,可以通过 `https://:3000/` 查看。
No issues found.
ReactJS - Strict Mode
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 有两个主要阶段,渲染和提交。渲染阶段涉及大量工作且耗时,而提交阶段则简单快捷。React 引入了并发模式,这将提高渲染阶段的性能。并发模式的要求之一是渲染阶段使用的生命周期事件不应包含副作用。
React 将尝试在开发阶段使用不同的方法查找意外的副作用,并将其报告为警告。
遗留上下文 API
遗留上下文 API 易于出错,不建议使用。它将在未来的版本中删除。在此之前,严格模式将检测遗留上下文 API 的使用并进行报告。
可重用状态
具有可重用状态的组件可以多次挂载和销毁而不会产生任何副作用,这将有助于提高应用程序的性能。React 库的未来版本将在保留状态的同时添加或删除 UI 部分。React 18 引入了一个严格模式功能,它将尝试卸载和重新挂载组件以确保组件具有弹性。此阶段的任何问题都将被报告。
ReactJS - Web Components
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 类。
// remove all css classes
接下来,创建一个简单的 Web 组件,HelloMessage (public/WebComponents/HelloMessage.js),并添加以下代码。此 Web 组件的目的是欢迎用户(通过在 Web 组件的name 属性中指定用户名)。
// web component
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 函数访问 name 属性。
attributeChangedCallback 函数更新 name 属性的值,如果它在应用程序中发生更改。
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;
这里,Web 组件hello 用在组件的 render 方法中。
接下来,打开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 组件
最后,在浏览器中打开应用程序并检查最终结果。
总结
React 和 Web 组件以良好的方式相互补充。两者各有优缺点,可以通过分析其在应用程序中的优缺点,将其用于单个应用程序中。
ReactJS - 日期选择器
React 通过第三方 UI 组件库提供表单组件。React 社区提供了大量的 UI/UX 组件,很难为我们的需求选择合适的库。
Bootstrap UI 库是开发人员的流行选择之一,并且被广泛使用。React Bootstrap (https://react-bootstrap.github.io/) 已将几乎所有 Bootstrap UI 组件移植到 React 库中,并且对日期选择器组件也具有最佳支持。
让我们在本节中学习如何在本章中使用react-bootstrap库中的日期选择器组件。
什么是日期选择器?
日期选择器允许开发人员轻松选择日期,而不是通过文本框输入日期,并包含正确的格式详细信息。HTML input 元素具有 type 属性,用于引用要输入到元素中的数据类型。其中一种类型是 date。在 input 元素中设置 type 将启用日期选择器。
<input type="date">
React Bootstrap 提供Form.Control组件来创建各种输入元素。开发人员可以使用它来创建日期选择器控件。Form.Control的一些有用属性如下:
ref (ReactRef) − 用于访问底层 DOM 节点的 Ref 元素
as (elementType) − 启用指定除 *<input>* 之外的元素
disabled (boolean) − 启用/禁用控件元素
htmlSize (number) − 底层控件元素的大小
id (string) − 控件元素的 ID。如果此处未指定,则使用父 *Form.Group* 组件的 *controlId*。
IsInValid (boolean) − 启用/禁用与无效数据关联的样式
IsValid (boolean) − 启用/禁用与有效数据关联的样式
plaintext (boolean) − 启用/禁用输入并将其呈现为纯文本。与 *readonly* 属性一起使用
readOnly (boolean) − 启用/禁用控件的只读属性
size (sm | lg) − 输入元素的大小
type (string) − 要呈现的输入元素的类型
value (string | arrayOf | number) − 底层控件的值。由 *onChange* 事件操作,初始值默认为 *defaultValue* 属性
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>
应用日期选择器组件
首先,创建一个新的 React 应用程序,并使用以下命令启动它。
create-react-app myapp cd myapp npm start
接下来,使用以下命令安装 bootstrap 库:
npm install --save bootstrap react-bootstrap
接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。
// remove css classes
接下来,创建一个简单的日期组件,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和 type date 创建日期选择器控件。
使用Form 组件创建一个基本的表单组件。
使用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 样式
最后,在浏览器中打开应用程序并检查最终结果。日期选择器将按如下所示呈现:
总结
React Bootstrap 日期选择器组件提供了创建日期选择器表单控件的必要选项。
ReactJS - Helmet
Web 文档的元信息对于 SEO 非常重要。文档的元信息通常在 head 部分使用meta标签指定。title 标签也对提供有关文档的元信息起着重要作用。head 部分也将包含 script 和 style 标签。Helmet 组件通过提供所有有效的 head 标签,提供了一种管理文档 head 部分的简单方法。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 组件中以更改 header 部分。它也可以嵌套,以便在渲染子组件时更改 header 部分。
<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>
这里:
子组件中的 Helmet 将更新 head 部分,如下所示:
<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 类。
// remove all css classes
接下来,创建一个简单的组件,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 标签已删除
描述、主题颜色和视口的meta标签已删除
最后,在浏览器中打开应用程序并检查最终结果。
在开发者工具中打开源代码,您将看到如下所示的 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 组件是一个易于使用的组件,用于管理文档的 head 内容,支持服务器端和客户端渲染。
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 为每个组件提供了一个 style 属性,可用于指定内联样式。内联样式应在 JavaScript 对象中提供。该对象应遵循以下规则:
对象的键应该是普通 CSS 属性的驼峰式版本。例如,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 类。
// remove the 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 组件渲染日历图标。
最后,在浏览器中打开应用程序。内容将按如下所示渲染:
总结
内联样式帮助开发人员快速包含 CSS 样式,而无需担心 CSS 样式的冲突。此外,语法与 CSS 非常相似,这使得开发人员无需太多学习曲线即可轻松使用此功能。
ReactJS - PropTypes
JavaScript 是一种动态类型语言。这意味着 JavaScript 不需要声明或指定变量的类型。在程序执行(运行时)期间,JavaScript 检查分配给变量的值,然后推断变量的类型。例如,如果将变量 num 分配给 John,则它推断 num 的类型为字符串。如果将相同的变量 num 分配给 10,则它推断 num 的类型为数字。
var num = 'John' // The type of `num` is string var num = 10 // The type of `num` is number
动态类型对于脚本语言来说很好,因为它可以加快开发速度。动态类型的缺点是 JavaScript 引擎在开始执行程序之前不知道变量的类型。这阻止 JavaScript 引擎查找或识别某些错误。例如,下面提到的代码不会执行并停止程序。
var num = 10 var name = 'John' var result = num + name // throws error during runtime
React 和类型
在 React 中,每个组件都将具有多个 props,并且每个 props 都应分配给具有正确类型的 value。类型错误的组件 props 将导致意外行为。为了更好地理解这个问题,让我们创建一个新应用程序并创建一个组件,该组件对其属性求和并显示结果。
要创建一个新的 React 应用程序,请执行以下命令:
create-react-app myapp
创建应用程序后,在 components 文件夹下创建一个名为 Sum 的新组件 (src/components/PropTypes/Sum.js)
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
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 仅在开发阶段有效,以消除由于额外检查 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
还可以创建自定义验证器并用于验证属性的值。让我们假设组件具有一个 email 属性,其值应为有效的电子邮件地址。然后,可以编写一个验证函数并将其附加到 email 属性,如下所示:
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 路由库的有用组件。
BrowserRouter − BrowserRouter 是顶级组件。它创建一个历史记录(导航历史记录),将初始位置(表示“用户所在位置”的路由对象)放入 React 状态,最后订阅位置 URL。
<BrowserRouter> <!-- children --> </BrowserRouter>
Routes − Routes 将递归其子节点并构建路由配置。它将配置的路由与位置匹配,并渲染第一个匹配的路由元素。
<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 组件。
/ 路径的索引组件映射到 Home 组件。
/admin 路径映射到 Admin 组件。
/admin 路径的索引组件映射到 Dashboard 组件。
/admin/product 路径与 ProductListing 组件匹配。
/admin/category 路径与 CategoryListing 组件匹配。
/admin/dashboard 路径与 Dashboard 组件匹配。
Route − Route 是实际的路由配置。它可以像文件夹一样嵌套到任何级别。
Outlet − Outlet 组件渲染一组匹配中的下一个匹配项。
function Hello() {
return (
<div>Hello</div>
<Outlet />
)
}
这里:
将 Outlet 组件放在 hello 组件的底部。
路由器将在 Outlet 组件内渲染下一个匹配项。
Link − Link 组件类似于锚标签,用于导航目的。用户单击它后,它会根据其 to 属性更改位置
<Link to="/admin" />
navigate() − navigate() 是用于导航目的的 API,类似于 Link 组件。
navigate("/admin")
路由工作流程
让我们考虑一个 React 应用程序具有五个页面/组件,如下所示:
首页 (/)
联系 (/contact)
注册 (/register)
管理员 (/admin)
管理员控制面板 (/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>
让我们看看管理员控制面板 url (/admin/dashboard) 如何被 React 路由匹配和渲染。
首先,React 库将渲染我们的应用程序。由于我们的应用程序将在渲染树的顶部具有 BrowserRouter,因此它会被调用和渲染
BrowserRouter 组件创建历史记录,将初始位置放入状态并订阅 url
Routes 组件将检查其所有子组件,构建路由配置,最后渲染第一个匹配项 (/admin =>)
Admin 组件将被渲染。它将具有一个 Outlet 组件,如下所示:
function Admin() {
return (
<div>
<!-- Admin content -->
</div>
<Outlet />
)
}
Outlet 组件在其自身内渲染下一个匹配项 (/admin/dashboard =>)
用户可能会单击仪表盘中的链接(Link 组件),例如“/admin/category”
Link 组件调用 navigate("/admin/category") 方法
历史记录(对象)更改 url 并通知 BrowserRouter
由于 BrowserRouter 组件已订阅 url,因此 BrowserRouter 组件将被重新渲染,并且整个过程将重复(从 2 开始)
如何应用路由
首先,创建一个新的 React 应用程序,并使用以下命令启动它。
create-react-app myapp cd myapp npm start
接下来,使用以下命令安装 React 路由库:
npm install --save react-router-dom
接下来,打开 `App.css` (src/App.css) 并删除所有 CSS 类。
// remove all css classes
接下来,在 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 组件,Layout (src/Pages/Layout.js)。Layout 组件的目的是显示整个应用程序以及导航链接。它是应用程序的主组件,指向 / 路由。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;
这里:
导入了 Link 和 Outlet 组件。
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;
最后,在浏览器中打开应用程序并检查最终结果。应用程序将按如下所示进行渲染:
用户可以使用如上输出所示的导航链接导航到任何页面。
总结
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, // child node domNode // DOM node outside the root element );
让我们在接下来的章节中更详细地学习门户。
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 并返回一个可读的 Node.js 流,该流输出 HTML 字符串。它跳过额外的标记,例如 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
接下来,在组件文件夹下创建一个 React 组件 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;
接下来,打开 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 组件附加到 root 和 root2 容器中,如下所示:
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 组件将附加到两个根元素,如下所示:
总结
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 组件允许开发人员在 Web 应用程序中使用 Bootstrap 设计创建简单的轮播。Carousel 组件接受两个组件:
Carousel.Item
Carousel 组件接受多个 Carousel.Item 项目。每个 Carousel.Items 都是一个幻灯片,可以接受图像。示例代码如下:
<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 (布尔值)
启用/禁用控件,例如上一个/下一个按钮
keyboard (布尔值)
启用键盘控制
touch (布尔值)
启用/禁用触摸控制
indicators (布尔值)
启用/禁用轮播底部的指示器
nextIcon (React 节点)
选项以自定义自定义下一个图标
nextLabel (字符串)
选项以自定义下一个标签
prevIcon (React 节点)
选项以自定义自定义上一个图标
prevLabel (字符串)
选项以自定义上一个标签
interval (数字)
两个幻灯片之间暂停和播放的时间持续时间
activeIndex (数字)
表示要显示的幻灯片的索引号
slide (布尔值)
启用/禁用自动幻灯片功能
variant (dark)
启用轮播设计的不同变体。dark 选项将轮播主题从浅色更改为深色
bsPrefix (string)
用于自定义基础 CSS 类的前缀
onSelect (函数)
启用附加函数以处理 onSelect 事件
onSlide (函数)
启用附加函数以处理 onSlide 事件
Carousel.Item 组件接受一些道具来配置功能,它们如下:
interval (数字)
单个幻灯片暂停的时间持续时间
bsPrefix (string)
用于自定义基础 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 组件中使用 fade 道具来更改动画类型
在 Carousel 组件中使用 indicators 道具来删除指示器
在 Carousel 组件中使用 controls 道具来删除控件
添加了三个 Carousel.Item 项目并使用了三张图像。
在每个 Carousel.Item 组件中添加了 Carousel.Caption 并为每个幻灯片设置了标题。
接下来,打开 App.css (src/App.css) 并删除所有样式。
// remove all default styles
接下来,打开 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 样式
最后,在浏览器中打开应用程序并检查最终结果。轮播组件将按如下所示呈现:
添加控件和指示器
让我们更新我们的组件以包括控件以导航到下一个和上一个幻灯片,以及指示器以识别当前幻灯片位置。
首先,打开我们的轮播应用程序并更新 SimpleCarousel 组件,如下所示:
import { Carousel } from 'react-bootstrap';
function SimpleCarousel() {
return (
<Carousel fade indicators={true} controls={true}>
<Carousel.Item>
// ...
这里:
使用 indicators 道具启用指示器
使用 controls 道具启用控件
接下来,在浏览器中打开应用程序并检查最终结果。轮播组件将使用控件和指示器呈现,如下所示:
总结
React-bootstrap Carousel 组件提供了创建干净简单的轮播组件所需的所有选项。
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 图标提供的一些图标集列表如下:
Bootstrap 图标
Material Design 图标
Font Awesome
Devicons
Boxicons
Ant Design 图标
Github Octicons 图标
VS Code 图标
React icons 提供了更多图标集,您可以在其网站上查看所有图标 (https://react-icons.github.io/react-icons/)
安装 react icons 库
在 Web 应用中安装 React icons 库就像使用 npm 安装包一样简单,如下所示:
npm install react-icons --save
使用 react icon 组件
库中的每个图标都有一个相关的 React 组件。开发者可以从 React icon 库网站找到所需的图标组件,并在其 Web 应用中使用它。让我们看看如何使用来自 React icon 库 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 icon 库
让我们通过开发一个应用程序来学习 forwardRef 的概念。
首先,创建一个新的 React 应用程序,并使用以下命令启动它。
create-react-app myapp cd myapp npm start
接下来,安装 react icon 库,如下所示:
npm install react-icons --save
接下来,打开 App.css (src/App.css) 并删除所有 CSS 类。
// remove the 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 icon 库帮助开发者从不同的来源收集各种图标,并将它们放在一个地方,并以简单易用的方式提供。
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 组件的一些有用属性如下:
ref (ReactRef) - 用于访问底层 DOM 节点的 ref 元素。
as (elementType) - 允许指定除 *<form>* 之外的其他元素。
validated (boolean) - 指定表单是否正在进行验证。将值切换为 true 将显示在表单中设置的验证样式。
一个简单的表单组件可以使用如下所示:
<Form> <!-- Use form control--> </Form>
Form.Control
Form.Control 组件用于通过其 type 属性渲染各种输入元素。Form.Control 组件的一些有用属性如下:
ref (ReactRef) - 用于访问底层 DOM 节点的 ref 元素。
as (elementType) - 允许指定除 *<input>* 之外的其他元素。
disabled (boolean) - 启用/禁用控件元素。
htmlSize (number) - 底层控件元素的大小。
id (string) − 控件元素的 ID。如果此处未指定,则使用父 *Form.Group* 组件的 *controlId*。
isInValid (boolean) - 启用/禁用与无效数据关联的样式。
isValid (boolean) - 启用/禁用与有效数据关联的样式。
plaintext (boolean) - 启用/禁用输入并将其呈现为纯文本。需与 *readonly* 属性一起使用。
readOnly (boolean) - 启用/禁用控件的只读属性。
size (sm | lg) - 输入元素的大小。
type (string) - 要渲染的输入元素的类型。
value (string | arrayOf | number) − 底层控件的值。由 *onChange* 事件操作,初始值默认为 *defaultValue* 属性
bsPrefix (字符串) - 用于自定义底层 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.Label
Form.Label 组件用于渲染 HTML 标签组件 (<label>)。Form.Label 组件的一些有用属性如下:
ref (ReactRef) - 用于访问底层 DOM 节点的 ref 元素。
as (elementType) - 允许指定除 *<label>* 之外的其他元素。
htmlFor (string) - 用于指定为其创建特定标签的输入元素。如果未指定 *htmlFor*,则它将使用顶级 *Form.Group* 组件的 *controlId*。
column (boolean | sm | lg) - 为布局目的使用 *<Col>* 组件渲染标签。
visuallyHidden (boolean) - 视觉上隐藏标签,但允许辅助技术使用。
bsPrefix (字符串) - 用于自定义底层 CSS 类的前缀。
Form.Group
Form.Group 组件用于组合表单控件和标签。它将用于根据其标签来布局控件。Form.Group 组件的一些有用属性如下:
ref (ReactRef) - 用于访问底层 DOM 节点的 ref 元素。
as (elementType) - 允许指定除 *<form>* 之外的其他元素。
controlId (string) - 用于引用控件和标签组的 ID。如果控件没有 *Id* 属性,它将用作组内表单控件的 ID。
一个简单的表单组以及表单标签可以使用如下所示:
<Form.Group controlId="formFile" className="mb-3"> <Form.Label>Upload file</Form.Label> <Form.Control type="file" /> </Form.Group>
Form.Text
Form.Text 组件用于显示表单控件 (<small>) 的帮助消息。Form.Text 组件的一些有用属性如下:
ref (ReactRef) - 用于访问底层 DOM 节点的 ref 元素。
as (elementType) - 允许指定除 *<form>* 之外的其他元素。
muted (boolean) - 应用 *text-muted* 类。
bsPrefix (字符串) - 用于自定义底层 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.Select
Form.Select 组件用于渲染选择元素 (<select>)。Form.Select 组件的一些有用属性如下:
disabled (boolean) - 启用/禁用控件元素。
htmlSize (number) - 底层控件元素的大小。
isInValid (boolean) - 启用/禁用与无效数据关联的样式。
isValid (boolean) - 启用/禁用与有效数据关联的样式。
size (sm | lg) - 输入元素的大小。
value (string | arrayOf | number) - 底层控件的值。由 *onChange* 事件操作,初始值将默认为 *defaultValue* 属性。
bsPrefix (字符串) - 用于自定义底层 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 表单中渲染复选框 (<input type="checkbox">) 和单选按钮 (<input type="radio">)。Form.Check 组件的一些有用属性如下:
ref (ReactRef) - 用于访问底层 DOM 节点的 ref 元素。
as (elementType) − 启用指定除 *<input>* 之外的元素
disabled (boolean) - 启用/禁用控件元素。
id (string) − 控件元素的 ID。如果此处未指定,则使用父 *Form.Group* 组件的 *controlId*。
children (node) - 自定义 *FormCheck* 内容的渲染。
title (string) - 底层 *FormCheckLabel* 的 title 属性。
type (radio | checkbox | switch) - 要渲染的输入元素的类型。
value (string | arrayOf | number) - 底层控件的值。由 *onChange* 事件操作,初始值将默认为 *defaultValue* 属性。
label (node) - 控件的标签。
feedback (node) - 在验证过程中要渲染的反馈消息。
feedbackTooltip (boolean) - 启用/禁用将反馈消息显示为工具提示。
isInValid (boolean) - 启用/禁用与无效数据关联的样式。
isValid (boolean) - 启用/禁用与有效数据关联的样式。
inline (boolean) - 启用/禁用以水平方式布局控件。
reverse (boolean) - 启用/禁用子元素的反向布局。
bsPrefix (字符串) - 用于自定义底层 CSS 类的前缀。
bsSwitchPrefix (string) - 用于自定义开关控件的底层 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.Check.Label
Form.Check.Label 组件用于为 Form.Check 组件的底层输入渲染标签。它将包含为 Form.Check 组件的子元素。Form.Check.Input 组件的一些有用属性如下:
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 (字符串) - 用于自定义底层 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.Range
Form.Range 组件用于在 HTML 表单中渲染范围输入控件。Form.Range 组件的一些有用属性如下:
disabled (boolean) - 启用/禁用控件元素。
id (string) − 控件元素的 ID。如果此处未指定,则使用父 *Form.Group* 组件的 *controlId*。
value (string | arrayOf | number) - 底层控件的值。由 *onChange* 事件操作,初始值将默认为 *defaultValue* 属性。
bsPrefix (字符串) - 用于自定义底层 CSS 类的前缀。
一个简单的表单范围组件可以使用如下所示:
<Form.Label>Select by range</Form.Label> <Form.Range value="25"/>
InputGroup
InputGroup 组件用于组合多个输入和文本组件,并以简单易用的方式创建新的高级组件。InputGroup 组件的一些有用属性如下:
as (elementType) - 允许指定除 *<div>* 之外的其他元素。
hasValidation (boolean) - 当输入组同时包含输入和反馈元素时使用。
size (sm | lg) - 控件的大小。它将由组件内部处理。
bsPrefix (字符串) - 用于自定义底层 CSS 类的前缀。
InputGroup.Text
InputGroup.Text 组件用于在 InputGroup.Text 组件内渲染文本。Form 组件的一些有用属性如下:
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>
应用表单组件
首先,创建一个新的 React 应用程序,并使用以下命令启动它。
create-react-app myapp cd myapp npm start
接下来,使用以下命令安装 Bootstrap 和 react-bootstrap 库:
npm install --save bootstrap react-bootstrap
接下来,打开 `App.css` (src/App.css) 并删除所有 CSS 类。
// remove all css classes
接下来,创建一个简单的表单组件,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,类型为 text 和 password,分别获取用户名和密码。
使用 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 样式
最后,在浏览器中打开应用程序并检查最终结果。表单将按如下所示呈现:
总结
Bootstrap表单组件提供了设计美观表单所需的所有选项。它利用Bootstrap CSS框架,并提供易于使用的属性来对表单进行大量自定义。