如何使用 React 开发工具调试 React 组件

作者选择了 Creative Commons以作为 Write for Donations计划的一部分获得捐赠。

介绍

由于 React 应用程序可以快速扩展和增长,所以微妙的错误很容易渗透到您的代码中。 React Developer Tools 浏览器扩展可以帮助您跟踪这些错误,通过为每个 组件提供更多关于当前 状态的见解。 React Developer Tools 提供了探索 React 组件树的界面,以及当前 props,状态和 context的单个组件。

此教程从安装 React Developer Tools 浏览器扩展开始. 然后,您将构建一个文本分析器作为测试应用程序,该应用程序将需要一个文本块并显示信息,如词数,字符数和字符使用情况。 最后,您将使用 React Developer Tools 来探索文本分析器的组件,并跟踪不断变化的优惠和背景。 示例将使用 Chrome 浏览器,但您也可以使用 Firefox的插件。

到本教程结束时,您将能够开始使用 React 开发工具来调试和探索任何 React 项目。

前提条件

步骤 1 – 安装 React 开发工具扩展

在此步骤中,您将安装 Chrome 中的 React Developer Tools 浏览器扩展程序。您将使用 Chrome JavaScript 控制台中的开发人员工具来探索您在前提条件中创建的debug-tutorial项目的组件树。

在此步骤结束时,您将在浏览器中安装 React Developer Tools,您将能够根据名称探索和过滤组件。

React Developer Tools 是 Chrome 和 Firefox 浏览器的插件。当您添加扩展时,您正在向开发者控制台添加额外的工具。 请访问 Chrome 插件页面以获取 React Developer Tools 来安装扩展。

点击添加到Chrome按钮,然后点击添加扩展按钮,以确认:

Chrome add extension button

Chrome 将安装扩展,一个成功消息和一个新的图标将在您的浏览器的右上角显示在地址栏旁边:

Chrome success message

如果图标不显示,您可以通过点击拼图片来添加它,然后点击 React Developer Tools 的 pushpin 图标:

Pin extension

当您在没有 React 组件的页面上时,该图标将显示为灰色,但是,如果您在具有 React 组件的页面上,图标将显示为蓝色和绿色。

访问 digitalocean.com,发现主页正在运行 React 的生产版本:

DigitalOcean React Production Build information

现在您已在使用 React 的网站上,请打开控制台以访问 React 开发工具. 通过右键单击并检查一个元素或通过单击 ** 查看 > 开发人员 > JavaScript 控制台**来打开控制台。

当你打开控制台时,你会发现两个新的卡: ** 组件和 ** 配置文件:

Console Open

** 组件** 卡将显示当前的 React 组件树,以及任何附件、状态或背景。 ** Profiler** 卡允许您记录交互并测量组件渲染。

点击组件标签以查看当前组件树。

由于这是一个生产构建,代码将是 minified并且组件将没有描述性名称:

Components for digitalocean.com in the console

现在你已经在工作网站上尝试了 React Developer Tools,你可以在测试应用程序中使用它. 如果你还没有启动你的调试教程应用程序,请进入终端窗口并从项目的根源运行npm start

打开一个浏览器到 http://localhost:3000

请注意, React Developer Tools 图标现在是红色和白色。如果您点击 React Developer Tools 图标,您将看到一个警告,该页面处于开发模式。

打开控制台,您将在组件选项卡中找到应用组件的名称。

Base Component

目前还没有太多信息,但当您在下一步构建项目时,您将看到您的所有组件形成一个可航行的树。

在此步骤中,您已将 React Developer Tools 扩展添加到 Chrome. 您已在生产页面和开发页面上激活了工具,并在 Components 页面中简短地探索了您的 debug-tutorial 项目. 在下一步中,您将构建文本分析器,您将使用它来测试 React Developer Tools 的功能。

步骤 2 – 识别实时组件特性和背景

在此步骤中,您将构建一个小型应用程序来分析一个文本块。应用程序将确定并报告输入字段中的文本的单词数、字符数和字符频率。当您构建应用程序时,您将使用 React Developer Tools 来探索每个组件的当前状态和特权。

到此步骤结束时,您将能够使用 React 开发工具来探索实时应用程序,并观察当前的支持和状态,而无需控制台声明或调试。

