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

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Creative Commons 来接受捐赠。

介绍

由于 React 应用程序可以快速扩展和增长,因此细微的错误很容易渗透到您的代码中。 React 开发者工具浏览器扩展 可以帮助您跟踪这些错误,让您更深入地了解每个 组件 的当前 状态 。 React Developer Tools 为您提供了一个界面,用于探索 React 组件树以及各个组件的当前 props、状态和 context。 React Developer Tools 还可以让您确定哪些组件正在重新渲染,并且可以生成图表来显示各个组件需要多长时间来渲染。 您可以使用此信息来追踪低效代码或优化数据密集型组件。

本教程首先安装 React Developer Tools 浏览器扩展。 然后,您将构建一个文本分析器作为测试应用程序,它将获取一个文本块并显示诸如字数、字符数和字符用法等信息。 最后,您将使用 React 开发人员工具来探索文本分析器的组件并跟踪不断变化的道具和上下文。 示例将使用 Chrome 浏览器 ,但您也可以使用 Firefox 的插件。

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

先决条件

第 1 步 — 安装 React 开发者工具扩展

在这一步中,您将在 Chrome 中安装 React Developer Tools 浏览器扩展。 您将使用 Chrome JavaScript 控制台 中的开发人员工具来探索您在先决条件中创建的 debug-tutorial 项目的组件树。 此步骤将使用 Chrome,但在 Firefox 中将 React 开发者工具安装为 附加组件 的步骤几乎相同。

在这一步结束时,您将在浏览器中安装 React 开发人员工具,并且您将能够按名称浏览和过滤组件。

React 开发者工具是 Chrome 和 Firefox 浏览器的插件。 添加扩展时,您正在向开发人员控制台添加其他工具。 访问 React Developer Tools 的 Chrome 插件页面 以安装扩展。

单击 添加到 Chrome 按钮。 然后点击【X18X】添加扩展【X35X】按钮确认:

Chrome 将安装扩展程序,成功消息和新图标将出现在浏览器右上角地址栏旁边:

如果图标没有出现,你可以通过点击拼图来添加它,然后点击 React Developer Tools 的图钉图标:

当您在没有任何 React 组件的页面上时,该图标将显示为灰色。 但是,如果您在带有 React 组件的页面上,该图标将显示为蓝色和绿色。 如果单击该图标,它将指示该应用程序正在运行 React 的生产版本。

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

现在您在使用 React 的网站上,打开控制台以访问 React 开发人员工具。 通过右键单击并检查元素或通过单击 View > Developer > JavaScript console 打开工具栏来打开控制台。

当您打开控制台时,您会发现两个新选项卡:ComponentsProfiler

Components 选项卡将显示当前的 React 组件树,以及任何道具、状态或上下文。 Profiler 选项卡可让您记录交互并测量组件渲染。 您将在 Step 3 中探索 Profiler 选项卡。

单击 Components 选项卡以查看当前组件树。

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

现在您已经在一个工作网站上试用了 React Developer Tools,您可以在您的测试应用程序中使用它。 如果您尚未启动 debug-tutorial 应用程序,请转到终端窗口并从项目的根目录运行 npm start

打开浏览器到 http://localhost:3000。

请注意,React Developer Tools 的图标现在是红色和白色的。 如果您单击 React 开发人员工具图标,您将看到页面处于开发模式的警告。 由于您仍在处理示例应用程序,因此这是意料之中的。

打开控制台,您会在 Components 选项卡中找到 App 组件的名称。

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

在这一步中,您将 React Developer Tools 扩展添加到 Chrome。 您在生产和开发页面上都激活了这些工具,并在 Components 选项卡中简要探索了您的 debug-tutorial 项目。 在下一步中,您将构建文本分析器,您将使用它来试用 React 开发人员工具的功能。

第 2 步 - 识别实时组件道具和上下文

在此步骤中,您将构建一个小型应用程序来分析文本块。 该应用程序将确定并报告输入字段中文本的字数、字符数和字符频率。 在构建应用程序时,您将使用 React Developer Tools 来探索每个组件的当前状态和 props。 您还将使用 React 开发人员工具在深度嵌套的组件中查看当前上下文。 最后,您将使用这些工具来识别在状态更改时重新呈现的组件。

在这一步结束时,您将能够使用 React 开发人员工具来探索实时应用程序并观察当前的道具和状态,而无需控制台语句或调试器。

首先,您将创建一个需要大量文本的输入组件。

打开App.js文件:

nano src/components/App/App.js

在组件内部,添加一个div与一类wrapper ,然后创建一个元素围绕一个[1]

调试教程/src/components/App/App.js

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 属性使用 JSXlabel 元素链接到具有 textid 的元素。 您还为 <textarea> 组件提供 10 行和 100 列,以便为大量文本提供空间。

保存并关闭文件。 接下来,打开App.css

nano src/components/App/App.css

通过将内容替换为以下内容,为应用程序添加一些样式:

调试教程/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

