React 和库教程(五)

React 和库教程(五)以下是一些 E2E 选项及其受欢迎程度 木偶师 66100 星柏树 24200 颗硒 18900 颗 一万颗星星 四千星如你所见 基于 GitHubstars 木偶师是最受欢迎的 E2E 图书馆

原文:React and Libraries

协议:CC BY-NC-SA 4.0

十、测试第二部分:开发和部署周期

在前一章中,我们在 Jest、Jest-dom、Enzyme 和 Sinon 库的帮助下进行了单测试。单测试只是测试的一种。除了单测试,端到端(E2E)测试模拟真实的最终用户体验,集成测试结合了测试。

这一章分为两部分。在第一部分中,我将介绍 E2E 测试,并向您展示如何使用 Jest 和 Puppeteer 库来实现它。在本章的第二部分,我将介绍如何创建一个完整的自动化开发和部署集成周期,包括完整的代码覆盖,以及如何测试您的代码并确保质量得到维护。

执行端到端测试

端到端(E2E)集成,模拟真实的最终用户体验。E2E 测试通过使用一个无头浏览器在一个真实的网络环境中进行模拟。

在本章的这一节中,我将借助 Jest 和 Puppeteer 库重点介绍 CRA 的 E2E 测试。

你为什么应该关心 E2E 测试?

E2E 测试是一种从头到尾测试我们的应用工作流程的方法。我们在 E2E 所做的是复制真实的用户场景,以便我们的应用在集成和数据完整性方面得到验证。

您可能会惊讶地发现,大多数公司都愿意部署一个完整的质量保证(QA)人工测试团队,但是不重视投资 E2E 测试。

E2E 是我经常听到开发人员说的任务之一,“我们总有一天会到达那里;这是我们的遗愿清单。”但是那一天永远不会到来,因为要专注于特性和满足紧张的最后期限。

是的,建立 E2E 测试确实需要时间,但是 E2E 测试减少了人为错误,并且有助于交付高质量的软件。一些公司已经承认了这一点,事实上,近年来,我已经看到一些公司开始重视 E2E 测试,并让全职开发人员参与进来,仅仅是为了创建和维护测试。

你知道吗,根据 QualiTest 的调查,几乎 90%的人表示,如果他们遇到错误或故障,他们会放弃一个应用,并每天进行测试。

为什么打字稿?

在本书中,我们使用了 TS。TS 在测试方面大放异彩。使用 TS,可以更容易地调试和测试你的应用,并通过描述预期的对象来防止潜在的问题。

为什么是木偶师?