首先,您将创建一个输入组件,它将占用大量的文本。

打开App.js文件:

1nano src/components/App/App.js

在组件内部,添加一个div与一个类wrapper,然后创建一个围绕一个 <textarea> 元素<label> 元素:

 1[label debug-tutorial/src/components/App/App.js]
 2import React from 'react';
 3import './App.css';
 4
 5function App() {
 6  return(
 7    <div className="wrapper">
 8     <label htmlFor="text">
 9       Add Your Text Here:
10       <br>
11       <textarea
12         id="text"
13         name="text"
14         rows="10"
15         cols="100"
16       >
17       </textarea>
18      </label>
19    </div>
20  )
21}
22
23export default App;

这将是您的用户的输入区域。 htmlFor 属性将 标签 元素链接到使用 JSXid' 的 text` 元素中。

保存并关闭文件,然后打开App.css:

1nano src/components/App/App.css

通过用以下内容代替应用程序添加一些风格:

 1[label debug-tutorial/src/components/App.App.css]
 2.wrapper {
 3    padding: 20px;
 4}
 5
 6.wrapper button {
 7    background: none;
 8    border: black solid 1px;
 9    cursor: pointer;
10    margin-right: 10px;
11}
12
13.wrapper div {
14    margin: 20px 0;
15}

在这里,您将添加一些包装类,然后通过删除背景颜色并添加一些边缘来简化孩子<按钮>元素。

当你这样做时,浏览器会更新,你会看到输入:

Text area

打开App.js:

1nano src/components/App/App.js

接下来,创建一个 context,以保持从 <textarea> 元素的值. 使用 useState Hook捕捉数据:

 1[label debug-tutorial/src/components/App/App.js]
 2import React, { createContext, useState } from 'react';
 3import './App.css';
 4
 5export const TextContext = createContext();
 6
 7function App() {
 8  const [text, setText] = useState('');
 9
10  return(
11    <TextContext.Provider value={text}>
12      <div className="wrapper">
13        <label htmlFor="text">
14          Add Your Text Here:
15          <br>
16          <textarea
17            id="text"
18            name="text"
19            rows="10"
20            cols="100"
21            onChange={e => setText(e.target.value)}
22          >
23          </textarea>
24        </label>
25      </div>
26    </TextContext.Provider>
27  )
28}
29
30export default App;

请确保导出TextContext,然后用TextContext.Provider包装整个组件。

浏览器将重新加载. 请确保打开 React Developer Tools,并注意应用组件现在将Context.Provider显示为儿童组件。

Component context in React Developer Tools

该组件的默认名称为Context,但您可以通过将displayName属性添加到生成的背景中来更改它。

 1[label debug-tutorial/src/components/App/App.js]
 2import React, { createContext, useState } from 'react';
 3import './App.css';
 4
 5export const TextContext = createContext();
 6TextContext.displayName = 'TextContext';
 7
 8function App() {
 9    ...
10}
11
12export default App;

您不需要添加显示名称,但在分析控制台中的组件树时,它有助于导航组件。在侧面栏中,您还会看到useState的值。

Update Value in Developer Tools

Hook 也有状态的通用名称,但这并不像背景一样容易更新。 有一个 useDebugValue] Hook,但它只在自定义的 Hooks 上工作,并且不建议所有自定义的 Hooks 使用。

在这种情况下,对应用程序组件的状态是TextContext.Provider的标签。在 React 开发工具中,点击TextContext.Provider,你会看到也反映了你与状态设置的输入值:

Updated value for the context

React 开发工具正在向您展示实时支持和背景信息,当您添加组件时,价值将增加。

接下来,添加一个名为文本信息的组件,该组件将是具有特定数据分析的组件的容器,例如字数。

首先,创建地址:

1mkdir src/components/TextInformation

然后在文本编辑器中打开TextInformation.js

1nano src/components/TextInformation/TextInformation.js

在组件中,您将有三个独立的组件: CharacterCount, WordCountCharacterMap. 您将在一瞬间创建这些组件。