接下来,创建一个 context 来保存 <textarea> 元素中的值。 使用 useState Hook 捕获数据:

调试教程/src/components/App/App.js

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

调试教程/src/components/App/App.js

import React, { createContext, useState } from 'react';
import './App.css';

export const TextContext = createContext();
TextContext.displayName = 'TextContext';

function App() {
    ...
}

export default App;

不需要添加 displayName,但在控制台中分析组件树时,它确实有助于导航组件。 您还将在侧栏中看到 useState 挂钩的值。 在输入中键入一些文本,您将在 App 组件的 hooks 部分下的 React Developer Tools 中看到更新的值。

Hook 也有一个通用名称 State,但这并不像上下文那样容易更新。 有一个 useDebugValue Hook,但它只适用于自定义 Hook,不推荐用于所有自定义 Hook。

在这种情况下,App 组件的状态是 TextContext.Provider 的属性。 点击 React Developer Tools 中的 TextContext.Provider,你会看到 value 也反映了你设置的 state 的输入值:

React 开发者工具向你展示了实时的 prop 和上下文信息,并且随着你添加组件,价值会增长。

接下来,添加一个名为 TextInformation 的组件。 该组件将是具有特定数据分析(例如字数统计)的组件的容器。

首先,制作目录:

mkdir src/components/TextInformation

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

nano src/components/TextInformation/TextInformation.js

在组件内部,您将拥有三个独立的组件:CharacterCountWordCountCharacterMap。 稍后您将制作这些组件。

TextInformation 组件将使用 useReducer Hook 来切换每个组件的显示。 创建一个 reducer 函数来切换每个组件的显示值,并创建一个按钮来使用 onClick 操作来切换每个组件:

调试教程/src/components/TextInformation/TextInformation.js

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

添加新组件:

调试教程/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;

保存并关闭文件。 当您这样做时,浏览器将重新加载,您将看到更新后的组件。 如果你在 React Developer Tools 中点击 TextInformation,你会看到每次点击按钮的值都会更新:

现在您已经有了容器组件,您需要创建每个信息组件。 每个组件都将采用一个名为 show 的道具。 如果 show 为假,则组件将返回 null。 组件将消耗 TextContext,分析数据并显示结果。

首先,创建 CharacterCount 组件。

首先,新建一个目录:

mkdir src/components/CharacterCount

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

nano src/components/CharacterCount/CharacterCount.js

在组件内部,创建一个使用 show 属性的函数,如果 show 为假,则显示 null

调试教程/src/components/CharacterCount/CharacterCount.js

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 函数中,使用 useContext 挂钩将 TextContext 的值分配给变量。 然后使用 length 方法返回一个显示字符数的 <div>。 最后,PropTypes 添加了一个弱类型系统来提供一些强制措施,以确保不会传递错误的道具类型。

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

nano src/components/TextInformation/TextInformation.js

导入 CharacterCount 并在按钮之后添加组件,将 tabs.characterCount 作为 show 属性传递:

调试教程/src/components/TextInformation/TextInformation.js

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 开发人员工具中看到该组件。 请注意,当您在输入中添加单词时,上下文将更新。 如果你切换组件,你会在每次点击后看到 props 更新:

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

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

创建目录:

mkdir src/components/WordCount

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

nano src/components/WordCount/WordCount.js

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

调试教程/src/components/WordCount/WordCount.js

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 属性来显示结果,就像您在之前的组件中所做的那样:

调试教程/src/components/CharacterMap/CharacterMap.js

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
}

这个组件需要一个稍微复杂的函数来为每个字母创建频率图。 您需要遍历每个字符并在重复时增加一个值。 然后,您需要获取该数据并对其进行排序,以便最常见的字母位于列表顶部。

为此,请添加以下突出显示的代码:

调试教程/src/components/CharacterMap/CharacterMap.js

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 的函数,该函数使用 split() 字符串方法将文本拆分为 array 字符。 然后通过添加字符然后增加每个后续字符的计数,将数组 reduce 减少为一个对象。 最后,使用 Object.entriessort 将对象转换为对数组,将最常用的字符放在顶部。

创建函数后,在 render 方法和 map 方法中将文本传递给函数以显示字符 - 数组值 [0] - 和计数 -数组值 [1] — 在 <div> 内。

保存并关闭文件。 此功能将使您有机会在下一节中探索 React 开发者工具的一些性能特性。

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

打开TextInformation.js

nano src/components/TextInformation/TextInformation.js

导入并渲染新组件:

调试教程/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 开发者工具来探索组件树。 您还学习了如何查看每个组件的实时道具以及如何使用开发人员工具手动更改道具。 最后,您通过输入查看了组件更改的上下文。

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

第 3 步 — 跨交互跟踪组件渲染

在此步骤中,您将在使用示例应用程序时使用 React Developer Tools 分析器来跟踪组件渲染和重新渲染。 您将导航 flamegraphs 或应用程序相关优化指标的可视化,并使用这些信息来识别低效组件、减少渲染时间并提高应用程序速度。