以下是一些 E2E 选项及其受欢迎程度:

  • 木偶师(https://github.com/puppeteer/puppeteer):66100 星
  • 柏树(https://github.com/cypress-io/cypress):24200 颗
  • (https://github.com/SeleniumHQ/selenium):18900 颗
  • nigh watch . js(https://github.com/nightwatchjs/nightwatch):一万颗星星
  • cucumber.js ( https://github.com/cucumber/cucumber-js ):四千星

如你所见,基于 GitHub stars,木偶师是最受欢迎的 E2E 图书馆。它是基于 Chrome 的,也是我们将要使用的(见图 10-1 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-1

木偶师标志

木偶库是由谷歌 Chrome 团队创建的。木偶师让我们控制 Chrome(或 Chrome DevTools 基于协议的浏览器)并执行常见的动作,就像在真正的浏览器中一样——这意味着通过 API 以编程方式进行。

“puppeter 是一个节点库,它提供了一个高级 API 来控制 Chrome 或 DevTools 协议上的 Chrome。默认情况下,Puppeteer 无头运行,但可以配置为运行完整(非无头)Chrome 或 Chrome。

——https://github.com/puppeteer/puppeteer

我们能对木偶师做些什么并做出 React?

  • 从页面中生成截图和 pdf
  • 爬网页面
  • 自动化用户交互
  • 捕获站点的时间线跟踪,以帮助诊断性能问题
  • 可以独立使用它们,也可以将它们与 Jest 或 Mocha 等其他流行的 React 测试框架集成在一起

为什么是玩笑和玩笑木偶师?

我们在上一章已经用过 Jest ( https://github.com/facebook/jest )(见图 10-2 )。在这一章中,我们将整合 Jest 木偶师( https://github.com/smooth-code/jest-puppeteer )。Jest Puppeteer 附带了在运行测试套件时启动服务器的功能。此外,Jest Puppeteer 可以在测试完成后自动关闭服务器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-2

有一个标志

如何将 E2E 测试集成到我的 CRA React 应用中?

为了帮助您理解如何在 CRA 项目中使用 Jest 和 Puppeteer,我将本节中的过程分为三个步骤。

  • 步骤 1 :设置和配置我们的项目
  • 第二步:编写代码
  • 第三步:运行 E2E 测试

我们开始吧。

步骤 1:设置和配置我们的项目

在 Jest 的小丑鞋图标和木偶师标志之间,这里开始感觉像一个马戏团。但是说真的,正确地设置您的项目并不是一个玩笑,可能会比您预期的花费更多的时间,特别是因为我们需要考虑 CRA,它有一些固执己见的库需要配置,以及需要额外配置步骤的 TS。

开始运行 TS、Jest、Puppeteer、Jest、Enzyme 和其他必备库的 CRA 项目的最简单方法是使用我在本书中使用的 CRA 模板。让我们称我们的测试项目为e2e_testing_with_puppeteer.

 $ yarn create react-app e2e_testing_with_puppeteer --template must-have-libraries 

Tip

什么都配置好了,什么都不需要设置!

CRA MHL 项目已经包括以下:puppeteerjest-puppeteer ts-jest,和类型。

$ yarn add puppeteer jest-puppeteer ts-jest @types/puppeteer @types/expect-puppeteer @types/jest-environment-puppeteer 

除了类型puppeteer,,我们还有@types/expect-puppeteer用于jest-puppeteer的断言库,我们需要使用@types/jest-environment-puppeteer来提供全局浏览器定义。你可以在 https://github.com/smooth-code/jest-puppeteer 找到更多信息。

为了确认一切按预期工作,运行yarn start(见图 10-3 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-3

起始项目模板

$ cd e2e_testing_with_puppeteer $ yarn start 

如果你查看你的项目的文件夹(图 10-4 ,你可以看到有一个名为e2e的文件夹包含了测试和 Jest 配置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-4

e2e 文件夹包括测试和 Jest 配置

在下一节中,我将深入介绍为您设置的配置和文件,以便您完全了解如何设置 E2E。但是,如果您使用的是 CRA MHL 模板项目,则不需要设置这些。

配置我们的项目

在我们开始配置我们的项目之前,这里有一些日常规则。我正在 E2E 文件夹中设置 E2E 集成测试。E2E 可以与 Jest 的单测试集成在一起;然而,我个人认为最好将单测试和 E2E 测试的任务分开。也就是说,如果您想要或需要它们在一起,可以随意更改这种配置。

为了让我们的 E2E 测试正常工作,添加并重构了一些文件。

  • jest-puppeteer.config.js:指定一个服务器。
  • .env:设置木偶服务器的环境变量。
  • tsconfig.json:我们使用tsconfig.json来指定编译 TypeScript 项目所需的根级文件和编译器选项。
  • .eslintignore:这告诉 ESLint 忽略特定的文件和目录。
  • e2e/global.d.ts:指定全局类型。我们需要添加一个变量来“神奇地”为我们提供一个接口。
  • 那是 Jest 配置文件。
  • 我们将添加脚本标签来运行我们的测试。

让我们回顾一下这些文件和我们需要做的事情。

jest-pup Peter . config . js-设定档

文件指定了木偶师的服务器配置。

使用yarn设置本地启动,但是如果您使用 NPM 管理您的库,您可以将其更改为npm start

module.exports = { server: { command: `yarn start`, port: 3000, launchTimeout: 10000, debug: true, }, } 
。包封/包围(动词 envelop 的简写)

文件.env用于设置木偶服务器的环境变量。在这里,我告诉木偶服务器做一个飞行前检查,不要在测试中使用浏览器,所以它将在后台运行。

SKIP_PREFLIGHT_CHECK=true BROWSER=none 
tsconfig.json 文件

我们需要重构这个tsconfig.json文件来添加我们用于测试的文件夹。这将允许我们编译我们的类型脚本测试。

... "include": [ ... "e2e" ] 
。eslintingnore

需要忽略配置文件,因为我们不需要它们来遵循 React 项目的 Lint 角色。我们可以通过将这些文件添加到.eslintignore文件的末尾来忽略它们。

e2e/jest.config.js e2e/puppeteer_standalone.js jest-puppeteer.config.js 

请注意文件名puppeteer_standalone.js,我们将创建并很快讨论它来独立运行木偶剧。

e2e/jest.config.js

接下来,我们要重构jest.config.js。这个文件是 Jest 配置文件。我将它设置在e2e文件夹中,因为它允许我定义不同的jest.config.js文件(以防我需要它们)。您将看到,当我们定义我们的package.json脚本时,我们可以引用特定的脚本来使用。

testRegex中,我正在设置我们的测试以这种格式设置的项目:[component name].test.tsx。这里有一个例子:app.test.tsx

这种格式与我们设置测试的方式一致,并且这种格式将允许我们的e2e被发现。

请注意,我正在设置一个全局变量SERVER_URL,这使得我们的工作变得简单,以防我们需要为所有测试全面更新服务器名称。

module.exports = { preset: 'jest-puppeteer', globals: { SERVER_URL: "http://localhost:3000" }, testRegex: './*\\.test\\.tsx$' } console.log('RUNNING E2E INTEGRATION TESTS - MAKE SURE PORT 3000 IS NOT IN USAGE') 

注意,我使用console.log来显示一条带有注释的消息,提醒您本地主机需要运行这个测试。

这个配置将打开一个无头的 Chrome 浏览器,在端口 3000 上创建我们的测试,所以如果您运行了终端,请确保在终端中使用yarn start,因为我们不能同时运行两个localhost:3000实例。

另一种方法是在不同的端口上运行这个独立测试;如果你需要的话,你可以这样配置。我想保持简单。

e2e/全局. d.ts

global.d.ts文件让我们添加需要设置的 ts 声明。因为我们把SERVER_URLJEST_TIMEOUT放在了jest.config.js里面,我们的 Lint 规则会因为只使用这些变量而不声明它们而产生矛盾。TS 需要定义所有东西,所以通常我不建议只添加声明,但是这里我们可以例外,因为我们知道我们在做什么。

// globals defined in jest.config.js need to be included in this `d.ts` // file to avoid TS lint errorsdeclare var SERVER_URL: string declare var JEST_TIMEOUT: number 
package.json

最后,我在package.json中设置了三个测试。

  • test:e2e:运行 E2E 测试,指向我们在e2e文件夹中设置的e2e/jest.config.js配置文件。
  • 这运行我们独立的 NodeJs 木偶脚本。
  • 如果我们想让一个观察器运行,我们就用这个。
"scripts": { ... "test:e2e": "jest -c e2e/jest.config.js", "test:e2e-alone": "node e2e/puppeteer_standalone.js", "test:e2e-watch": "jest -c e2e/jest.config.js --watch" } 

步骤 2:编写代码

我们将编写一个可以独立运行的独立 Nodejs 脚本和一个 Jest Puppeteer 脚本来测试我们的App.tsx文件。

我们将创建两个文件。

  • 这个独立的文件并不是真的需要,但在某些情况下它会派上用场,例如,如果我们需要在第三方库和脚本上进行测试,或者只是想不带玩笑地进行测试。
  • e2e/app.test.tsx:为App.tsx提供 E2E 集成测试。正如我提到的,虽然理论上我们可以一起进行单测试和 E2E 测试,但是在测试的这一部分,我们将只做 E2E。

让我们看一下这些文件。

e2e/木偶师 _standalone.js

在代码层面上,我们正在创建一个无头 Chrome 浏览器,并测试到http://localhost:3000的链接将通过使用脚本它来打开 URL。然后我们等待两秒钟,关闭 Chromeless 浏览器。

我们需要在http://localhost:3000(和$yarn start)上运行我们的应用,所以我留了一条try - catch消息,以防你忘记这么做。

const puppeteer = require('puppeteer'); const SERVER_URL = 'http://localhost:3000'; (async function main(){ try { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); await page.goto(SERVER_URL, {waitUntil: 'domcontentloaded'}); const urlLink = await page.$('a[href*="https://github.com"]'); if (urlLink) { await urlLink.click(); } else { console.log('No "urlLink" found on page'); } // wait 2 secs and shut down! await new Promise(resolve => setTimeout(resolve, 2000)); await browser.close(); } catch (error) { if (error.message.includes('ERR_CONNECTION_REFUSED')) console.log('Make sure you have React running: $ yarn start'); console.log('Error message', error.message); } })(); 
e2e

对于我们的app.test.tsx组件的 E2E 测试,我将测试一些东西。

首先,我放置了一个健全检查测试来打开 Google。并检查单词谷歌是否出现。这只是一个磨利剑的示范。随意丢弃它。

我们的应用测试套件将包括以下三项测试:

  • 确保编辑在页面上。
  • 检查<a href>是否正确放置并链接到正确的 URL。
  • 检查页面上是否有 React 旋转徽标图像。

要了解更多关于我们可以运行的 E2E 测试的类型,请访问 https://devdocs.io/puppeteer/ 。

另外,我的文件以@ts-ignore开头。这是因为我在这个文件中没有 import 语句,ESLint 会对isolatedModules有问题,因为它希望这个文件成为模块的一部分。

// @ts-ignore due to isolatedModules flag - no import so this needed describe('Google', () => { beforeAll(async () => { await page.goto('https://google.com', {waitUntil: 'domcontentloaded'}) }) it('sanity check, test Google server by checking "google" text on page', async () => { await expect(page).toMatch('google') }) }) // @ts-ignore due to isolatedModules flag - no import so this needed describe('<App />', () => { beforeAll(async () => { await page.goto(SERVER_URL, {waitUntil: 'domcontentloaded'}) }, JEST_TIMEOUT) it('should include "edit" text on page', async () => { await expect(page).toMatch('Edit') }, JEST_TIMEOUT) it('should include href with correct link', async () => { const hrefsArray = await page.evaluate( () => Array.from( document.querySelectorAll('a[href]'), a => a.getAttribute('href') ) ) expect(hrefsArray[0]).toMatch('https://github.com/EliEladElrom/react-tutorials') }, JEST_TIMEOUT) it('should include the React svg correct image', async () => { const images = await page.$$eval('img', anchors => [].map.call(anchors, img => img['src'])); expect(images[0]).toMatch(SERVER_URL + '/static/media/logo.5d5d9eef.svg') }, JEST_TIMEOUT) }) 

因为 Jest 设置了 5000 毫秒的超时,这可能不足以运行您的测试,所以我将组件设置为不同的超时,以确保我不会收到这个不好的消息。请随意更改超时时间。以下是您将收到的 Jest 超时错误消息:

Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout. 

这里我不做任何用户交互,因为唯一的交互是页面上的链接,我们已经在独立的例子中测试过了。如果你想看另一个例子,比如 CRA 测试一个表单素并填充表单,请看 https://github.com/smooth-code/jest-puppeteer/tree/master/examples/create-react-app 。

步骤 3:执行 E2E 测试

既然我们已经编写了独立的 E2E NodeJS 脚本,以及对App.tsx组件的 E2E 测试,我们可以运行这些测试并检查结果。

正如您所记得的,为了运行这些测试,我们在我们的package.json文件中创建了运行脚本,以便能够用一个命令运行我们的测试。

让我们运行这些。

首先,在单独的窗口中运行以下命令:

$ yarn start # if not running $ yarn test:e2e-alone 

NPM 运行脚本与下面的package.json命令相关联:

// package.json "test:e2e-alone": "node e2e/puppeteer_standalone.js" 

当您运行该命令时,如果一切顺利,该脚本将使用我们的 localhost 3000 版本打开 Chrome headless 浏览器,并单击链接打开一个新选项卡。然后该脚本在两秒钟内关闭浏览器,您将得到以下消息。参见localhost:3000单机 E2E 测试结果。

yarn run v1.22.10 $ node e2e/puppeteer_standalone.js Done in 5.36s. 

恭喜你!我们通过了测试。

这发生得很快,因为我们设置 Chrome 在 2 秒内关闭。但是正如你所看到的,脚本打开了一个浏览器,了我们的应用中打开另一个窗口的链接(如我们所料),然后关闭了测试!印象深刻,对吧?

接下来,要使用 Jest Puppeteer 运行我们对App.tsx组件的 E2E 测试,停止本地服务器,并运行名为test:e2e的 NPM 脚本。

$ yarn test:e2e 

这将运行 NPM 运行脚本命令。

// package.json "test:e2e": "jest -c e2e/jest.config.js --watch" "test:e2e-watch": "jest -c e2e/jest.config.js --watch" 

运行该命令后,您应该会得到以下 E2E 测试 Jest 和 Puppeteer 输出:

Jest dev-server output: [Jest Dev server] $ react-scripts start [Jest Dev server] [wds]: Project is running at [Jest Dev server] [wds]: webpack output is served from [Jest Dev server] [wds]: Content not from webpack is served [Jest Dev server] [wds] : 404s will fallback to / [Jest Dev server] Starting the development server... RUNS e2e/app.test.tsx 

如果 2E 测试结果顺利,您将会收到一条甜蜜的成功消息,如下所示:

PASS e2e/app.test.tsx (15.264s) <App /> ✅ should include "edit" text on page (189ms) ✅ should include href with correct link (9ms) ✅ should include the React svg correct image (29ms) Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 15.57s, estimated 19s Ran all test suites. 

太棒了。我们能够运行独立的 E2E 测试,并覆盖我们应用的特定部分。

自动化开发和使用持续集成周期

当谈到发布高质量的代码时,有许多方法来配置您的项目,并且关于如何做有许多不同的意见。你应该用什么?应该如何配置?选择似乎是无限的。

在本章的这一部分,我的目的是给你配备一些很棒的工具,以确保你的代码质量很高,并帮助你避免打嗝。

该过程在我们的开发环境中开始,通过建立编码指南,坚持这些指南,并从那里继续创建单测试,集成这些测试(集成测试),然后进行端到端测试和覆盖。一旦我们做到了这一点,我们就可以进入持续集成(CI)周期了。

Note

持续集成意味着将所有的开发副本合并到一个共享的主线中。尽可能经常这样做。

在 CI 方面,我们的代码仍然需要测试和检查覆盖率、质量和依赖性,以确保我们确实遵循了我们设定的测试和编码指南,找到潜在的编码错误,最后开绿灯以表明部署一切正常。冲洗并重复。

在本章的这一节,我将我的最终 React 质量发展和部署周期,这是赫斯基➤木偶师➤ GitHub 工作流程➤ Codecov.io ➤工作服➤特拉维斯➤ DeepScan。

作为奖励,你可以得到一些很酷的徽章来证明你的项目达到了你设定的标准。图 10-5 显示了整个循环中使用的库。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-5

最终 React 质量开发和部署周期

动机

React 只是一个库,仅此而已。确保工程质量的任务就交给了我们。配置和设置我们项目的开发和部署周期并不容易。您应该选择什么工具,如何确保代码的质量以及您的应用符合您设定的质量水平?

此外,建立一个良好的开发和部署过程是至关重要的一部分;在我看来,这个过程应该在你写第一行代码之前设置好。理想情况下,我们希望快速发布并经常发布,并确保我们的代码质量不会随着代码库的增长而下降。

本章中的过程可以确保你一开始就获得 100%的覆盖率(如果这是你想要的),并确保你一直保持下去。目标不仅是确保您和您的团队实现开发指导方针并编写无错误的代码,理想情况下,而且任何新加入的开发人员也应该遵守这些指导方针,甚至不需要从团队领导那里获得预期实践是什么的解释。

结构

为了帮助您理解,我将该过程分解为以下步骤:

  • 第一步:设置
  • 哈士奇
  • 步骤 3: GitHub 动作
  • 第四步:Codecov.io
  • 第五步:特拉维斯
  • 工作服
  • 步骤 7:深度扫描

我们开始吧。

设置项目

你可以从一个新项目开始,或者在你喜欢的任何项目上实现这些改变。

$ yarn create react-app your_project_name --template must-have-libraries 

要确认一切正常,运行yarn start

$ cd your_project_name $ yarn start 
测试

在编写测试的过程中,我们可能会在源代码中发现新的错误或语法问题,这些问题在发布我们的应用之前必须解决。正如我们所看到的,我们的 CRA·MHL 模板项目附带了单测试和 E2E 固执己见的库来帮助你以更直观的方式编写你的测试。

新闻报道

一旦我们准备好了单测试,我们就可以检查覆盖率了。为了建立覆盖率,我们也可以使用 Jest。

Note

代码覆盖率衡量当一个特定的测试套件运行时,我们的源代码被执行了多少。

Jest 允许我们创建不同格式的报告,并设置我们希望从哪里收集这些报告(或不从哪里收集),以及确保coverageThreshold值。看看我的package.json设置,如下所示:

// package.json
"jest": {
  "coverageReporters": [
    "json",
    "text",
    "html",
    "lcov"
  ],
  "collectCoverageFrom": [
    "src//*.{js,jsx,ts,tsx}",
    "!src//*/*.d.ts",
    "!src//*/Loadable.{js,jsx,ts,tsx}",
    "!src//*/types.ts",
    "!src//store.ts",
    "!src/index.tsx",
    "!src/serviceWorker.ts",
    "!<rootDir>/node_modules/",
    "!/templates/",
    "!/template/"
  ],
  "coverageThreshold": {
    "global": {
      "branches": 50,
      "functions": 50,
      "lines": 50,
      "statements": 50
    }
  },

在这个例子中,我执行 50%的coverageThreshold。当我设置这个时,它将确保我在我的阈值内测试,否则我将得到一个错误。这很方便,因为我们可以设置这些值来确保每一个函数、语句、行和分支得到至少 50%的测试覆盖率,甚至 100%的测试覆盖率。

package.json中,我们可以通过将watchAll设置为false来设置我们的运行脚本以包含覆盖率测试,这样一旦测试完成,脚本就会关闭。

// package.json "scripts": { ... "coverage": "npm test -- --coverage --watchAll=false" } 

运行脚本后,如下所示:

$ yarn run coverage 

您将获得一个用报告创建的文件夹(图 10-6 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-6

Jest 覆盖文件夹树和文件

由于我的package.json文件包含一个 HTML 报告,我们实际上可以打开创建的coverage/index.html文件,用一个简洁的用户界面查看我们的报告(图 10-7 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-7

Jest 覆盖 HTML 报告

您可以根据需要随意配置您的项目。我的建议是,用某些运行脚本来设置您的package.json文件,以确保代码的格式,并确保风格指南得到实施,以及测试覆盖按照您的要求完成。

使用 Husky 设置 Git 挂钩

接下来,我们将安装一个名为 Husky ( https://github.com/typicode/husky )的库。

$ yarn add husky 

Husky 提供对 Git 挂钩的访问,我们可以使用这些挂钩来确保在允许我们的团队成员提交到我们的 repo 之前成功完成某些任务。看一下这个例子:

// package.json { "husky": { "hooks": { "pre-commit": "npm test", "pre-push": "npm test", "...": "..." } } } 

如果你还记得前面的章节,我们在每个完成的练习结束时手动运行formatlinttest。在我们的例子中,我们可以设置一个预提交钩子来运行formatlint和单测试以及 E2E 测试,作为预提交的一个条件。

"husky": { "hooks": { "pre-commit": "yarn format && yarn lint && yarn test:e2e && yarn coverage" } }, 

一旦你提交任何代码,这个钩子就会运行。除非这些运行脚本正确无误地完成,否则不允许提交。这将确保我们的代码遵循我们设定的准则、格式和测试。

在 GitHub repo 上设置您的库,并尝试提交以查看预提交的运行情况。

$ git add . $ git commit -m 'my first commit' 

使用 GitHub 操作设置工作流程

现在我们的代码已经在 GitHub 中完成了,我们可以确保代码的质量,然后执行具体的操作。这些操作为我们的存储库增加了自动化。

例如,我们可以确保代码编译,通过覆盖,并通过其他测试,然后将代码上传到生产服务器,或者我们可以将代码上传到其他系统进行分析。

这可以使用 GitHub 动作来完成。要了解更多关于 GitHub 动作的信息,请查看位于 https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions 的 GitHub 文档。这些动作是用一种叫做 YAML 的语言写的。

Note

YAML 是“YAML 不是标记语言”的递归首字母缩写,是一种人类可读的、直观的数据序列化格式。

要创建这些动作,您需要创建这个文件夹结构:.github/workflows。然后,我们将创建以下 GitHub 操作:

  • Main :运行 Lint,测试 Main 动作,上传到codecov
  • 构建:确保构建动作构建。
  • 测试:测试并上传测试动作到coverall
  • 皮棉:确保我们的项目通过皮棉测试。
手动操作:手. yml

我们的主要任务(main.yml)与推送提交挂钩。我们将使用 Node.js 版本 12.x 并在 Ubuntu 服务器上运行。我们将安装项目和依赖项,运行 Lint,测试并将我们的代码上传到 Codecov 应用(我们将很快设置我们的帐户)。

name: CIon: [push]jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [12.x] steps: - uses: actions/checkout@v1 - name: Use Node.js ${ 
  
    
  { matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${ 
  
    
  { matrix.node-version }} - name: install dependencies run: | yarn - name: run lint run: | yarn lint - name: run tests run: | yarn test --watchAll=false --coverage --reporters=default - name: Upload coverage to Codecov uses: codecov/codecov-action@v1.0.14 with: token: ${ 
  
    
  { secrets.CODECOV_TOKEN }} - name: build run: | yarn build 
构建操作:build.yaml

为了构建我们的代码(build.yaml),我将使用ubuntu-latest服务器(在撰写本文时,它的版本是 20.10)。默认情况下,将警告视为错误。如果你想禁用默认行为,你可以设置process.env.CI = false

你可以在 https://github.com/facebook/create-react-app/issues/3657 了解更多信息。我的钩子将被设置为在推拉请求时运行。看一看:

name: build on: - push - pull_request jobs: createAndTestTemplateCRA: runs-on: ubuntu-latest steps: - name: Use Node.js 12.x uses: actions/setup-node@v1 with: node-version: 12.x - name: Settings to fix problem on Ubuntu run: echo fs.inotify.max_user_watches= | sudo tee -a /etc/sysctl.conf - run: sudo sysctl -p - name: Create CRA from downloaded template run: npx create-react-app --template cra-template-must-have-libraries . - name: No need to fail due to warnings using CI=false run: CI= npm run build 
测试操作:test.yaml

我们的测试脚本(test.yaml)在pushpull_request上有一个钩子,并使用节点 v12。对于测试,我们将运行覆盖率脚本,并将代码上传到coverallsapp。我们将在本章的后面设置连体工作服。

name: test on: - push - pull_requestjobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Use Node.js 12.x uses: actions/setup-node@v1 with: node-version: 12.x - run: npm i - run: npm run coverage - name: Upload coverage file to Coveralls using lcov.info uses: coverallsapp/github-action@master with: github-token: ${ 
  
    
  { secrets.GITHUB_TOKEN }} 

该链接用于创建可用于 coverallsapp 和任何其他第三方工具的个人访问令牌。请遵循以下步骤:

https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token 
Lint 行动:lint.yml

对于 Lint,我们已经在开发周期中运行了 ESLint,那么为什么我们在部署周期中还需要这个(lint.yml)呢?

在这里,我们再次运行 Lint,以确保开发人员实际上正在运行 Lint,并且没有更改他的本地设置和提交没有 Lint 的代码。

name: lint on: - push - pull_request jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: npm i - run: npm run lint 

最后,您可以为每个 GitHub 动作生成一个徽章,方法是 Actions,选择动作名称,然后“创建状态徽章”,如图 10-8 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-8

GitHub 操作构建结果和徽章

Codecov.io 的测试覆盖率

Codecov 是一个用来测量你的代码库的测试覆盖率的工具。它通常通过检查在运行单测试时执行了哪些代码行来计算覆盖率。

为什么是 Codecov?

Codecov 生成的覆盖报告具有增强 CI 工作流程的功能。使用代码覆盖工具激励开发人员编写测试并提高覆盖率。

它允许我们托管我们在本地生成的覆盖报告,因此我们可以检查历史并增强我们的 CI 工作流和团队合作。

“Codecov 专注于整合和促进健康的拉式请求。Codecov 将覆盖率指标直接交付或“注入”到现代工作流中,以提高代码覆盖率,尤其是在新功能和错误修复经常出现的拉式请求中。”

——https://docs.codecov.io/docs/about-code-coverage

为此,我们可以创建一个名为codecov.yml的文件。如果您没有设置codecov.yml,默认配置会自动为您设置。你可以在 https://docs.codecov.io/docs/codecov-yaml 找到 Codecov 文档。

用你的 GitHub 账号登录 Codecov,你会看到你的回购,自从我们在 GitHub action hook 期间上传到 Codecov 之后(图 10-9 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-9

Codecov 链接存储库

Travis 的 CI

Travis 是一个托管的 CI 服务,用于构建和测试我们的代码。

为什么是特拉维斯?

通过让 Travis 调用连体工作服运行脚本.travis.yml,可以将连体工作服设置为连接到 Travis。

language: node_js sudo: false node_js: - "stable" branches: only: - master cache: directories: - node_modules before_install: - npm update - sudo apt-get update - npm install --global http-server install: - npm install - npm build script: - npm run coveralls 

要启用 Travis,请登录您的 GitHub 帐户,找到 repo,然后打开它(图 10-10 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-10

Travis CI 集成存储库

确保启用了构建推送分支。你可以建造徽章,Travis 会为你生成一个很酷的徽章(图 10-11 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-11

Travis CI 集成存储库详细信息页

用工作服跟踪代码覆盖率

工装跟踪 GitHub repos 的代码覆盖率,并确保所有新代码都被完全覆盖。

为什么穿工作服?

我们使用 Codecov,为什么我们还需要工作服?那不是多此一举吗?

Codecov 主持我们的报道,但工作服做得更多。工作服筛选覆盖数据,在问题变成问题之前寻找我们可能没有发现的问题。

创建一个有工作服的账户,打开回购,如图 10-12 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-12

集成存储库页面的工作服

接下来,如果你正在运行一个私有的回购协议,你需要使用你的密匙,并在.coveralls.yml文件中设置它。

service_name: travis-pro repo_token: [YOUR-KEY] 

您可以从套装设置中获取令牌,如图 10-13 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-13

工作服储存库密钥

接下来,package.json需要一个运行脚本来显示lcov.info文件,该文件是我们通过在 Travis 中使用的覆盖运行脚本生成的,以便让工作服链接到这些报告。

为此,在package.json文件中设置一个名为coveralls的运行脚本,为工作服显示这些脚本。

// pacakge.json "scripts": { ... "coveralls": "cat ./coverage/lcov.info | coveralls" } 

使用 DeepScan 检查代码质量

DeepScan 是一个静态分析工具,让我们可以全面地检查我们的代码。

为什么选择 DeepScan?

除了在 Lint 中设置的规则,DeepScan 还可以检查我们代码的质量。

使用您的 GitHub 帐户登录 DeepScan 并启用回购,如图 10-14 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-14

DeepScan 集成存储库

在 DeepScan 中,您可以看到回购和任何潜在问题的评级,并生成徽章,如图 10-15 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-15

DeepScan 存储库详细信息和徽章

摘要

在本章的第一部分,我介绍了 E2E 测试,并展示了如何使用 Jest 和 Puppeteer 库来模拟 E2E 测试。我们看到了如何配置我们的项目,创建独立的 E2E 测试,以及为特定的测试与 Jest 集成。

在本章的第二部分,我介绍了如何创建一个完全自动化的开发和部署集成周期。

为了帮助你理解从开发到 CI 的过程,我把这一章的过程分解成几个步骤。我们使用 Husky 创建了一个预提交挂钩,以确保一切运行无误。在 CI 部署方面,我们使用 Github 操作和 YAML 文件来检查我们的代码,并将我们的代码上传到 Codecov.io、Travis、工作服和 DeepScan,以分析和存储我们的数据和报告。最后,我向您展示了如何为您的项目生成一些很酷的徽章。

在下一章,你将学习如何调试和分析你的 React 应用。

十一、调试和分析 React 应用

当您使用 React 应用时,当您遇到问题时,可以使用一些特定的工具来调试和分析 React 应用。拥有适合工作的工具并知道如何使用它们可以消除棘手问题并加快流程。

React 基于 JavaScript (JS),适用于任何基于 JS 的应用的所有工具都适用于 React。与 Next.js 或 Razzle 等其他基于 React 的框架不同,CRA 基于开箱即用的单页面应用(SPA)。为了更好地了解我们的应用中发生了什么,有一些特定的工具可以帮助我们调试应用。在这一章中,你将学习帮助你完成工作的最佳工具。

本章分为两个部分:调试你的应用和分析你的应用。在每一节中,我将为您提供特定的工具,不仅可以帮助您调试和分析您的应用,还可以帮助确保在代码增长时性能不会下降。

您可以使用前面章节中的任何项目来学习如何调试和分析应用。

调试 React 应用

调试是检测和删除代码中可能导致不良行为的错误(也称为bug)的常见做法。本节涵盖了调试应用的八种方法。您可以使用任何一种或几种的组合。这种选择归结为您试图消除的 bug、您对代码的访问权限以及代码驻留的位置。我将介绍的调试选项如下:

  • 断点
  • 安慰
  • 调试器
  • 调试 Jests 测试

我们开始吧!

断点

在代码中设置断点并停止和检查数据通常不仅仅用于粉碎 bugs 这是任何职业发展努力的核心。

所有主流的现代 ide 都有设置断点和检查数据的选项。我将向您展示如何在 VS 代码中使用断点,这是我们在第一章中设置的,以及另一个名为 WebStorm 的 IDE,这是一个流行的 React IDE 但是,您可以选择任何 IDE,因为大多数 IDE 都可以设置为执行此任务。

使用 Visual Studio 代码调试 React 应用

在第一章,我们设置 VS 代码。VS 代码有一个调试 JavaScript 代码的特性。在本节中,我们将安装和调试我们的代码。

步骤 1 :在 VS 代码中使用顶层菜单安装附加调试器工具,参见图 11-1 :

https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-1

Chrome 调试器扩展

第二步:在项目根目录下配置一个launch.json文件,端口 3000。

// .vscode/launch.json { "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome against localhost", "url": "http://localhost:3000", "webRoot": "${workspaceFolder}" } ] } 

确保应用正在运行($ yarn start)。接下来,在App.tsx中放置一个断点。

第三步:现在从左侧菜单中选择运行。然后顶部菜单中的启动 Chrome。见图 11-2 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-2

在调试模式下为 VS 代码启动 Chrome

你可以在这里找到更多关于调试 Visual Studio 代码的内容: https://code.visualstudio.com/docs/editor/debugging 。

在 WebStorm 上调试 React 应用

在第一章,我们安装了 VS 代码。我提到还有其他流行的 ide,比如 WebStorm。我在这里将 WebStorm IDE 作为一个选项展示的原因是,尽管它需要钱,但它包括一些高级功能,可以帮助您调试和分析您的应用,并且是编写 JavaScript 代码的一个流行选项。

如果您运行的是 WebStorm,那么您可以使用配置向导像配置 WebStorm 上的任何其他项目一样配置您的项目。

顶栏中的添加配置,如图 11-3 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-3

WebStorm 中的添加配置

接下来,+图标添加新的配置,然后选择 JavaScript Debug,如图 11-4 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-4

为 JavaScript 调试添加新的配置窗口

使用名称、URL ( http://localhost:3000/)和您将使用的浏览器设置配置,如图 11-5 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-5

设置配置以调试我们的应用

现在,我们可以在要调试的行上设置断点,调试图标,开始调试 app,如图 11-6 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-6

用 WebStorm 调试 React 应用

使用控制台

在 JavaScript 中,console.log()是记录消息的实际方法。可以留言,显示数值。控制台是调试基于 web 的应用的好的、老的方法,它今天仍然适用。

浏览器包括一个控制台,用于与 web 平台 API 进行交互,您可以使用它在代码中留下消息。例如,将这个console.log放在AppRouter.tsx中:

// src/AppRouter.tsx const AppRouter: FunctionComponent = () => { console.log('console testing') return (...) } 

例如,要在 Chrome 中看到这条消息,右键单击页面,选择 Inspect,然后单击 Console 选项卡,如图 11-7 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-7

控制台选项卡中的 console.log 消息

Tip

建议您在发布代码之前隐藏这些console.log()消息,因为将它们留在这里是不专业的。也被认为是副作用。如果您还记得,我们将 ESLint 设置为在您留下消息时吠叫,并且我们使用 Husky 为 Git 配置了一个提交挂钩,以确保如果开发人员试图用这些消息提交代码时提交失败。其他方法是创建一个debug变量,在 Grunt 或 Gulp 上添加一个插件来删除这些消息,或者使用 Webpack 上的 Logger API。

使用调试器语句

如果我们想在某段代码上暂停浏览器,一个好的方法是使用调试器语句。将debugger添加到您的组件将暂停正在呈现页面的浏览器。见图 11-8 。

// src/index.tsx debugger 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-8

使用 debugger 语句停止 Chrome 浏览器的渲染

调试 Jest 测试

单测试和 E2E 测试呢?在前面的章节中,我们在 Jest 和其他库的帮助下建立了单测试和 E2E 测试。当测试失败并且您需要调试您的测试时,有两个主要的选择。

  • 使用浏览器调试
  • 设置您的 IDE
用 Chrome DevTools 调试 Jest 测试

CRA 有一个脚本来帮助使用 Jest 调试单测试和 E2E 测试。我们可以使用类似的方法来调试测试,就像我们在开发代码中所做的那样。为了调试测试,在测试中放置一个debugger语句。

// src/App.test.tsx import React from 'react' import { shallow } from 'enzyme' import App from './App' describe('<App />', () => { let component beforeEach(() => { component = shallow(<App />) }) debugger test('It should mount', () => { expect(component.length).toBe(1) }) }) 

接下来,向package.json中的运行脚本添加一个测试调试脚本。

// package.json "test:debug": "react-scripts --inspect-brk test --runInBand --no-cache" 

右键单击网页并选择 Inspect,打开 Chrome DevTools。在 Chrome DevTools 检查器中,你会看到一个绿色图标,代表为 Node.js 打开一个专用的 DevTools,如图 11-9 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-9

Chrome DevTools 检查器

现在当我们运行yarn命令时:

$ yarn test:debug 

DevTools 将在我们的调试器点停止。参见图 11-10 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-10

Node.js 的 Chrome 专用开发工具

用 VS 代码 IDE 调试 Jest 测试

调试 Jest 测试的第二个选项是使用 Visual Studio 代码中可用的调试工具。

步骤 1 :重构launch.json配置文件,使其包含以下设置:

// .vscode/launch.json { "version": "0.2.0", "configurations": [ { "name": "Debug CRA Tests", "type": "node", "request": "launch", "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/react-scripts", "args": ["test", "--runInBand", "--no-cache", "--watchAll=false"], "cwd": "${workspaceRoot}", "protocol": "inspector", "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", "env": { "CI": "true" }, "disableOptimisticBPs": true } ] } 

第二步:将debugger语句放在你喜欢的任何地方。

第三步:调试控制台按钮(看起来像带有 bug 图片的运行图标),如图 11-11 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-11

用 VS 代码调试 Jest 测试

用 WebStorm 调试 Jest 测试

在 WebStorm 中,过程是相似的。

步骤 1 :为 Jest 添加一个配置,类似于我们之前所做的。见图 11-12 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-12

在 WebStorm 中添加 Jest 的新配置

步骤 2 :接下来在运行/调试配置对话框中,可以设置配置文件,设置 Jest 包设置,如图 11-13 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-13

WebStorm 运行/调试配置对话框

第三步:接下来,把debugger语句放在你喜欢的任何地方。

步骤 4 :现在在调试模式下运行测试(红色的 bug 图标,绿色的 place 图标旁边),如图 11-14 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-14

在 Jests 测试中以调试模式运行

你可以在这里找到更多: https://www.jetbrains.com/help/webstorm/running-unit-tests-on-jest.html#ws_jest_navigation 。

此外,请访问 CRA 调试测试页面以跟上任何更新: https://create-react-app.dev/docs/debugging-tests/ 。

使用 Chrome 开发工具

您还记得,在第一章中,我们介绍了 React 如何利用虚拟 DOM (VDOM)的概念,并在内存中保存一个 UI 版本来与“真实”DOM 同步。

这是使用 React 16 中的纤程协调引擎完成的。有时候你可能需要深入了解实际的 DOM,Chrome DevTools 可以帮上忙。

为了帮助更好地查看、访问和理解 DOM,我推荐以下两个资源:

  • https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction
  • https://developers.google.com/web/tools/chrome-devtools/dom

查看 DOM 素

例如,在 CRA,我可以设置index.tsx来将AppRouter呈现到我们在index.html页面中设置的根素中(<div id="root"></div>)。

// src/index.tsx import React from 'react' import ReactDOM from 'react-dom' import './index.scss' import AppRouter from './AppRouter' import * as serviceWorker from './serviceWorker' ReactDOM.render(<AppRouter />, document.getElementById('root')) serviceWorker.unregister() 

图 11-15 显示了 DOM 结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-15

DOM 结构

然后我们可以打开 Chrome DevTools 并粘贴到控制台中。

window.document.getElementById('root') 

这将给我们根素,我们可以检查 DOM 子树素,如图 11-16 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-16

检查 Chrome DevTools 中的根素

在 DOM 更改时设置断点

Chrome DevTools 的另一个强大功能是我们可以根据某些标准设置断点,比如节点删除、子树修改和属性修改。

例如,在前面的章节中,我们创建了一个完整的站点,它包括一个主题首选项按钮,可以改变页脚和页眉的配色方案。

我们的主题按钮将页脚的颜色从亮变暗。我可以右键单击素并选择“Break on”,然后选择“属性修改”,如图 11-17 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-17

在属性修改时设置中断素

接下来,当我单击将主题更改为 dark 时,代码将在调试模式下暂停,我可以看到是什么导致了更改。浏览器的断点指向react-dom.development.js。你可以在我的网站上看到野外的现场演示: EliElrom。com 。参见图 11-18 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-18

属性更改导致调试器暂停

使用 Chrome DevTools 扩展

正如您在前面的例子中看到的,当我们检查属性更改时,我们看到的结果对于理解 React 中发生的事情没有多大意义。

然而,Chrome DevTools 的扩展可以提供帮助。

什么是 Chrome DevTools 扩展?

“一个 DevTools 扩展为 Chrome DevTools 增加了功能。它可以添加新的 UI 面板和侧边栏,与被检查的页面进行交互,获取有关网络请求的信息,等等。”

——https://developer.chrome.com/extensions/devtools

有两个有用的 React development DevTools 扩展和两个状态管理工具,您应该知道。这些是 React 开发开发工具扩展:

  • React 开发者工具 : https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
  • 意识到 React 过来 : https://chrome.google.com/webstore/detail/realize-for-react/llondniabnmnappjekpflmgcikaiilmh

这些是状态管理工具:

  • Redux : https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
  • 反冲 : https://chrome.google.com/webstore/detail/recoil-dev-tools/dhjcdlmklldodggmleehadpjephfgflc
Chrome DevTools 扩展:React 开发者工具

React 开发工具允许你在 Chrome 开发工具中检查 React 组件的层次结构。你将在 Chrome DevTools 界面中获得两个新标签。

  • ⚛组件
  • ⚛ 型材

为了测试这个工具,使用前面章节中的编码例子,比如当我们构建我们的应用时。我正在使用我的网站并导航到一个/books页面。

我们可以看到很多关于组件的信息,比如 React 的版本(撰写本文时为 v17.0.0)、props信息、路由信息以及组件层次结构,如图 11-19 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-19

React 开发人员工具,组件窗口

第二个选项卡是用于概要分析器的,在这里我们可以记录产品概要分析构建。我将在这一章的后面讲述更多关于概要分析的内容。

Chrome DevTools 扩展:React 的实现

正如您在本书中了解到的,组件是 React 的核心。一旦你安装了 React 开发工具,Realize 是一个很好的工具,可以帮助你可视化 React 组件树。

该工具有助于跟踪状态,并为您提供组件层次结构的整体概述。参见图 11-20 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-20

实现 React

下面的整体概述对我们在前几章创建的 CRA 模板项目进行了分解:

// src/AppRouter.tsx const AppRouter: FunctionComponent = () => { useEffect(() => { // console.log('AppRouter.tsx :: CheckAuthentication') }, []) return ( <Router> <ScrollToTop /> <RecoilRoot> <ToastNotification /> <ShareSocialMediaButtons /> <Suspense fallback={ <div className="home_loading_container"> <img width="250px" className="loading-home" alt="loading" src={myImage} /> </div> } > <HeaderTheme /> <Switch> <Route exact path="/" component={App} /> <Route exact path="/Home" component={Home} /> ... <Route path="/404" component={NotFoundPage} /> <Redirect to="/404" /> </Switch> <Testimonials /> <SocialMediaButtons /> <div className="footer"> <FooterTheme /> </div> </Suspense> </RecoilRoot> </Router> ) } export default AppRouter 
Chrome DevTools Extension:退火

如本书前面所述,当使用 Redux 或反冲进行状态管理时,如果能够跟踪状态的内部工作情况,那就太好了。

网上有很多关于 Redux Chrome DevTools 扩展的信息,但我想指出如何深入了解反冲,这是我们在本书前面设置的。它给出了关于原子、选择器和订阅者的信息。参见图 11-21 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-21

反冲铬 DevTools 扩展图表的书页

在我的例子中,如图 11-21 所示,原子是基于bookObject的,我可以在浏览器中检查状态值和变化。

export interface bookObject { title: string author: string pubDate: string link: string thumbnail: string } 

使用 Web 代理

我们可以使用 web 代理服务器来调试我们的应用。一个网络代理被设置为我们计算机上的另一层,它作为一个中枢,通过它所有的互联网请求将被处理。

利用查尔斯

Chrome DevTools 很棒,但有时我们需要更多。除了网络资源,查尔斯还对其他资源提出了很好的见解。例如,您可以进行节流(模拟较慢的互联网)、SSL 代理、数据格式化和 Ajax 调试,以及记录和保存不同的会话等。从这里下载查尔斯:

https://www.charlesproxy.com/latest-release/download.do 

这在处理 SSL 站点时很方便,因为数据通常是隐藏的。为了能够查看 SSL 站点的详细信息,您需要设置一个 Charles 根证书。要设置 Charles 根证书,请打开 Charles。

从 Charles 的顶部菜单中,选择帮助,SSL 代理,然后选择“安装 Charles 根证书”

每个站点或所有站点都需要信任该证书。对于 Mac,您可以连按证书并使用“信任”标签来将证书设置为始终受信任。

然后我们可以看到 SSL 站点上的安全项目,如图 11-22 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-22

Charles 配置了 SSL 代理

有关设置 SSL 证书的更多信息,请访问 https://www.charlesproxy.com/documentation/using-charles/ssl-certificates/ 。

Note

我想指出的是,查尔斯的一个缺点是它不是免费的。它有一个使用有限的试用版。然而,还有其他类似的 web 代理工具,如 Fiddler ( https://www.telerik.com/fiddler )在撰写本文时是免费的。

使用网络协议分析器

有时,我们需要更深入地了解我们的应用或 API 的网络数据,我们连接到这些数据,但无法直接控制它们。在这些情况下,您可以使用网络协议分析器。

Wireshark

当您需要更深入地了解网络数据时,例如,如果您需要对网络协议进行深入分析,Wireshark 就是标准。

它允许您深入检查数百个协议,并提供实时捕获功能。

从这里下载: https://www.wireshark.org/download.html 。

您可以过滤结果并观察网络请求。图 11-23 显示了一个例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-23

Wireshark 捕获结果

剖析您的 React 应用

与传统的 web 应用相比,当您的代码构建正确时,React 速度更快,这主要是因为使用了虚拟 DOM 概念。然而,这并不意味着你不能写内存泄漏或你不应该试图提高性能。

事实上,一个好的开发习惯是保存应用的性能配置文件,并在每次添加新功能时引用它。

在这一部分,我将与您分享四种分析 React 应用的可靠方法。

JS 中的内存是由垃圾收集器自动管理的。很容易造成内存泄漏。

Note

GC 是自动内存管理的过程。它基于收集不再需要的记忆。

但是,即使没有内存泄漏,大量的内存占用也会降低我们的应用的速度,并导致 CPU 峰值和大量内存使用。

认知度是确保你的应用性能良好的关键。你应该不断衡量你添加的新功能的表现,以及它对你现有应用的影响。在前一章中,我们自动化了我们的开发和部署。发布应用时,还应该考虑自动化性能测试。

如何描述我的 React 应用?

有很多工具可以帮助完成这项工作。以下是我在处理性能问题时最常用的选项:

  • 活动监视器/Windows 任务管理器
  • Chrome DevTools 的“性能”标签
  • React Chrome 开发工具扩展
  • React 探查器 API

在我们开始之前,我想谈谈用于概要分析的构建。

分析本地版本时,最好使用版本的生产版本,而不是开发版本,因为开发没有经过优化,与生产版本不同。

为了让产品构建在本地运行,我们正在使用的 CRA·MHL 模板项目已经包含了serve库( https://github.com/vercel/serve )。

可以全局安装serve。为此,确保您设置了对全局node_modules的读写权限并安装了它。

$ sudo chown -R $USER /usr/local/lib/node_modules $ npm install -g serve 

接下来,通过指定-- profile标志来创建一个分析构建。

将这些运行脚本添加到您的package.json文件中,以构建生产构建和分析构建,并为这些构建启动一个本地服务器。

// package.json "build:serve": "yarn build && serve -s build && open http://localhost:5000", "build:profile": "yarn build --profile && serve -s build && open http://localhost:5000" 

活动监视器/Windows 任务管理器

就我个人而言,我喜欢从我们计算机上的内置工具开始,对我们的内存占用和处理器使用情况进行 10,000 英尺的概述。

是的,它们是“穷人的”工具,但却是一个很好的起点。例如,Safari 会按网站对使用情况进行分类。

看看活动监视器使用前后的情况。图 11-24 显示浏览器加载网站前后的内存使用情况,图 11-25 显示 CPU 使用增加了 0.6%。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-25

使用 Safari 导航到“我的网站”后的活动监视器

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-24

Safari 打开后的活动监视器

浏览器的分析工具

主要的浏览器都为开发者提供了工具,可以帮助他们调试和分析你的应用。谷歌是领先的搜索引擎,为开发者提供了很好的工具。所以在这里,我将介绍 Google DevTools,但是要注意,其他流行的浏览器如 Safari、Firefox 和 Microsoft Edge 也包含开发人员工具。

Chrome DevTools 的“性能”标签

Chrome DevTools 有一个性能标签。要打开性能选项卡,请在浏览器上单击鼠标右键,然后单击检查。

接下来,选择截图和内存复选框来查看两者。

图 11-26 和 11-27 显示了一个比较 CRA 模板和我的网站的例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-27

埃利罗姆。Chrome DevTools 的“性能”标签中的 com

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-26

Chrome DevTools 的“性能”标签中的 CRA 模板

Chrome DevTools 扩展:React 开发者工具中的 Profiler

之前我们安装了 React 开发者工具扩展。该扩展有两个选项卡:组件和 Profiler。Profiler 选项卡让您深入了解什么叫做火焰图

火焰图是一个有序的图表,显示每个组件渲染所用的总时间。颜色表示渲染时间(越绿越好)以及从虚拟 DOM 到“真实”DOM 渲染或重新渲染这些变化所花费的时间。它包括不同排名的标签以及互动。参见图 11-28 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-28

我的开发版本的火焰图结果

记住,我们创建了一个运行脚本来分析生产构建。您可以比较优化构建和开发构建的不同结果。图 11-29 显示了生产构建($ yarn build:profile)的分级分析结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-29

我的生产版本的排名结果

您还可以选择进入您的产品版本,以使概要分析工作正常进行。请记住,在产品版本上设置概要分析确实会带来很小的开销,所以只在需要的时候才使用它。您可以在此处找到说明:

https://gist.github.com/bvaughn/25e6233aeb1b4f0cdb8d8366e54a3977 

React 探查器 API

React Profiler API ( https://reactjs.org/docs/profiler.html )包括一个<Profiler/>组件,可以帮助我们从源代码定制指标,并测量组件的生命周期时间。

为了测试这个组件,您可以用 CRA 模板项目建立一个新的项目,或者使用我们之前创建的任何一个项目。

$ yarn create react-app your-project-name --template must-have-libraries 

接下来,重构路由sec/AppRouter.tsx并用Profiler组件包裹它,如图 11-30 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11-30

CRA-MHL 模板的探查器 API 结果,开发版本

// src/AppRouter.tsx import { Profiler } from 'react' const AppRouter: FunctionComponent = () => { return ( <Profiler onRender={(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}`); }}> <Router> <RecoilRoot> <Suspense fallback={<span>Loading...</span>}> <Switch> <Route exact path="/" component={App} /> </Switch> </Suspense> </RecoilRoot> </Router> </Profiler> ) } 

正如你在 DevTools 的控制台标签中看到的,我得到了结果。在本例中,我记录了所有内容,但是我们可以创建一个脚本来过滤结果并以不同的方式处理它们。

摘要

在本章中,我介绍了调试和分析 React 应用的方法。在本章的第一部分,我介绍了调试 React 应用的八种方法。

  • 我向您展示了如何在 Visual Studio 代码 IDE 和 WebStorm 上创建断点和调试 React。
  • 我向您展示了如何使用 Console 选项卡并与 web 平台 API 交互。
  • 您了解了debugger语句,并使用 Chrome DevTools 以及 VS 代码和 WebStorm IDEs 将debugger语句用于 Jest 测试。
  • 我们通过学习如何查看 DOM 素以及基于 DOM 变化放置断点来了解 Chrome DevTools。
  • 我们安装了这些 Chrome DevTools 扩展:React 开发者工具、Realize、Redux 和反冲。
  • 最后,我们检查了 Charles web 代理和 Wireshark 网络协议分析器。

在本章的第二部分,我介绍了四种分析 React 应用的方法。

  • 我向您展示了如何使用活动监视器(Mac)/任务管理器(Windows)来检查内存和 CPU 占用。
  • 我介绍了 Chrome DevTools 的 Performance 选项卡以及 React Chrome DevTools 扩展的 Profiling 选项卡。
  • 最后,您学习了如何使用 React Profiler API 来获取组件的呈现时间。

在下一章,也是最后一章,我将向你展示一些技术,你可以用这些技术来优化你的 React 应用,以提高性能和应用的质量。

十二、优化您的 React 应用

优化您的代码是一个高级主题,需要确保我们交付一个高质量的产品,降低资源占用并更快地加载我们的应用。在这一章中,我将强调一些在你写第一行代码之前应该知道的优化技术。涵盖的主题包括预缓存、延迟加载、代码拆分、树抖动、预取和子画面拆分等等。

为什么我们需要优化?

正如你在这本书里看到的,CRA 是创建 React 应用的一种流行方式。它设置为使用react-scripts管理您的应用配置。它利用 Webpack 来优化产品构建,包括缩小、丑化和压缩代码。你不需要做太多,因为所有这些技术都是 CRA 自带的。

此外,在前面的章节中,我们使用了自动化开发和部署技术来提高代码的质量。具体来说,我们建立了 ESLint、Huskey、单测试、E2E 测试等等来识别糟糕的编码和弱点。

然而,尽管如此,CRA 仍然是一个香草味的,一刀切的工具,有时你会想微调和配置你的应用更多一点,以满足你的特定需求,并更多地了解正在发生的事情。此外,如果你不小心,你的应用可能会膨胀,变成一个“骗子”

Note

邱建不是错别字;这是一个低质量的过程,会导致应用的响应时间很短,或者阻碍用户交互。

CRA 所基于的单页应用(SPAs)的主要性能挑战之一是,用户需要等待组成应用的 JS 包完成下载,然后才能看到内容。

如果 JS 包变得臃肿,对于网速慢的用户来说会花费很多时间,使得你的应用运行缓慢或者对某些用户不可用。这将导致失去游客和业务。我们的目标是构建渐进式网络应用(PWAs)的应用。

“渐进式 Web 应用(PWA)是使用现代 API 构建和增强的,以提供增强的功能、可靠性和可安装性,同时使用单一代码库在任何设备上向任何人、任何地方提供服务。”

——https://web.dev/what-are-pwas/

你会学到什么?

按照本章中的步骤,您将了解如何减少应用的内存占用,避免内存泄漏,减少捆绑文件大小,只在使用中加载一次资源,减少查看内容的等待时间,提高性能,并确保它随时随地工作,甚至离线。

你还会更加了解你的应用中发生了什么,这样你就可以更好地配置你的应用,而不是使用默认设置。这是一个很大的主题,但我的目标是给你最重要的优化方法。

如何优化我的应用?

我把优化你的 CRA React TS 应用的最好方法分成几个部分。

  • 尽可能使用 PureComponent 作为类组件
  • 惰性装载
  • 预呈现静态页面
  • 预缓存—脱机工作
  • 代码分割
  • 树摇晃
  • 缩小媒体尺寸
  • 预取
  • 清除未使用的副作用事件处理程序

但是在开始之前,让我们创建一个可以用来试验和测试的项目。

我将使用我的 CRA 启动模板项目;我把它的名字改成optimize-ts

$ yarn create react-app optimize-ts --template must-have-libraries 

让我们创建一个可以用来实验的页面组件。我使用的是已经包含在 CRA MHL 模板项目中的generate模板。这些模板可以帮助我们用一个命令快速创建这些页面。

$ cd optimize-ts $ npx generate-react-cli component MyPage --type=page 

模板使用templates/page文件夹中的模板集为我们自动生成了三个文件(SCSS 风格、TS 组件和 Jest 测试文件)。我们在终端得到确认。

Stylesheet "MyPage.scss" was created successfully at src/pages/MyPage/MyPage.scss 

在 src/pages/my page/MyPage.test.tsx 中成功创建了测试“my page . test . tsx”

组件“MyPage.tsx”已在 src/pages/MyPage/MyPage.tsx 中成功创建

接下来,打开AppRouter.tsx。让我们将创建的页面添加到路由中。

// src/AppRouter.tsx import MyPage from './pages/MyPage/MyPage' const AppRouter: FunctionComponent = () => { return ( <Router> <RecoilRoot> <Suspense fallback={<span>Loading...</span>}> <Switch> <Route exact path="/" component={App} /> <Route exact path="/MyPage" component={MyPage} /> </Switch> </Suspense> </RecoilRoot> </Router> ) } 

测试一切是否正常工作(您应该只看到带有链接的微调器)。

$ yarn start 

此时,您可以导航到页面http://localhost:3000/MyPage并确保它工作正常。

接下来,让我们添加一个到我们创建的页面组件的链接,这样我们就可以从主页导航到页面。

打开src/App.tsx并添加NavLink组件,这样我们就可以将它链接到我们的页面菜单。我将它设置为一个数组,以防我们想要添加更多的页面。

<List> {[ { name: 'MyPage', url: '/MyPage' } ].map((itemObject, index) => ( <NavLink to={itemObject.url} key={itemObject.url} > <ListItem>{itemObject.name}</ListItem> </NavLink> ))} </List> 

你可以在图 12-1 中看到最终的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12-1

我们的 CRA 模板启动项目与页面和导航

您可以从这里下载用于本章的最终完整项目:

https://github.com/Apress/react-and-libraries/12/optimize-ts 

分析器包

另一个值得了解的工具是 Analyzer Bundle。它可以帮助您调试和分析您的应用。

如果我们想知道 JS 包的内幕,我们可以从 CRA 弹出( https://create-react-app.dev/docs/available-scripts/ )并修改我们的代码来查看 JS 包的内容。

在不弹出的情况下调整 CRA Webpack 配置的另一个选项是使用这个库:react-app-rewired ( https://github.com/timarney/react-app-rewired )。

但是您不需要做所有这些,因为弹出会迫使您维护配置文件。我们可以使用source-map-explorer来查看我们的包的地图。

$ npm install -g source-map-explorer 

现在,您可以打开库并直观地查看库。

$ source-map-explorer optimize-ts/build/static/js/[my chunk].chunk.js 

或者,可以使用bundle-analyzer ( https://github.com/svengau/cra-bundle-analyzer )。它更加丰富多彩,在一个页面中包含了所有的包,而不是用source-map-explorer一个一个地调用它们。

$ yarn add -D cra-bundle-analyzer 

现在我们可以创建报告了。

$ npx cra-bundle-analyzer 

一旦运行yarn build,该命令将在build/report.html中生成一个webpack-bundle-analyzer报告。

尽可能使用 PureComponent 作为类组件

正如您在前面章节中回忆的那样,React 17 在创建 React Component类时提供了两个主要选项。

  • React.Component
  • React.PureComponent

在整本书中,我们使用了PureComponent而不是React.Component,但是为什么呢?当你不需要访问shouldComponentUpdate方法时,最好使用PureComponent来代替。

extends React.PureComponent 

React.PureComponent在某些情况下提供了性能提升,但代价是失去了shouldComponentUpdate生命周期。你可以在 React 文档( https://reactjs.org/docs/react-api.html#reactpurecomponent )中了解更多。

在我们的代码中,我们不需要访问shouldComponentUpdate,所以我们可以使用PureComponent。下面是显示页面名称的初始文件的代码:

import React from 'react' import './MyPage.scss' import { RouteComponentProps } from 'react-router-dom' import Button from '@material-ui/core/Button // or React.Component export default class MyPage extends React.PureComponent<IMyPageProps, IMyPageState> { constructor(props: IMyPageProps) { super(props) this.state = { name: this.props.history.location.pathname .substring(1, this.props.history.location.pathname.length) .replace('/', ''), results: 0 } } render() { return ( <div className="TemplateName"> {this.state.name} Component </div>) ) } } interface IMyPageProps extends RouteComponentProps<{ name: string }> { // TODO } interface IMyPageState { name: string results: number } 

重新-重新-重新-重新渲染

也就是说,有时需要使用shouldComponentUpdate,因为我们可以使用该方法让 React 知道该组件不受父组件状态变化的影响,并且不需要重新呈现。在这种情况下,你需要将你的类设置为React.Component,然后你就可以访问shouldComponentUpdate

public shouldComponentUpdate(nextProps: IProps, nextState: IState) { return false // prevent rendering } 

在这些情况下,我们需要控制组件并希望停止重新呈现器。使用React.Component可以获得更好的性能,因为我们可以停止重新渲染过程。

要找到行为不端的公民,可以使用 Chrome DevTools 扩展 React Developer Tools,并使用其高亮更新复选框来查找行为不端的组件。它通过寻找过度渲染的组件来做到这一点。

惰性装载

延迟加载是提高应用性能并快速看到显著效果的最简单方法之一。我会说,这种努力是优化的低挂果实。

最好从路由开始。让我们创建一个优化的生产版本。

$ yarn build 

如果您导航到为我们创建的build/static文件夹,您可以看到我们有三个 JS 文件和一个许可证文件。见图 12-2 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12-2

CRA 生产在利用延迟加载之前构建

现在更新代码以包含延迟加载。Suspense组件将在组件的加载阶段显示,使用 lazy 方法导入组件将确保组件只在使用后才加载。

这一变化更新了我们导入组件的方式,具体如下:

import MyPage from './pages/MyPage/MyPage' 

致以下内容:

const MyPage = lazy(() => import('./pages/MyPage/MyPage')) 

组件加载时,Suspense组件包括一个回退。看一下完整的代码:

import React, { FunctionComponent, lazy, Suspense } from 'react' import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' import { RecoilRoot } from 'recoil' import App from './App' // Normal // import MyPage from './pages/MyPage/MyPage' // Lazy loading const MyPage = lazy(() => import('./pages/MyPage/MyPage')) const AppRouter: FunctionComponent = () => { return ( <Router> <RecoilRoot> <Suspense fallback={<span>Loading...</span>}> <Switch> <Route exact path="/" component={App} /> <Route exact path="/MyPage" component={MyPage} /> <Redirect to="/" /> </Switch> </Suspense> </RecoilRoot> </Router> ) } 

再次运行yarn build。您可以看到,构建脚本将我们的包块从三个文件分解为四个文件,这是因为我们采用了惰性加载。见图 12-3 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12-3

CRA 生产构建已优化

当您使用yarn start时,您看不到回退加载消息,因为它发生得太快了,因为我们的页面组件只包含页面的名称。然而,你可以在我的网站( https://elielrom.com )上看到一个例子,我为登录页面放置了延迟加载,并为回退页面放置了一个图像微调器。浏览页面,您将在第一次浏览登录页面时看到微调器。如果没有,可以减少互联网连接 DevTools。

如果你使用 Chrome DevTools 或者 Charles(关于调试的更多信息,参见第十一章),你实际上可以测量结果。这取决于你的应用的大小和你正在做的事情,但这个简单的方法可以让你获得几秒钟的时间。

Note

关于延迟加载有一点需要注意。有时候,对所有页面进行延迟加载是没有意义的,因为页面很轻。拆分和加载多个包文件可能需要更长时间。

您需要尝试一下延迟加载,因为它的效果如何取决于具体情况。此外,最好只在某些组件上包装这些惰性加载方法。例如,一个登录成员区域不是所有用户都使用的,而其他页面可以一起加载。最好的方法是试验并设置内存分析,然后降低网络速度,找出最佳用户体验。这不是放之四海而皆准的事情。

你可以在 React 文档中找到更多信息( https://reactjs.org/docs/code-splitting.html#route-based-code-splitting )。

预呈现静态页面

在某些情况下,CRA (SPA)模式非常好,因为你不会刷新页面,感觉就像在移动应用中一样。

这些页面应该在客户端呈现。CRA 一开始就不支持服务器端渲染(SSR)。但是,有一些方法可以配置路由并让 CRA 作为 SSR 工作,但是这可能需要您自己退出和维护配置,因此可能不值得这样做。

服务器端呈现(SSR)是在服务器上将客户端 JavaScript 站点呈现为静态 HTML 和 CSS 的过程,而不是在客户端(浏览器)呈现站点。

如果您正在构建需要 SSR 的更大的东西,最好使用已经配置了 SSR 的不同 React 库,如 Next.js framework、Razzle 或 Gatsby(包括如果您在构建时将网站预渲染为 HTML)。

Tip

如果你想用 React 和 Node.js 做服务器渲染,可以去看看 Next.js,Razzle,或者 Gatsby。

Create-React-App 在后端是不可知的,产生静态的 HTML/JS/CSS 包。也就是说,通过 CRA,我们可以进行预渲染,这是目前最接近 SSR 的方法。参见 CRA 文献: https://create-react-app.dev/docs/pre-rendering-into-static-html-files/ 。

有许多选项可以为每个路径或相对链接生成 HTML 页面。以下是一些例子:

  • react-snap
  • react-snapshot
  • Webpack 静态站点生成器插件

我推荐react-snap ( https://github.com/stereobooster/react-snap ),在 GitHub 上有 4000 个明星最受欢迎,和 CRA 配合的天衣无缝。react-snap使用我们在第十章中用于 E2E 测试的相同的操纵器,在你的应用中自动创建不同路径的预渲染 HTML 文件。

最大的好处是,一旦你使用了react-snap,应用并不关心 JS 包是否成功加载,因为你设置的每个页面都是独立的。

请记住,对于每个单独加载的页面,有些包可能有多余的代码,所以这是有代价的。

步骤 1 :要开始,运行以下命令:

$ yarn add --dev react-snap 

步骤 2 :接下来,添加postbuild运行脚本。

// package.json "scripts": { ... "postbuild": "react-snap" }, 

第三步:静态 HTML 几乎立即呈现。默认情况下,HTML 是有样式的,这可能会导致一个问题,称为显示“无样式内容的闪烁”(FOUC)。如果使用 CSS-in-JS 库来生成选择器,这一点尤其明显,因为 JavaScript 包必须在设置任何样式之前完成执行。

react-snap使用另一个第三方库,minimalcss ( https://github.com/peterbe/minimalcss )提取不同路线的任何关键 CSS。

您可以通过在您的package.json文件中指定以下内容来启用它:

// package.json"scripts": { ... "postbuild": "react-snap" }, "reactSnap": { "inlineCss": true }, 

第四步 : src/index.tsx是我们要补水的地方,我们也可以在那里用serviceWorker.register()注册预缓存。在下一节中,您将了解更多关于预缓存的内容。

// src/index.tsximport React from 'react' import { hydrate, render } from 'react-dom' import './index.scss' import AppRouter from './AppRouter' import * as serviceWorker from './serviceWorker' const rootElement = document.getElementById('root') if (rootElement && rootElement!.hasChildNodes()) { hydrate(<AppRouter />, rootElement serviceWorker.register() } else { render(<AppRouter />, rootElement) } 

步骤 5 :现在运行 Yarn build命令,之后,构建将通过在 CRA 配置的 NPM 脚本自动调用。你应该看到成功的结果。把你的结果和我的比较一下。

$ react-snap
✅ crawled 1 out of 3 (/)
✅ crawled 2 out of 3 (/404.html)
✅ crawled 3 out of 3 (/MyPage)
✨ Done in 29.29s.

打开 build 文件夹,会看到自动创建的静态页面,如图 12-4 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12-4

静态页面

Note

预先呈现和提供静态页面不一定总是最好的方法。这实际上会给用户带来不愉快的体验,因为每个页面都将被加载,并且组件负载会跨页面分布。对于轻量级应用,最好等待半秒钟,这样所有的内容都可以加载,这样就不会有更多的等待时间,而不是在每次页面加载时等待一会儿。您需要对此进行测试并亲自查看,但是要注意这个特性。

步骤 5 :要在本地剥离本地生产构建,运行 CRA 模板运行脚本。它使用的是serve库,所以如果你使用的是 CRA MHL 模板,你甚至不需要安装或配置package.json

运行serve运行脚本来添加本地服务器并查看生产构建。

$ yarn build:serve 

我想指出,使用 prerender 的另一个重要原因是除了优化之外对静态页面的需求:搜索引擎优化(SEO)。如果您预渲染页面,并希望生成不同的标题、描述、数据等。,对于由于 SEO 原因的每个页面,或者您需要通过社交媒体共享单个页面,请选中react-helmet,这可以帮助您为每个 React 页面组件设置唯一的标题。

如何让 react-helmet 为每个页面生成一个标题?

在这一节中,我将解释为每个页面生成标题的步骤。

第一步:安装react-helmet和 TS 的型号。

$ yarn add react-helmet @types/react-helmet 

步骤 2 :现在,我们可以重构我们的MyPage.tsx并添加Helmet组件。

import Helmet from 'react-helmet'render() { return ( <div className="MyPage"> <Helmet> <title>My Page</title> </Helmet> {this.state.name} Component </div>) } 

请注意,在我们的代码中,state 存储了页面的名称,该名称是从 React Router 中提取的,因此我们需要使用.replace('/', ')来表示this.state.name,因此如果用户刷新静态页面,它的末尾将会有about/

constructor(props: IMyPageProps) { super(props); this.state = { name: this.props.history.location.pathname.substring( 1, this.props.history.location.pathname.length ).replace('/', '') } } 

现在,如果您查看源代码,一旦您单击 MyPage 链接,它就会有标题。

预缓存:脱机工作

能够离线是 PWA 的一个核心功能。我们可以用一个serviceWorker来做。

CRA 将serviceWorker包含在索引文件中。

serviceWorker.unregister() 

这是什么意思?

CRA 包括一个用于生产构建的 Workbox webpack 插件( https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin )。

要启用此功能,只需将serviceWorker状态更改为register

serviceWorker.register() 

我们已经在前面的部分中为生产进行构建时添加了serviceWorker

const rootElement = document.getElementById('root') if (rootElement && rootElement!.hasChildNodes()) { hydrate(<AppRouter />, rootElement) serviceWorker.register() } else { render(<AppRouter />, rootElement) }// serviceWorker.unregister() 

现在当您再次构建($yarn build)时,新文件出现:build/precache-manifest.[string].js

见图 12-5 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12-5

添加到静态文件夹中的运行时主包文件

要查看工作器的运行情况,您需要再次发布构建。

$ yarn build:serve. 

看看 Chrome DevTools 的网络标签。在尺寸栏中,你可以看到上面写着“(ServiceWorker)”,如图 12-6 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12-6

预缓存服务人员出现在 Chrome DevTools 的网络选项卡上

你现在可以通过关闭网络并连接或者选择 Chrome DevTools 的网络选项卡上的离线复选框来模拟离线体验。刷新应用,它仍然可以工作!

你的应用如何离线工作?

CRA 的工作箱默认预缓存策略是CacheFirst。静态资产从服务工作者缓存机制中检索,如果失败,则发出网络请求。

一个工具箱支持不同的策略,如CacheOnlyNetworkFirst等。,但 CRA 可能需要被驱逐,以使用不同于默认的策略。

https://create-react-app.dev/docs/making-a-progressive-web-app/ 了解更多关于此功能的信息。

代码拆分

当我们使用延迟加载时,我们能够将 JS 包分解成多个块,并在只需要它们的时候提供服务。

动态导入

我们可以做得更多。CRA 用 Webpack 处理代码拆分任务。我们可以告诉 Webpack 进一步拆分我们的 JS 包,并动态导入这些模块。

让我们来看看。创建文件src/page/MyPage/math.tsx并添加以下代码:

// src/page/MyPage/math.tsx export function square(x: number) { return x * x }export function cube(x: number) { return x * x * x }export function add(x: number, y: number) { return x + y } 

为了使用add方法,我们通常会这样写代码:

import { add } from './math' console.log(add(1, 2)) 

但是,如果我们想要分割代码,以便 JS 包只在需要时才被检索,并且只绑定使用过的内容,我们可以执行以下操作:

import("./math").then(math => { console.log(math.add(1, 2)) }) 

让我们创建一个实际的工作示例。通过使用 math add函数,我们可以有一个包含可变结果的状态和一次单击更新。看一看MyPage.tsx代码,如下所示:

// src/page/MyPage/MyPage.tsx render() { const onClickHandler = (event: React.MouseEvent) => { event.preventDefault() import('./math').then((math) => { this.setState({ results: (math.add(1, 2)) }) }) } return ( <div className="MyPage"> <Helmet> <title>My Page</title> </Helmet> {this.state.name} Component <Button type="submit" onClick={onClickHandler}> Math.add </Button> {this.state.results} </div> ) } 

或者更好的是,让我们添加每次的结果。

import('./math').then((math) => { this.setState(prevState => { const newState = prevState.results + (math.add(1, 2)) return ({ ...prevState, results: newState }) }) }) 

现在构建生产代码,并在本地机器上运行它。

$ yarn build:serve 

Tip

我们的构建设置为预缓存,所以你需要在 Mac 上通过按 Shift+Refresh 来强制清除缓存,或者你可以在 Chrome 网络商店找到一个奇特的插件来做这件事。否则,你可能是在服务老页面,拔头发。

见图 12-7 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12-7

Meth.add 在本地运行生产版本的代码分割功能

请注意,我们的包增加了 1,当我们调用MyPage和按钮时,包文件将被检索,而不是在我们第一次加载页面时让用户等待包。见图 12-8 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12-8

代码拆分后的 JS 包

模块联盟

我们能做得更多吗?答案是有也没有。

在撰写本文时,CRA 的 Webpack 版本为 4.42.0(见node_modules/react-scripts/package.json)。然而,截至 2020 年 10 月,Webpack 的当前版本是 v5.4.0,它包括了大量的性能改进。一个主要的问题涉及到模块联合。

模块联合允许您将远程 Webpack 构建导入到应用中。使用 Webpack v5,我们不仅可以从我们的项目中导入这些块,还可以从不同的源(项目)中导入这些块。你可以在 https://webpack.js.org/guides/build-performance/ 了解更多信息。

您可以弹出和升级 Webpack。如果你需要的话,确保你自己构建并包含模块联合。

树摇晃

摇树 ( https://webpack.js.org/guides/tree-shaking/ )是 JavaScript 上下文中使用的一个术语,意思是删除死代码。当谈到删除死代码时,有很多事情要做。

当我说死代码时,它可能意味着这两件事:

  • 从未执行过的代码:运行时永远不能执行的代码
  • 结果从未使用过:代码被执行,但结果从未使用过

比如在我们搭建的 app 里,我在src/AppRouter.tsx里定义了反冲。

<RecoilRoot> <Suspense fallback={<span>Loading...</span>}> <Switch> <Route exact path="/" component={App} /> <Route exact path="/MyPage" component={MyPage} /> <Redirect to="/" /> </Switch> </Suspense> </RecoilRoot> 

但是在这个例子中,我从来没有使用反冲功能。

现在,如果我们深入我们的 JS 包,看看发生了什么,我们可以看到反冲使用了几乎 38.19KB。见图 12-9 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12-9

我们的捆绑带反冲的源图

$ source-map-explorer optimize-ts/build/static/js/[my chunk].chunk.js 

这里我们重构代码,去掉反冲,重新构建:

<Router> <Suspense fallback={<span>Loading...</span>}> <Switch> <Route exact path="/" component={App} /> <Route exact path="/MyPage" component={MyPage} /> <Redirect to="/" /> </Switch> </Suspense> </Router> 

现在代码从 276KB 下降到 224.51KB,反冲不包括在内。参见图 12-10 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12-10

无后座力我们束的来源图

CRA 包括现成的 Webpack,将我们的应用与一些已经为我们设置好的设置捆绑在一起。

您可以看到包含compressionOptimizeCSSAssetsPlugin的优化标签。

如果需要更改,需要自行弹出和维护。然而,即使没有弹射,我们也可以做一些事情。

为了更好地理解幕后发生的事情,在 CRA 打开 Webpack 配置文件,它位于react-scripts中。

$ open node_modules/react-scripts/config/webpack.config.js 

或者对于开发服务器,使用以下命令:

$ open node_modules/react-scripts/config/webpackDevServer.config.js 

先说副作用。

Note

副作用是在被调用函数之外可以观察到的状态变化,而不是它的返回值。副作用的例子包括:改变外部变量或对象属性(全局变量或父函数作用域链中的变量)的值、没有说明符的导入语句(即import 'someLib')、登录到控制台、获取数据、设置订阅或手动改变 DOM。

如果您使用 Webpack,您可以指导 Webpack 如何处理库。事实上,大多数 NPM 图书馆都有副作用。

如果您查看 CRA Webpack 的webpack.config.js文件,您会看到导入的库都设置了sideEffects: true标志。这是根据每个库设置的。

为我们不使用的库导入会增加我们代码的大小(JS 包)。这些进口商品应该取消。

导入所需模块与使用不带说明符的 Import 语句

我们来看一个例子。假设我添加了一个import声明,说明我没有使用和导入我需要的特性,比如useRecoilValue

// src/page/MyPage/MyPage.tsx import { useRecoilValue } from 'recoil' 

CRA react-script工具允许我开始并构建我的生产构建,但我会得到一条警告消息,如图 12-11 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12-11

未使用代码的警告

有这些警告不好,但也不是世界末日。

在这个具体的例子中,Webpack 正在使用的 Terser ( https://github.com/terser/terser )试图解决这个问题,并决定不需要这个代码。因此,尽管它创建了一个警告,但是它并没有在我们的 build JS 包中包含这个库。

代码没有包括在内,因为这个包已经过优化,库已经为我们移除了(您可以通过检查源代码图来验证这一点)。

但是不要依赖 Terser 总是能够解决这个问题。当它不能时,它将包括死代码。

然而,如果我使用一个没有说明符(import 'recoil')的import语句,就像这样:

// src/page/MyPage/MyPage.tsx import 'recoil' 

代码将为我们编译、构建,甚至运行 ESLint,没有任何警告,但它将包括整个反冲库。

原因是 Webpack 将不带说明符(import 'recoil')的import语句视为副作用,并且它将在我们的源映射中包含反冲,就好像我们有意这样做一样。

Note

只包含您需要的模块,避免使用没有说明符的import语句。

您还可以微调并将sideEffects属性添加到项目的package.json文件中,以告诉 Webpack 如何在不弹出的情况下处理副作用。你可以在 Webpack 文档中读到更多关于这个和关于树摇动的内容: https://webpack.js.org/guides/tree-shaking/ 。

缩小介质尺寸

我们讨论了树抖动和将 JS 包的大小减到最小,但是除了包的大小之外,在一个应用中经常使用其他资源,这会占用很多资源。这些是媒体文件,如图像、视频、音频文档以及与大数据同步。

要优化资源,有很多工具可以使用。如果你有 Adobe 产品,你有带“存储为 Web 格式”选项的 Photoshop,你可以用它来确保你的图像保持较小的尺寸。Adobe Premiere 可以针对不同的设备和不同的设置对您的视频进行编码。

您可以使用一个库来检查用户的网络速度,并根据用户的连接提供不同的资源。这是视频传输的常见做法。

理想情况下,我们希望在运行时而不是编译时上传资源,因为我们不希望用户等待资源。

另一件事是 SVG。SVG 是基于向量的,非常棒。它给用户清晰的图形外观,在任何分辨率的屏幕尺寸;然而,这是有代价的。React 提高性能的方法是减少对服务器的请求数量。

导入小于 10KB 的图像会返回数据 URI,而不是实际的 SVG 文件;参见 React 文档( https://create-react-app.dev/docs/adding-images-fonts-and-files/ )。这在 CRA 开发中被默认忽略,但是您可以在产品构建中看到这种行为;见IMAGE_INLINE_SIZE_LIMIT ( https://create-react-app.dev/docs/advanced-configuration )。

拥有大量的 SVG 图形很容易使你的应用膨胀。最好将它们收集到一个 JPG 图像文件中,并使用图像精灵。加载单个图像比逐个加载单个图像更快。

预取

你可能已经看到 React 中的高阶组件(hoc)可以增强组件的能力( https://reactjs.org/docs/higher-order-components.html )。对于我们的 JS 包,我们可以采用类似的方法。

我们希望首先加载页面,然后检索 JS 包,这样我们就可以尽快显示页面。

让我们来看看。如果你用$ yarn build:serve构建了一个量产版,并在 Chrome DevTools 中测试,你可以看看 JS bundle chunks 的层次结构;他们在顶端。参见图 12-12 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12-12

没有为 JS 包设置以特定顺序显示的层次结构

我们想把这些包裹移到底部。为此,我们可以使用 Quicklink ( https://github.com/GoogleChromeLabs/quicklink )。Quicklink 通过使用技术来决定首先加载什么,试图使后续页面的导航加载得更快。让我们安装它。

$ yarn add -D quicklink webpack-route-manifest 

在我们的案例中,我们将使用 React CRA 水疗中心。我们将使用 React HOC,在这里我们希望为我们延迟加载的页面添加预取功能。为此,我们只需使用一个空的 option 对象,并用 Quicklink HOC 包装我们的组件。

<Route exact path="/MyPage" component={withQuicklink(MyPage, options)} /> 

看看这里显示的重构后的src/AppRouter.tsx:

// src/AppRouter.tsx import React, { FunctionComponent, lazy, Suspense } from 'react' import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom' import { RecoilRoot } from 'recoil' // @ts-ignore // eslint-disable-next-line import/extensions import { withQuicklink } from 'quicklink/dist/react/hoc.js' import App from './App' import ScrollToTop from './components/ScrollToTop/ScrollToTop' // Lazy loading const MyPage = lazy(() => import('./pages/MyPage/MyPage')) const options = { origins: [] } const AppRouter: FunctionComponent = () => { return ( <Router> <ScrollToTop /> <RecoilRoot> <Suspense fallback={<span>Loading...</span>}> <Switch> <Route exact path="/" component={App} /> <Route exact path="/MyPage" component={withQuicklink(MyPage, options)} /> <Redirect to="/" /> </Switch> </Suspense> </RecoilRoot> </Router> ) } export default AppRouter 

如你所见,在实现了逻辑之后,HOC 工作了,现在我们的块在底部,如图 12-13 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12-13

为“关于页面”组件设置的层次结构

清理未使用的事件处理程序

下面是如何清理任何未使用的事件处理程序。

在 useEffect 挂钩中设置副作用

如果我们想使用浏览器 API 滚动到每个页面更新的顶部,我们甚至不需要编写一个类。我们可以将代码包装在 React 函数的useEffect钩子中。

// src/components/ScrollToTop/ScrollToTop.tsx import { useEffect } from 'react' import { useLocation } from 'react-router-dom' export default function ScrollToTop() { const { pathname, search } = useLocation() useEffect( () => () => { try { window.scroll({ top: 0, left: 0, behavior: 'smooth', }) } catch (error) { // older browsers fallback window.scrollTo(0, 0) } }, [pathname, search] ) return null } 

Note

副作用需要包在useEffect里。如果处理不当,副作用会在每次渲染时出现,并导致内存泄漏。

我们还需要重构src/AppRouter.tsx,将组件包含在 React 路由标记中。

// src/AppRouter.tsx import ScrollToTop from './components/ScrollToTop/ScrollToTop' const AppRouter: FunctionComponent = () => { return ( <Router> <ScrollToTop /> ... </Router> ) } 

这个特殊的副作用不需要任何清理,因为我们没有附加任何事件。

清除副作用

组件卸载后留下事件处理程序会导致内存泄漏。幸运的是,一旦组件被自动卸载,React 组件可以清理基于 React 的事件处理程序。

但是,如果我们需要使用浏览器滚动 API 事件监听器,我们可以,如下所示:

window.addEventListener('scroll', scrollHandler) 

您必须自己手动删除该事件,因为 React 不会为您删除它。

window.removeEventListener('scroll', scrollHandler) 

您可以在 React 文档( https://reactjs.org/docs/hooks-effect.html )中了解更多相关信息。

我最后的笔记

很高兴知道可以做多少来优化我们的应用。在我们这一方,只要付出一点努力,我们就可以提高应用的性能,改善用户体验。即使在一个小应用上,结果也是显而易见的。此外,优化您的应用可以让您了解正在发生的事情、它的工作原理以及需要改进的地方。

也就是说,优化我们的应用就是测试、调整和再次测试结果,以微调一切。在一些用例中,做所有或任何优化工作都是没有意义的。我们添加的每个功能都有一个权衡。

你需要试验,每个特性都需要逐个检查。最好的方法是记录内存分析、限制网络连接、检查捆绑包、离线,并尝试不同的网络速度,以找出最佳的用户体验。

请始终记住,您的开发和生产版本是互不相同的,所以不要假设它们的工作方式是一样的。这不是放之四海而皆准的事情。

摘要

在本章中,我向您展示了如何将您的应用消耗的内存量降至最低,减少捆绑文件大小,只加载一次资源,减少查看内容的等待时间,提高性能,并确保您的应用随时随地工作,即使在离线时也是如此。

我们还安装了 Analyzer Bundle,并查看了您可以配置的其他设置,以使您的应用更好,而不是使用默认设置。

我们将这个过程分解为以下几个方面:使用PureComponent,延迟加载,预存储,预缓存,代码分割,树抖动,减小媒体大小,预取,以及清除副作用。

这个主题足够写一整本书,但我的目的是给你一个好的起点,并涵盖你需要了解的最重要的方面。

例如,我没有介绍网络流量技术。另外,如果渲染一个大的列表,可以使用react-window ( https://github.com/bvaughn/react-window ),这样只会渲染一个大数据集的一部分(刚好够填满视口);react-infinite-scroll-component;或者react-paginate。这个清单还在继续。

这一章非常适合作为本书的最后一章,因为它是一个高级主题,你在这里学到了如何对你构建的应用进行最后的润色。我想再次感谢你购买这本书,并祝贺你完成这一章!请随时给我留言,让我知道这本书是如何帮助你的。你也可以在网上留下书评,并把这本书推荐给朋友。

作为购买这本书的奖励材料,前往 https://elielrom.com/ReactQuestions 领取一本免费电子书,里面有最常见的面试问题,包括答案。

今天的文章 React 和库教程(五)分享到此就结束了,感谢您的阅读。
编程小号
上一篇 2025-01-06 10:11
下一篇 2025-01-06 10:06

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/103512.html