TextInformation组件将使用useReducer链接来切换每个组件的显示。创建一个Reducer函数,切换每个组件的显示值,以及一个按钮以切换每个组件的onClick操作:

 1[label debug-tutorial/src/components/TextInformation/TextInformation.js]
 2import React, { useReducer } from 'react';
 3
 4const reducer = (state, action) => {
 5  return {
 6    ...state,
 7    [action]: !state[action]
 8  }
 9}
10export default function TextInformation() {
11  const [tabs, toggleTabs] = useReducer(reducer, {
12    characterCount: true,
13    wordCount: true,
14    characterMap: true
15  });
16
17  return(
18    <div>
19      <button onClick={() => toggleTabs('characterCount')}>Character Count</button>
20      <button onClick={() => toggleTabs('wordCount')}>Word Count</button>
21      <button onClick={() => toggleTabs('characterMap')}>Character Map</button>
22    </div>
23  )
24}

请注意,您的 useReducer Hook 以一个对象开始,该对象将每个密钥绘制为布尔式。 缩减函数使用 spread operator来保存以前的值,同时使用 action 参数设置一个新值。

保存并关闭文件,然后打开App.js:

1nano src/components/App/App.js

添加你的新组件:

 1[label debug-tutorial/src/components/App/App.js]
 2import React, { createContext, useState } from 'react';
 3import './App.css';
 4import TextInformation from '../TextInformation/TextInformation';
 5
 6...
 7
 8function App() {
 9  const [text, setText] = useState('');
10
11  return(
12    <TextContext.Provider value={text}>
13      <div className="wrapper">
14        <label htmlFor="text">
15          Add Your Text Here:
16          <br>
17          <textarea
18            id="text"
19            name="text"
20            rows="10"
21            cols="100"
22            onChange={e => setText(e.target.value)}
23          >
24          </textarea>
25        </label>
26        <TextInformation />
27      </div>
28    </TextContext.Provider>
29  )
30}
31
32export default App;

保存和关闭文件. 当您这样做时,浏览器将重新加载,您将看到更新的组件. 如果您在 React Developer Tools 中点击TextInformation,您将在每个按钮上看到更新值:

Update Reducer on Click

现在你有容器组件,你需要创建每个信息组件. 每个组件将采取一个名为显示的支撑。 如果显示是假的,该组件将返回无效

要开始,创建CharacterCount组件。

首先,创建一个新目录:

1mkdir src/components/CharacterCount

然后,在文本编辑器中打开CharacterCount.js:

1nano src/components/CharacterCount/CharacterCount.js

在组件中,创建一个函数,该函数使用显示口号并显示null,如果显示是假的:

 1[label debug-tutorial/src/components/CharacterCount/CharacterCount.js]
 2import React, { useContext } from 'react';
 3import PropTypes from 'prop-types';
 4import { TextContext } from '../App/App';
 5
 6export default function CharacterCount({ show }) {
 7  const text = useContext(TextContext);
 8
 9  if(!show) {
10    return null;
11  }
12
13  return(
14    <div>
15      Character Count: {text.length}
16    </div>
17  )
18}
19
20CharacterCount.proTypes = {
21  show: PropTypes.bool.isRequired
22}

CharacterCount函数中,您将TextContext的值分配给使用useContext链接的变量,然后返回一个<div>,显示使用长度方法的字符数。

保存并关闭文件. 打开 TextInformation.js:

1nano src/components/TextInformation/TextInformation.js

输入CharacterCount并在按钮后添加组件,将tabs.characterCount作为显示标签:

 1[label debug-tutorial/src/components/TextInformation/TextInformation.js]
 2import React, { useReducer } from 'react';
 3import CharacterCount from '../CharacterCount/CharacterCount';
 4
 5const reducer = (state, action) => {
 6  return {
 7    ...state,
 8    [action]: !state[action]
 9  }
10}
11
12export default function TextInformation() {
13  const [tabs, toggleTabs] = useReducer(reducer, {
14    characterCount: true,
15    wordCount: true,
16    characterMap: true
17  });
18
19  return(
20    <div>
21      <button onClick={() => toggleTabs('characterCount')}>Character Count</button>
22      <button onClick={() => toggleTabs('wordCount')}>Word Count</button>
23      <button onClick={() => toggleTabs('characterMap')}>Character Map</button>
24      <CharacterCount show={tabs.characterCount} />
25    </div>
26  )
27}

