作者选择Creative Commons接受捐赠,作为Write for DOnations计划的一部分。
介绍
由于 React 应用程序可以快速扩展和增长,因此细微的错误很容易渗透到您的代码中。该阵营开发工具的浏览器扩展可以帮助您让您更深入地了解当前追查这些错误状态为每个组件。React Developer Tools 为您提供了一个界面,用于探索 React 组件树以及各个组件的当前props、状态和上下文。React Developer Tools 还可以让您确定哪些组件正在重新渲染,并且可以生成图表来显示单个组件需要多长时间来渲染。您可以使用此信息来追踪效率低下的代码或优化数据密集型组件。
本教程首先安装 React Developer Tools 浏览器扩展。然后,您将构建一个文本分析器作为测试应用程序,它将获取一个文本块并显示诸如字数、字符数和字符使用等信息。最后,您将使用 React Developer Tools 来探索文本分析器的组件并跟踪不断变化的 props 和上下文。这些示例将使用Chrome 浏览器,但您也可以使用Firefox插件。
在本教程结束时,您将能够开始使用 React Developer Tools 来调试和探索任何 React 项目。
先决条件
-
要使用 Chrome React Developer Tools 扩展,您需要下载并安装Google Chrome 网络浏览器或开源Chromium 网络浏览器。您还可以使用适用于FireFox 网络浏览器的React Developer Tools FireFox 插件进行操作。
-
你需要一个运行Node.js的开发环境;本教程在 Node.js 版本 10.22.0 和 npm 版本 6.14.6 上进行了测试。要在 macOS 或 Ubuntu 18.04 上安装它,请按照如何在 macOS 上安装 Node.js 和创建本地开发环境或如何在 Ubuntu 18.04 上安装 Node.js 的使用 PPA 安装部分中的步骤进行操作。
-
使用Create React App设置的 React 开发环境。要进行设置,请按照如何在 React 类组件上管理状态教程的第 1 步 — 创建空项目,这将删除非必要的样板。本教程将
debug-tutorial
用作项目名称。 -
您将在本教程中使用 React 组件和 Hooks,包括
useState
上下文 Hooks。您可以在我们的教程如何在 React 中创建自定义组件、如何使用 React 组件上的 Hook 管理状态以及如何使用上下文在 React 组件之间共享状态中了解组件和 Hook 。 -
您还需要具备 JavaScript 和 HTML 的基本知识,您可以在我们的如何使用 HTML 构建网站系列和如何在 JavaScript 中编码中找到这些知识。CSS 的基本知识也很有用,您可以在Mozilla 开发人员网络找到这些知识。
第 1 步 – 安装 React Developer Tools 扩展
在这一步中,您将在 Chrome 中安装 React Developer Tools 浏览器扩展。您将使用 Chrome JavaScript 控制台中的开发人员工具来探索debug-tutorial
您在先决条件中创建的项目的组件树。这一步将使用 Chrome,但在 Firefox 中安装 React Developer Tools 作为附加组件的步骤几乎相同。
在这一步结束时,您将在浏览器中安装 React Developer Tools,并且您将能够按名称浏览和过滤组件。
React Developer Tools 是 Chrome 和 Firefox 浏览器的插件。添加扩展程序时,您正在向开发人员控制台添加其他工具。访问React Developer Tools的Chrome 插件页面以安装扩展。
单击添加到 Chrome按钮。然后点击添加扩展按钮确认:
Chrome 将安装该扩展程序,并会在浏览器右上角的地址栏旁边显示一条成功消息和一个新图标:
如果图标没有出现,你可以通过点击拼图添加它,然后点击 React Developer Tools 的图钉图标:
当您在一个没有任何 React 组件的页面上时,该图标将显示为灰色。但是,如果您在带有 React 组件的页面上,图标将显示为蓝色和绿色。如果单击该图标,它将表明该应用程序正在运行 React 的生产版本。
访问digitalocean.com
,发现首页运行的是生产版本的 React:
现在您在使用 React 的网站上,打开控制台以访问 React 开发人员工具。通过右键单击并检查元素或通过单击“查看”>“开发人员”>“JavaScript 控制台”打开工具栏来打开控制台。
当您打开控制台时,您会发现两个新选项卡:Components和Profiler:
该组件标签会显示当前阵营组件树,与任何道具,状态,或上下文一起。该探查器选项卡可以让你记录的互动和测量组件的渲染。您将在步骤 3 中探索Profiler选项卡。
单击“组件”选项卡以查看当前的组件树。
由于这是一个生产版本,代码将被缩小并且组件将没有描述性名称:
现在您已经在一个工作网站上试用了 React Developer Tools,您可以在您的测试应用程序中使用它。如果您还没有启动debug-tutorial
应用程序,请转到终端窗口并npm start
从项目的根目录运行。
打开浏览器到http://localhost:3000
.
请注意,React Developer Tools 的图标现在是红色和白色。如果单击 React Developer Tools 图标,您将看到页面处于开发模式的警告。由于您仍在处理示例应用程序,因此这是意料之中的。
打开控制台,您将App
在“组件”选项卡中找到组件的名称。
目前还没有很多信息,但是当您在下一步中构建项目时,您将看到所有组件形成一个可导航的树。
在此步骤中,您将 React Developer Tools 扩展添加到 Chrome。您在生产和开发页面上都激活了这些工具,并debug-tutorial
在“组件”选项卡中简要浏览了您的项目。在下一步中,您将构建用于试用 React Developer Tools 功能的文本分析器。
第 2 步 – 识别实时组件道具和上下文
在此步骤中,您将构建一个小型应用程序来分析文本块。该应用程序将确定并报告输入字段中文本的字数、字符数和字符频率。在构建应用程序时,您将使用 React Developer Tools 来探索每个组件的当前状态和 props。您还将使用 React Developer Tools 查看深度嵌套组件中的当前上下文。最后,您将使用这些工具来识别在状态更改时重新呈现的组件。
在这一步结束时,您将能够使用 React Developer Tools 探索实时应用程序并观察当前的 props 和状态,而无需控制台语句或调试器。
首先,您将创建一个包含大量文本的输入组件。
打开App.js
文件:
- nano src/components/App/App.js
在组件内部,添加一个div
类为 的wrapper
,然后在<label>
元素周围创建一个<textarea>
元素:
import React from 'react';
import './App.css';
function App() {
return(
<div className="wrapper">
<label htmlFor="text">
Add Your Text Here:
<br>
<textarea
id="text"
name="text"
rows="10"
cols="100"
>
</textarea>
</label>
</div>
)
}
export default App;
这将是您的用户的输入区域。的htmlFor
属性的链接label
元件与元件id
的text
使用JSX。您还可以为<textarea>
组件提供10
行和100
列,以便为大量文本提供空间。
保存并关闭文件。接下来,打开App.css
:
- nano src/components/App/App.css
通过将内容替换为以下内容,为应用程序添加一些样式:
.wrapper {
padding: 20px;
}
.wrapper button {
background: none;
border: black solid 1px;
cursor: pointer;
margin-right: 10px;
}
.wrapper div {
margin: 20px 0;
}
在这里,您向wrapper
类添加一些填充,然后<button>
通过删除背景颜色和添加一些边距来简化子元素。最后,您为子<div>
元素添加一个小的边距。这些样式将应用于您将构建的组件以显示有关文本的信息。
保存文件。当您这样做时,浏览器将刷新,您将看到输入:
打开App.js
:
- nano src/components/App/App.js
接下来,创建一个上下文来保存<textarea>
元素的值。使用useState
Hook捕获数据:
import React, { createContext, useState } from 'react';
import './App.css';
export const TextContext = createContext();
function App() {
const [text, setText] = useState('');
return(
<TextContext.Provider value={text}>
<div className="wrapper">
<label htmlFor="text">
Add Your Text Here:
<br>
<textarea
id="text"
name="text"
rows="10"
cols="100"
onChange={e => setText(e.target.value)}
>
</textarea>
</label>
</div>
</TextContext.Provider>
)
}
export default App;
一定要导出TextContext
,然后用TextContext.Provider
. 通过向元素添加onChange
道具来捕获数据<textarea>
。
保存文件。浏览器将重新加载。确保您打开了 React Developer Tools,并注意该App
组件现在将 显示Context.Provider
为子组件。
默认情况下,组件具有通用名称Context
——但您可以通过向displayName
生成的上下文添加属性来更改它。里面App.js
,添加你设置一个行displayName
到TextContext
:
import React, { createContext, useState } from 'react';
import './App.css';
export const TextContext = createContext();
TextContext.displayName = 'TextContext';
function App() {
...
}
export default App;
没有必要添加displayName
,但它有助于在控制台中分析组件树时导航组件。您还将useState
在侧栏中看到Hook的值。在输入中输入一些文本,您将在 React Developer Tools中组件的hooks部分下看到更新的值App
。
Hook 也有一个通用名称State
,但这不像上下文那样容易更新。有一个useDebugValue
Hook,但它只适用于自定义 Hook,不推荐用于所有自定义 Hook。
在这种情况下,App
组件的状态是 to 的 prop TextContext.Provider
。单击TextContext.Provider
React Developer Tools 中的 ,您将看到value
也反映了您使用状态设置的输入值:
React Developer Tools 向您显示实时道具和上下文信息,并且随着您添加组件,价值会增加。
接下来,添加一个名为TextInformation
. 该组件将是具有特定数据分析(例如字数)的组件的容器。
首先,制作目录:
- mkdir src/components/TextInformation
然后TextInformation.js
在文本编辑器中打开。
- nano src/components/TextInformation/TextInformation.js
在组件内部,你将有三个独立的部分组成:CharacterCount
,WordCount
,和CharacterMap
。稍后您将制作这些组件。
该TextInformation
组件将使用useReducer
Hook 来切换每个组件的显示。创建一个reducer
函数来切换每个组件的显示值和一个按钮来切换每个组件的onClick
动作:
import React, { useReducer } from 'react';
const reducer = (state, action) => {
return {
...state,
[action]: !state[action]
}
}
export default function TextInformation() {
const [tabs, toggleTabs] = useReducer(reducer, {
characterCount: true,
wordCount: true,
characterMap: true
});
return(
<div>
<button onClick={() => toggleTabs('characterCount')}>Character Count</button>
<button onClick={() => toggleTabs('wordCount')}>Word Count</button>
<button onClick={() => toggleTabs('characterMap')}>Character Map</button>
</div>
)
}
请注意,您的useReducer
Hook 以一个对象开始,该对象将每个键映射到一个布尔值。reducer 函数使用扩展运算符来保留以前的值,同时使用action
参数设置新值。
保存并关闭文件。然后打开App.js
:
- nano src/components/App/App.js
添加新组件:
import React, { createContext, useState } from 'react';
import './App.css';
import TextInformation from '../TextInformation/TextInformation';
...
function App() {
const [text, setText] = useState('');
return(
<TextContext.Provider value={text}>
<div className="wrapper">
<label htmlFor="text">
Add Your Text Here:
<br>
<textarea
id="text"
name="text"
rows="10"
cols="100"
onChange={e => setText(e.target.value)}
>
</textarea>
</label>
<TextInformation />
</div>
</TextContext.Provider>
)
}
export default App;
保存并关闭文件。当您这样做时,浏览器将重新加载,您将看到更新的组件。如果您TextInformation
在 React Developer Tools 中单击,您将看到每个按钮单击时的值更新:
现在您有了容器组件,您需要创建每个信息组件。每个组件都将采用一个名为show
. 如果show
为假,组件将返回null
。组件将使用TextContext
、分析数据并显示结果。
首先,创建CharacterCount
组件。
首先,新建一个目录:
- mkdir src/components/CharacterCount
然后,CharacterCount.js
在文本编辑器中打开:
- nano src/components/CharacterCount/CharacterCount.js
在组件内部,创建一个使用show
prop 并显示null
ifshow
为假的函数:
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { TextContext } from '../App/App';
export default function CharacterCount({ show }) {
const text = useContext(TextContext);
if(!show) {
return null;
}
return(
<div>
Character Count: {text.length}
</div>
)
}
CharacterCount.proTypes = {
show: PropTypes.bool.isRequired
}
在CharacterCount
函数内部,您TextContext
使用useContext
Hook将 的值分配给变量。然后<div>
使用该length
方法返回显示字符数的 a 。最后,PropTypes
添加了一个弱类型系统来提供一些强制措施,以确保不会传递错误的 prop 类型。
保存并关闭文件。打开TextInformation.js
:
- nano src/components/TextInformation/TextInformation.js
CharacterCount
在按钮之后导入并添加组件,tabs.characterCount
作为show
道具传递:
import React, { useReducer } from 'react';
import CharacterCount from '../CharacterCount/CharacterCount';
const reducer = (state, action) => {
return {
...state,
[action]: !state[action]
}
}
export default function TextInformation() {
const [tabs, toggleTabs] = useReducer(reducer, {
characterCount: true,
wordCount: true,
characterMap: true
});
return(
<div>
<button onClick={() => toggleTabs('characterCount')}>Character Count</button>
<button onClick={() => toggleTabs('wordCount')}>Word Count</button>
<button onClick={() => toggleTabs('characterMap')}>Character Map</button>
<CharacterCount show={tabs.characterCount} />
</div>
)
}
保存文件。浏览器将重新加载,您将在 React Developer Tools 中看到该组件。请注意,当您在输入中添加单词时,上下文将更新。如果您切换组件,您将在每次点击后看到道具更新:
您还可以通过单击属性并更新值来手动添加或更改道具:
接下来,添加一个WordCount
组件。
创建目录:
- mkdir src/components/WordCount
在文本编辑器中打开文件:
- nano src/components/WordCount/WordCount.js
制作一个类似于 的组件CharacterCount
,但在显示长度之前使用空格的split
方法创建一个单词数组:
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { TextContext } from '../App/App';
export default function WordCount({ show }) {
const text = useContext(TextContext);
if(!show) {
return null;
}
return(
<div>
Word Count: {text.split(' ').length}
</div>
)
}
WordCount.proTypes = {
show: PropTypes.bool.isRequired
}
保存并关闭文件。
最后,创建一个CharacterMap
组件。此组件将显示特定字符在文本块中的使用频率。然后它将按频率对段落中的字符进行排序并显示结果。
首先,制作目录:
- mkdir src/components/CharacterMap
接下来,CharacterMap.js
在文本编辑器中打开:
- nano src/components/CharacterMap/CharacterMap.js
导入并使用TextContext
组件并使用show
prop 显示结果,就像在之前的组件中一样:
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { TextContext } from '../App/App';
export default function CharacterMap({ show }) {
const text = useContext(TextContext);
if(!show) {
return null;
}
return(
<div>
Character Map: {text.length}
</div>
)
}
CharacterMap.proTypes = {
show: PropTypes.bool.isRequired
}
这个组件需要一个稍微复杂的函数来为每个字母创建频率图。您需要遍历每个字符并在出现重复时增加一个值。然后,您需要获取该数据并对其进行排序,以便最常见的字母位于列表顶部。
为此,请添加以下突出显示的代码:
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { TextContext } from '../App/App';
function itemize(text){
const letters = text.split('')
.filter(l => l !== ' ')
.reduce((collection, item) => {
const letter = item.toLowerCase();
return {
...collection,
[letter]: (collection[letter] || 0) + 1
}
}, {})
return Object.entries(letters)
.sort((a, b) => b[1] - a[1]);
}
export default function CharacterMap({ show }) {
const text = useContext(TextContext);
if(!show) {
return null;
}
return(
<div>
Character Map:
{itemize(text).map(character => (
<div key={character[0]}>
{character[0]}: {character[1]}
</div>
))}
</div>
)
}
CharacterMap.proTypes = {
show: PropTypes.bool.isRequired
}
在此代码中,您将创建一个名为的函数itemize
,该函数使用string 方法将文本拆分为字符数组split()
。然后reduce
通过添加字符然后增加每个后续字符的计数来将数组添加到对象中。最后,使用Object.entries
和sort
将最常用的字符放在顶部,将对象转换为成对数组。
创建功能后,通过文本到函数的render
方法和map
在所述结果中显示的字符阵列值[0]
-and计数阵列值[1]
-内一个<div>
。
保存并关闭文件。此功能将让您有机会在下一节中探索 React Developer Tools 的一些性能特性。
接下来,添加新组件TextInformation
并查看 React Developer Tools 中的值。
打开TextInformation.js
:
- nano src/components/TextInformation/TextInformation.js
导入并渲染新组件:
import React, { useReducer } from 'react';
import CharacterCount from '../CharacterCount/CharacterCount';
import CharacterMap from '../CharacterMap/CharacterMap';
import WordCount from '../WordCount/WordCount';
const reducer = (state, action) => {
return {
...state,
[action]: !state[action]
}
}
export default function TextInformation() {
const [tabs, toggleTabs] = useReducer(reducer, {
characterCount: true,
wordCount: true,
characterMap: true
});
return(
<div>
<button onClick={() => toggleTabs('characterCount')}>Character Count</button>
<button onClick={() => toggleTabs('wordCount')}>Word Count</button>
<button onClick={() => toggleTabs('characterMap')}>Character Map</button>
<CharacterCount show={tabs.characterCount} />
<WordCount show={tabs.wordCount} />
<CharacterMap show={tabs.characterMap} />
</div>
)
}
保存并关闭文件。当您这样做时,浏览器将刷新,如果您添加一些数据,您将在新组件中找到字符频率分析:
在本节中,您使用 React Developer Tools 来探索组件树。您还学习了如何查看每个组件的实时 props 以及如何使用开发人员工具手动更改 props。最后,您查看了组件随输入更改的上下文。
在下一部分中,您将使用 React Developer Tools Profiler选项卡来识别具有较长渲染时间的组件。
步骤 3 — 跨交互跟踪组件渲染
在此步骤中,您将在使用示例应用程序时使用 React Developer Tools 分析器来跟踪组件渲染和重新渲染。您将浏览应用相关优化指标的火焰图或可视化,并使用这些信息来识别低效组件、减少渲染时间并提高应用速度。
在本步骤结束时,您将了解如何识别在用户交互期间呈现的组件以及如何组合组件以减少低效呈现。
查看组件如何相互更改的一种快速方法是在重新渲染组件时启用突出显示。这将使您直观地了解组件如何响应不断变化的数据。
在 React Developer Tools 中,单击设置图标。它看起来像一个齿轮:
然后选择General下的选项,即Highlight updates when components render。
当您进行任何更改时,React Developer Tools 将突出显示重新渲染的组件。例如,当您更改输入时,每个组件都会重新渲染,因为数据存储在根级别的 Hook 上,并且每次更改都会重新渲染整个组件树。
请注意组件周围的突出显示,包括屏幕顶部围绕根组件的突出显示:
将其与单击其中一个按钮以切换数据时组件重新呈现的方式进行比较。如果单击其中一个按钮,下面的组件TextInformation
将重新渲染,但不会重新渲染根组件:
显示重新渲染将使您快速了解组件之间的关系,但它不会为您提供大量数据来分析特定组件。为了获得更深入的了解,让我们看看分析器工具。
探查器工具旨在帮助您精确测量每个组件渲染所需的时间。这可以帮助您识别可能缓慢或过程密集的组件。
重新打开设置并取消选中“组件渲染时突出显示更新”框。然后单击控制台中的Profiler选项卡。
要使用分析器,请单击屏幕左侧的蓝色圆圈开始录制,完成后再次单击它:
当您停止录制时,您会发现组件更改的图表,包括每个项目的渲染时间。
为了更好地了解组件的相对效率,请粘贴知识共享的维基百科页面。这段文本足够长,可以给出有趣的结果,但又不会太大,以至于会使应用程序崩溃。
粘贴文本后,启动分析器,然后对输入进行一些小的更改。在组件完成重新渲染后停止分析。会有长时间的停顿,因为应用程序正在处理长时间的重新渲染:
当您结束录制时,React Developer Tools 将创建一个火焰图,显示重新渲染的每个组件以及重新渲染每个组件所需的时间。
在这种情况下,“Change”一词的每次按键都会导致重新渲染。更重要的是,它显示了每次渲染需要多长时间以及为什么会有很长的延迟。组件App
、TextContext.Provider
和TextInformation
重新渲染大约需要 0.2 毫秒。但是CharacterMap
由于itemize
函数中复杂的数据解析,组件每次击键需要大约 1 秒的时间来重新渲染。
在显示屏中,每个黄色条都是一个新的按键。您可以通过单击每个条来一次重播序列。请注意,渲染时间略有变化,但CharacterMap
始终很慢:
您可以通过选择选项记录分析时渲染每个组件的原因来获取更多信息。在设置的Profiler部分下。
尝试切换Word Count组件并注意更改需要多长时间。即使您没有更改文本内容,应用程序仍然滞后:
现在,当您将光标悬停在组件上时,您会发现它包含了组件重新渲染的原因。在这种情况下,组件更改的原因是父组件呈现。那是CharacterMap
组件的问题。CharacterMap
每次父更改时都会进行昂贵的计算,即使道具和上下文没有更改。也就是说,即使数据与之前的渲染相同,它也在重新计算数据。
单击“排名”选项卡,您会发现CharacterMap
与所有其他组件相比需要多长时间:
React Developer Tools 帮助隔离了一个问题:CharacterMap
组件重新渲染并在任何父组件更改时执行昂贵的计算。
有多种方法可以解决这个问题,但它们都涉及通过memoization 进行某种缓存,这是一个记住已经计算过的数据而不是重新计算的过程。你可以使用像lodash/memoize或memoize-one这样的库来缓存itemize
函数的结果,或者你可以使用内置的 Reactmemo
函数来记忆整个组件。
如果您使用 React memo
,则该函数只会在道具或上下文发生变化时重新渲染。在这种情况下,您将使用 React memo
。一般来说,您应该首先记住数据本身,因为它是一个更孤立的案例,但是如果您记住整个组件,React Developer Tools 中会有一些有趣的变化,因此您将在本教程中使用这种方法。
打开CharacterMap.js
:
- nano src/components/CharacterMap/CharacterMap.js
memo
从 React导入,然后将整个函数传递给memo
函数:
import React, { memo, useContext } from 'react';
import PropTypes from 'prop-types';
import { TextContext } from '../App/App';
...
function CharacterMap({ show }) {
const text = useContext(TextContext);
if(!show) {
return null;
}
return(
<div>
Character Map:
{itemize(text).map(character => (
<div key={character[0]}>
{character[0]}: {character[1]}
</div>
))}
</div>
)
}
CharacterMap.proTypes = {
show: PropTypes.bool.isRequired
}
export default memo(CharacterMap);
您将该export default
行移动到代码的末尾,以便memo
在导出之前将组件向右传递。之后,React 会在重新渲染之前比较 props。
保存并关闭文件。浏览器将重新加载,当您切换 时WordCount
,组件的更新速度会快得多。这一次,CharacterMap
不重新渲染。相反,在 React Developer Tools 中,您会看到一个灰色矩形,显示已阻止重新渲染。
如果您查看“排名”选项卡,您会发现CharacterCount
和WordCount
重新渲染,但原因不同。由于CharacterCount
没有被记忆,它会因为父项改变而重新渲染。在WordCount
重新渲染,因为道具发生变化。即使它被包裹在 中memo
,它仍然会重新渲染。
Note: Memoizing is helpful, but you should only use it when you have a clear performance problem, as you did in this case. Otherwise, it can create a performance problem: React will have to check props every time it re-renders, which can cause a delay on smaller components.
In this step, you used the profiler to identify re-renders and componenent re-rendering. You also used flamegraphs and ranked graphs to identify components that are slow to re-render and then used the memo
function to prevent re-rendering when there are no changes to the props or context.
Conclusion
The React Developer Tools browser extension gives you a powerful set of utilities to explore your components in their applications. With these tools, you’ll be able to explore a component’s state and identify bugs using real data without console statements or debuggers. You can also use the profiler to explore how components interact with each other, allowing you to identify and optimize components that have slow rendering in your full application. These tools are a crucial part of the development process and give you an opportunity to explore the components as part of an application and not just as static code.
如果您想了解有关调试 JavaScript 的更多信息,请参阅我们关于如何使用内置调试器和 Chrome DevTools 调试 Node.js 的文章。如需更多 React 教程,请查看我们的React 主题页面,或返回如何在 React.js 系列中编码页面。