到此步骤结束时,您将了解如何识别在用户交互期间呈现的组件以及如何组合组件以减少低效呈现。

查看组件如何相互更改的一种快速方法是在重新渲染组件时启用突出显示。 这将使您直观地了解组件如何响应不断变化的数据。

在 React 开发者工具中,点击 Settings 图标。 它看起来像一个齿轮:

然后选择 General 下的选项,显示 Highlight 在组件渲染时更新

当您进行任何更改时,React 开发人员工具将突出显示重新渲染的组件。 例如,当您更改输入时,每个组件都会重新渲染,因为数据存储在根级别的 Hook 中,并且每次更改都会重新渲染整个组件树。

注意组件周围的突出显示,包括根组件周围的屏幕顶部:

将其与单击其中一个按钮以切换数据时组件重新呈现的方式进行比较。 如果单击其中一个按钮,TextInformation 下的组件将重新渲染,但不会重新渲染根组件:

显示重新渲染将使您快速了解组件之间的关系,但它不会为您提供大量数据来分析特定组件。 为了更深入地了解,让我们看一下分析器工具。

分析器工具旨在帮助您精确测量每个组件渲染所需的时间。 这可以帮助您识别可能缓慢或过程密集的组件。

重新打开设置并取消选中组件渲染时突出显示更新的框。 然后单击控制台中的 Profiler 选项卡。

要使用分析器,请单击屏幕左侧的蓝色圆圈开始录制,并在完成后再次单击它:

当您停止录制时,您会发现组件更改的图表,包括每个项目的渲染时间。

为了更好地了解组件的相对效率,请粘贴到知识共享 维基百科页面。 这段文本足够长,可以给出有趣的结果,但又不会太大,以至于会导致应用程序崩溃。

粘贴文本后,启动分析器,然后对输入进行小幅更改。 在组件完成重新渲染后停止分析。 会有很长的停顿,因为应用程序正在处理长时间的重新渲染:

当您结束录制时,React Developer Tools 将创建一个火焰图,显示重新渲染的每个组件以及重新渲染每个组件所需的时间。

在这种情况下,“更改”一词的每次按键都会导致重新渲染。 更重要的是,它显示了每次渲染需要多长时间以及为什么会有很长的延迟。 组件 AppTextContext.ProviderTextInformation 需要大约 0.2 毫秒来重新渲染。 但是由于 itemize 函数中的复杂数据解析,CharacterMap 组件每次击键需要大约 1 秒的时间来重新渲染。

在显示中,每个黄色条都是一个新的击键。 您可以通过单击每个栏一次重播一个序列。 请注意,渲染时间略有不同,但 CharacterMap 一直很慢:

您可以通过在设置的 Profiler 部分下选择 记录每个组件在分析时呈现的原因。 选项来获取更多信息。

尝试切换 Word Count 组件并注意更改需要多长时间。 即使您没有更改文本内容,应用程序仍然滞后:

现在,当您将光标悬停在组件上时,您会发现它包含组件重新渲染的原因。 在这种情况下,组件改变的原因是渲染的父组件。 这是 CharacterMap 组件的问题。 CharacterMap 每次父项更改时都会进行昂贵的计算,即使道具和上下文没有更改。 也就是说,即使数据与之前的渲染相同,它也会重新计算数据。

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

React Developer Tools 帮助隔离了一个问题:只要任何父组件发生更改,CharacterMap 组件就会重新渲染并执行昂贵的计算。

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

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

打开CharacterMap.js

nano src/components/CharacterMap/CharacterMap.js

从 React 导入 memo,然后将整个函数传递给 memo 函数:

调试教程/src/components/CharacterMap/CharacterMap.js

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 开发者工具中,你会看到一个灰色的矩形显示重新渲染被阻止。

如果查看 Ranked 选项卡,您会发现 CharacterCountWordCount 都重新渲染,但原因不同。 由于 CharacterCount 没有被记忆,它重新渲染,因为父改变。 WordCount 因为道具改变而重新渲染。 即使它被包裹在 memo 中,它仍然会重新渲染。

注意: 记忆是有帮助的,但你应该只在你有明显的性能问题时才使用它,就像你在本例中所做的那样。 否则,它可能会产生性能问题:React 每次重新渲染时都必须检查 props,这可能会导致较小组件的延迟。


在此步骤中,您使用分析器来识别重新渲染和组件重新渲染。 您还使用火焰图和排名图来识别重新渲染缓慢的组件,然后使用 memo 函数来防止在道具或上下文没有更改时重新渲染。

结论

React Developer Tools 浏览器扩展为您提供了一组强大的实用程序来探索您的应用程序中的组件。 使用这些工具,您将能够探索组件的状态并使用真实数据识别错误,而无需控制台语句或调试器。 您还可以使用分析器来探索组件之间的交互方式,从而使您能够识别和优化在整个应用程序中呈现缓慢的组件。 这些工具是开发过程的关键部分,让您有机会将组件作为应用程序的一部分进行探索,而不仅仅是作为静态代码。

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