保存文件. 浏览器将重新加载,您将在 React Developer Tools 中看到该组件. 请注意,当您在输入中添加单词时,背景将更新。

Adding text and toggling

您还可以通过单击属性并更新值来手动添加或更改 prop:

Manually Changing Props

接下来,添加一个WordCount组件。

创建该目录:

1mkdir src/components/WordCount

在文本编辑器中打开文件:

1nano src/components/WordCount/WordCount.js

创建一个类似 CharacterCount 的组件,但在显示长度之前使用 split 方法在空格中创建一个 array的单词:

 1[label debug-tutorial/src/components/WordCount/WordCount.js]
 2import React, { useContext } from 'react';
 3import PropTypes from 'prop-types';
 4import { TextContext } from '../App/App';
 5
 6export default function WordCount({ show }) {
 7  const text = useContext(TextContext);
 8
 9  if(!show) {
10    return null;
11  }
12
13  return(
14    <div>
15      Word Count: {text.split(' ').length}
16    </div>
17  )
18}
19
20WordCount.proTypes = {
21  show: PropTypes.bool.isRequired
22}

保存并关闭文件。

最后,创建一个CharacterMap组件,该组件会显示在文本块中使用特定字符的频率,然后将字符按通道的频率排序,并显示结果。

首先,创建地址:

1mkdir src/components/CharacterMap

接下来,在文本编辑器中打开CharacterMap.js:

1nano src/components/CharacterMap/CharacterMap.js

导入并使用TextContext组件,然后使用显示插件显示结果,就像您在以前的组件中一样:

 1[label debug-tutorial/src/components/CharacterMap/CharacterMap.js]
 2import React, { useContext } from 'react';
 3import PropTypes from 'prop-types';
 4import { TextContext } from '../App/App';
 5
 6export default function CharacterMap({ show }) {
 7  const text = useContext(TextContext);
 8
 9  if(!show) {
10    return null;
11  }
12
13  return(
14    <div>
15      Character Map: {text.length}
16    </div>
17  )
18}
19
20CharacterMap.proTypes = {
21  show: PropTypes.bool.isRequired
22}

此组件将需要一个稍微更复杂的功能来创建每个字母的频率地图. 你需要通过每个字符并增加一个值,任何时候有重复。

要做到这一点,添加以下突出的代码:

 1[label debug-tutorial/src/components/CharacterMap/CharacterMap.js]
 2
 3import React, { useContext } from 'react';
 4import PropTypes from 'prop-types';
 5import { TextContext } from '../App/App';
 6
 7function itemize(text){
 8 const letters = text.split('')
 9   .filter(l => l !== ' ')
10   .reduce((collection, item) => {
11     const letter = item.toLowerCase();
12     return {
13       ...collection,
14       [letter]: (collection[letter] || 0) + 1
15     }
16   }, {})
17 return Object.entries(letters)
18   .sort((a, b) => b[1] - a[1]);
19}
20
21export default function CharacterMap({ show }) {
22  const text = useContext(TextContext);
23
24  if(!show) {
25    return null;
26  }
27
28  return(
29    <div>
30     Character Map:
31     {itemize(text).map(character => (
32       <div key={character[0]}>
33         {character[0]}: {character[1]}
34       </div>
35     ))}
36    </div>
37  )
38}
39
40CharacterMap.proTypes = {
41  show: PropTypes.bool.isRequired
42}

在此代码中,您创建一个名为itemize的函数,该函数将文本分为一个字符的数组(https://andsky.com/tech/tutorials/understanding-arrays-in-javascript)使用split()字符串方法,然后通过添加字符并增加每个后续字符的数目来 [reduce](https://andsky.com/tech/tutorials/how-to-use-array-methods-in-javascript-iteration-methods#reduce()对象的数组。

创建函数后,您将文本传递给函数在渲染方法和地图的结果,以显示字符 - 数组值 [0] - 和计数 - 数组值 [1] - 在一个 <div>

保存和关闭文件. 此功能将为您提供在下一节中探索 React Developer Tools 的某些性能功能的机会。

接下来,将新组件添加到TextInformation,并查看 React Developer Tools 中的值。

打开TextInformation.js:

1nano src/components/TextInformation/TextInformation.js

导入和重置新组件:

 1[label debug-tutorial/src/components/TextInformation/TextInformation.js]
 2import React, { useReducer } from 'react';
 3import CharacterCount from '../CharacterCount/CharacterCount';
 4import CharacterMap from '../CharacterMap/CharacterMap';
 5import WordCount from '../WordCount/WordCount';
 6
 7const reducer = (state, action) => {
 8  return {
 9    ...state,
10    [action]: !state[action]
11  }
12}
13
14export default function TextInformation() {
15  const [tabs, toggleTabs] = useReducer(reducer, {
16    characterCount: true,
17    wordCount: true,
18    characterMap: true
19  });
20
21  return(
22    <div>
23      <button onClick={() => toggleTabs('characterCount')}>Character Count</button>
24      <button onClick={() => toggleTabs('wordCount')}>Word Count</button>
25      <button onClick={() => toggleTabs('characterMap')}>Character Map</button>
26      <CharacterCount show={tabs.characterCount} />
27      <WordCount show={tabs.wordCount} />
28      <CharacterMap show={tabs.characterMap} />
29    </div>
30  )
31}

当您这样做时,浏览器将更新,如果您添加一些数据,您将在新组件中找到字符频率分析:

CharacterMap Component in React Developer Tools

在本节中,您使用了 React Developer Tools 来探索组件树,您还了解了如何查看每个组件的实时配置文件,以及如何使用开发人员工具手动更改配置文件。

在下一节中,您将使用 React Developer Tools Profiler 选项卡来识别具有长渲染时间的组件。

步骤 3 – 跟踪跨互动的组件渲染

在此步骤中,您将使用 React Developer Tools 配置文件来跟踪组件渲染和再渲染,当您使用样本应用程序时,您将导航 flamegraphs 或可视化应用程序的相关优化指标,并使用信息来识别效率低下的组件,减少渲染时间,并增加应用程序的速度。

到本步骤结束时,您将知道如何识别在用户交互中渲染的组件,以及如何组成组件以减少效率低下的渲染。

了解组件如何相互变化的一种快速方法是允许在重新渲染组件时进行突出显示,这将为您提供对组件如何应对变化的数据的视觉概述。

在 React Developer Tools 中,点击设置图标,它将看起来像一个工具:

Settings icon

然后选择 ** 一般 ** 下的选项,该选项表示 ** 亮点更新当组件渲染**。

Highlight changes

例如,当您更改输入时,每个组件都会重新渲染,因为数据存储在根层的胡克上,每个更改都会重新渲染整个组件树。

注意围绕组件的突出点,包括围绕根组件的屏幕顶部:

Highlighting Text

如果您点击其中一个按钮,则TextInformation下的组件将重新渲染,而不是根组件:

Rerendering lower components only

显示重现器会让你快速了解组件是如何关联的,但不会给你大量的数据来分析特定组件。

Profiler 工具旨在帮助您准确测量每个组件所需的时间,从而帮助您识别可能缓慢或过程密集的组件。

重新打开设置,然后卸载选取 高清更新时组件渲染的选项框,然后单击控制台中的 Profiler选项卡。

要使用配置文件,请点击屏幕左侧的蓝色圆圈开始录制,然后在完成时再次点击:

Start profiling

当您停止录制时,您会看到组件变更的图表,包括每个项目的渲染需要多久。

要了解组件的相对效率,请插入 [维基百科版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版版

在文本中粘贴后,启动配置文件,然后对输入进行小小的更改. 停止配置文件后,该组件已完成重新渲染. 将有一个长时间的休息,因为应用程序正在处理一个长时间的重新渲染:

Adding a change with a lot of text

完成录制后,React Developer Tools 会创建一个闪电图,显示每一个重新渲染的组件以及重新渲染每个组件需要多长时间。

在这种情况下,从更改这个词的每一个键盘都会产生重新渲染,更重要的是,它会显示每个渲染需要多长时间,以及为什么出现了长时间的延迟。 由于itemize函数中的复杂数据分析,App、TextContext.Provider和TextInformation的组件需要大约2毫秒的时间来重新渲染。

在显示屏中,每个黄色栏都是一个新的键盘。您可以通过点击每个栏来一次播放序列。 请注意,渲染时间有轻微的变化,但‘CharacterMap’始终是缓慢的:

Looking at the flamegraph

您可以通过选择选项 ** 记录为什么每个组件在配置设置的 ** 配置文件** 部分中进行渲染来获取更多信息。

"Record why" Option of the Profiler Tab

尝试转换 Word Count组件,并注意更改需要多长时间。

Word Count toggle flamegraph

现在,当你在一个组件上旋转导向器时,你会发现它包含了该组件重新渲染的原因。在这种情况下,该组件更改的原因是 ** 原始组件渲染**. 这对于CharacterMap组件是一个问题。

点击 Ranked 选项卡,你会发现 CharacterMap 与所有其他组件相比需要多长时间:

Ranked Tab

React 开发工具有助于孤立一个问题:CharacterMap 组件在任何主组件更改时都会重新渲染并执行昂贵的计算。

解决问题的方法有很多,但它们都涉及通过 memoization 进行某种类型的缓存,这是一种记忆而不是重新计算已经计算的数据的过程. 您可以使用一个库,例如 lodash/memoizememoize-one来缓存itemize函数的结果,或者您可以使用 React 内置的 memo函数来缓存整个组件。

如果你使用了React备忘录,该函数只会重新渲染如果备忘录或背景发生变化。在这种情况下,你会使用React备忘录。一般来说,你应该先记忆数据本身,因为它是一个更孤立的案例,但如果你记忆整个组件,React开发人员工具有一些有趣的变化,所以你将在本教程中使用这种方法。

打开CharacterMap.js:

1nano src/components/CharacterMap/CharacterMap.js

从 React 中导入备忘录,然后将整个函数传入备忘录函数:

 1[label debug-tutorial/src/components/CharacterMap/CharacterMap.js]
 2import React, { memo, useContext } from 'react';
 3import PropTypes from 'prop-types';
 4import { TextContext } from '../App/App';
 5
 6...
 7
 8function CharacterMap({ show }) {
 9  const text = useContext(TextContext);
10
11  if(!show) {
12    return null;
13  }
14
15  return(
16    <div>
17     Character Map:
18     {itemize(text).map(character => (
19       <div key={character[0]}>
20         {character[0]}: {character[1]}
21       </div>
22     ))}
23    </div>
24  )
25}
26
27CharacterMap.proTypes = {
28  show: PropTypes.bool.isRequired
29}
30
31export default memo(CharacterMap);

您将导出默认行移动到代码的末尾,以便在导出前直接将组件传送到备忘录

保存和关闭文件. 浏览器将重新加载,当您切换WordCount,组件将更快地更新。 这一次,CharacterMap不会重新渲染。

React Developer Tools showing that CharacterMap did not re-render

如果你看看 Ranked选项卡,你会发现,无论是CharacterCount还是WordCount都被重新渲染,但由于不同的原因。由于CharacterCount没有被回忆,它是因为父母改变而重新渲染的。

Ranked view of memoized app

<$>[注] 注: 记忆是有用的,但你应该只使用它当你有一个明显的性能问题,就像你在这种情况下一样。

在此步骤中,您使用了配置文件来识别重新渲染器和组件再渲染器. 您还使用了闪电图和排序图来识别慢再渲染的组件,然后使用了备忘录函数来防止重新渲染,当没有对代理或背景的更改时。

结论

React Developer Tools浏览器扩展为您提供一组强大的实用工具来探索您的组件在他们的应用程序中. 使用这些工具,您将能够探索组件的状态,并使用实际数据识别错误,而无需控制台声明或调试程序。 您还可以使用配置文件来探索组件如何相互作用,允许您识别和优化在您的整个应用程序中具有缓慢渲染的组件。

如果您想了解有关调试 JavaScript 的更多信息,请参阅我们的文章 如何使用内置调试器和 Chrome DevTools 调试 Node.js. 有关更多 React 教程,请参阅我们的 React 主题页面,或返回 如何在 React.js 系列中编码页面

Published At
Categories with 技术
comments powered by Disqus