_ 作者选择了 Creative Commons以作为 Write for Donations计划的一部分接收捐款。
介绍
在[React] (https://reactjs.org/)应用中,性能问题可能来自网络懒惰,过度工作API,效率低下的第三方库,甚至结构完善的代码,可以工作到遇到异常大的负载. 查明业绩问题的根源可能很困难,但其中许多问题源于部分的重新移交。 要么组件重报次数超过预期,要么组件有数据繁多的操作,可以使每个渲染速度缓慢. 由于这个原因,学习如何防止不需要的重接,可以帮助优化你的回放应用程序的性能,为用户创造更好的体验.
在这个教程中,你会专注于优化React组件中的性能. 为了探究这个问题,你会构建一个组件来分析一组文本. 您将查看不同的动作如何触发重报,以及如何使用 [Hooks] (https://reactjs.org/docs/hooks-intro.html)和 [memoization] (https://reactjs.org/docs/hooks-faq.html# how-to-memoize-calculations)来将昂贵的数据计算最小化. 到这个教程结束时,你会熟悉许多增强Hooks的性能,例如"useMemo"和"useCallback". 虎克,还有需要他们的情况.
前提条件
- 联合国 您需要一个运行 [Node.js] (https://nodejs.org/en/about/) 的开发环境; 此教程在 Node.js 版本 10.22.0 和 npm 版本 6.14.6. 上进行了测试 。 要在 macOS 或 Ubuntu 18.04 上安装此功能,请遵循 [如何在 macOS (https://andsky.com/tech/tutorials/how-to-install-node-js-and-create-a-local-development-environment-on-macos 上安装节点并创建本地开发环境] 或 [如何在 Ubuntu 18.04 (https://andsky.com/tech/tutorials/how-to-install-node-js-on-ubuntu-18-04 上安装节点.js] 的 ** 部分使用 PPA 。
- 与Create React App相接而来的一种反应开发环境,非必需锅炉板被取出. 要设置此选项, 请遵循 [ 步骤1 — 创建一个空项目** , 如何管理响应类组件的状态教程] (https://andsky.com/tech/tutorials/how-to-manage-state-on-react-class-components# step-1-%E2%80%94-creating-an-empty-project) 。 该教程将使用 " 性能辅导 " 作为项目名称。
- 联合国 如果您在 React 中是新调试, 请检查教程 [如何使用 React 开发工具调试组件] (https://andsky.com/tech/tutorials/how-to-debug-react-components-using-react-developer-tools), 并熟悉您正在使用的浏览器中的开发工具, 例如 [Chrome DevTools] (https://developers.google.com/web/tools/chrome-devtools) 和 [Firefox 开发工具] (https://developer.mozilla.org/docs/Tools) 。
- 联合国 您还需要JavaScript, HTML, 和 CSS 的基本知识, 您可以在 [How To Build a website With HTML series] (https://www.digitalocean.com/community/tutorial_series/how-to-build-a-website-with-html), [How To Build a website With CSS series] (https://www.digitalocean.com/community/tutorial_series/how-to-build-a-website-with-css) 和 [How To Code in JavaScript] (https://www.digitalocean.com/community/tutorial_series/how-to-code-in-javascript) 中找到这些知识. .
步骤 1 – 用备忘录
防止重现
在这个步骤中,你会构建一个文本分析组件. 你会创建一个输入来取一个块的文本和一个组件来计算字母和符号的频率. 然后,你将创造出一种情景,即文本分析器的性能不佳,并找出性能问题的根源. 最后,您会使用 React `memo' 函数来防止当父母改变时将组件重接,但孩子组件的 [道 (https://andsky.com/tech/tutorials/how-to-customize-react-components-with-props) 不改变.
到本步骤结束时,您将有一个工作组件,您将使用在教程的剩余时间,并了解家长重现如何在儿童组件中产生性能问题。
建立一个文本分析器
首先,在App.js
中添加一个<textarea>
元素。
在您选择的文本编辑器中打开App.js
:
1nano src/components/App/App.js
然后添加一个<textarea>
输入,包含一个<label>
。 将标签放入一个<div>
中,并添加以下突出代码为className
的wrapper
:
1[label performance-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 <p>Add Your Text Here:</p>
10 <textarea
11 id="text"
12 name="text"
13 rows="10"
14 cols="100"
15 >
16 </textarea>
17 </label>
18 </div>
19 )
20}
21
22export default App;
这将为样本应用程序创建一个输入框. 保存和关闭文件。
打开App.css
,添加一些风格:
1nano src/components/App/App.css
在App.css
中,向.wrapper
类添加插件,然后向div
元素添加一个边缘
。
1[label performance-tutorial/src/components/App/App.css]
2
3.wrapper {
4 padding: 20px;
5}
6
7.wrapper div {
8 margin: 20px 0;
9}
这将增加输入和数据显示之间的分离. 保存和关闭文件。
接下来,为CharacterMap
组件创建一个目录,该组件将分析文本,计算每个字母和符号的频率,并显示结果。
首先,创建地址:
1mkdir src/components/CharacterMap
然后在文本编辑器中打开CharacterMap.js
:
1nano src/components/CharacterMap/CharacterMap.js
内部,创建一个名为CharacterMap
的组件,将文本
作为支架,并显示文本
在一个<div>
中:
1[label performance-tutorial/src/components/CharacterMap/CharacterMap.js]
2import React from 'react';
3import PropTypes from 'prop-types';
4
5export default function CharacterMap({ text }) {
6 return(
7 <div>
8 Character Map:
9 {text}
10 </div>
11 )
12}
13
14CharacterMap.propTypes = {
15 text: PropTypes.string.isRequired
16}
请注意,您正在添加一个 PropType
为文本
prop 引入一些弱的打字。
添加一个函数在文本上循环并提取字符信息. 命名函数itemize
并将text
作为一个论点。 itemize
函数将在每个字符上循环几次,随着文本大小增加,它将非常慢。
1[label performance-tutorial/src/components/CharacterMap/CharacterMap.js]
2import React from 'react';
3import PropTypes from 'prop-types';
4
5function itemize(text){
6 const letters = text.split('')
7 .filter(l => l !== ' ')
8 .reduce((collection, item) => {
9 const letter = item.toLowerCase();
10 return {
11 ...collection,
12 [letter]: (collection[letter] || 0) + 1
13 }
14 }, {})
15 return letters;
16}
17
18export default function CharacterMap({ text }) {
19 return(
20 <div>
21 Character Map:
22 {text}
23 </div>
24 )
25}
26
27CharacterMap.propTypes = {
28 text: PropTypes.string.isRequired
29}
在)去除空格,并使用[`.reduc'方法(https://andsky.com/tech/tutorials/how-to-use-array-methods-in-javascript-iteration-methods# reduce()去去除每个字母的平面。 在 " .减少 " 方法内,使用一个[标 (https://andsky.com/tech/tutorials/understanding-objects-in-javascript)作为初始值,然后通过将其转换为小写来使字符正常化,如果以前没有总计,则在上一个总计中加上 " 1 " 或 " 0 " 。 以新值更新对象,同时使用对象扩展运算符保留先前的值.
现在你已经创建了一个对象,每个字符的计数,你需要按最高字符来排序它。 将对象转换为对象的数组使用 Object.entries
。 数组中的第一个项目是字符,第二个项目是计数。 使用 .sort
方法将最常见的字符放在顶部:
1[label performance-tutorial/src/components/CharacterMap/CharacterMap.js]
2import React from 'react';
3import PropTypes from 'prop-types';
4
5function itemize(text){
6 const letters = text.split('')
7 .filter(l => l !== ' ')
8 .reduce((collection, item) => {
9 const letter = item.toLowerCase();
10 return {
11 ...collection,
12 [letter]: (collection[letter] || 0) + 1
13 }
14 }, {})
15 return Object.entries(letters)
16 .sort((a, b) => b[1] - a[1]);
17}
18
19export default function CharacterMap({ text }) {
20 return(
21 <div>
22 Character Map:
23 {text}
24 </div>
25 )
26}
27
28CharacterMap.propTypes = {
29 text: PropTypes.string.isRequired
30}
最后,用文本调用itemize
函数并显示结果:
1[label performance-tutorial/src/components/CharacterMap/CharacterMap.js]
2
3import React from 'react';
4import PropTypes from 'prop-types';
5
6function itemize(text){
7 const letters = text.split('')
8 .filter(l => l !== ' ')
9 .reduce((collection, item) => {
10 const letter = item.toLowerCase();
11 return {
12 ...collection,
13 [letter]: (collection[letter] || 0) + 1
14 }
15 }, {})
16 return Object.entries(letters)
17 .sort((a, b) => b[1] - a[1]);
18}
19
20export default function CharacterMap({ text }) {
21 return(
22 <div>
23 Character Map:
24 {itemize(text).map(character => (
25 <div key={character[0]}>
26 {character[0]}: {character[1]}
27 </div>
28 ))}
29 </div>
30 )
31}
32
33CharacterMap.propTypes = {
34 text: PropTypes.string.isRequired
35}
保存并关闭文件。
现在,导入组件并在App.js
内部渲染。
1nano src/components/App/App.js
导入 useState
然后调用函数并将值存储在名为 text
的变量和一个名为 setText
的更新函数上。
若要更新「文本」,請在「onChange」中添加一個函數,將「event.target.value」傳送到「setText」函數:
1[label performance-tutorial/src/components/App/App.js]
2import React, { useState } from 'react';
3import './App.css';
4
5function App() {
6 const [text, setText] = useState('');
7
8 return(
9 <div className="wrapper">
10 <label htmlFor="text">
11 <p>Your Text</p>
12 <textarea
13 id="text"
14 name="text"
15 rows="10"
16 cols="100"
17 onChange={event => setText(event.target.value)}
18 >
19 </textarea>
20 </label>
21 </div>
22 )
23}
24
25export default App;
请注意,您正在用一个空串初始化useState
,这将确保您传输到CharacterMap
组件的值始终是字符串,即使用户尚未输入文本。
导入 CharacterMap
并在 <label>
元素之后返回它. 将 text
状态传送到 text
prop:
1[label performance-tutorial/src/components/CharacterMap/CharacterMap.js]
2import React, { useState } from 'react';
3import './App.css';
4
5import CharacterMap from '../CharacterMap/CharacterMap';
6
7function App() {
8 const [text, setText] = useState('');
9
10 return(
11 <div className="wrapper">
12 <label htmlFor="text">
13 <p>Your Text</p>
14 <textarea
15 id="text"
16 name="text"
17 rows="10"
18 cols="100"
19 onChange={event => setText(event.target.value)}
20 >
21 </textarea>
22 </label>
23 <CharacterMap text={text} />
24 </div>
25 )
26}
27
28export default App;
当您这样做时,浏览器将更新,当您添加文本时,您将在输入后找到字符分析:
正如示例中所示,该组件在少量的文本中表现相当好. 每次按键时,React 都会更新CharacterMap
以新的数据。
性能测试
您可以添加大量的文本或设置您的浏览器以使用更少的内存。 要将组件推到性能瓶颈,请复制 Wikipedia entry for GNU并将其粘贴到文本框中。 根据维基百科页面编辑的方式,您的样本可能略有不同。
在插入文本框后,尝试输入额外的字母e
,并注意显示需要多长时间。
如果组件不够慢,并且您正在使用 Firefox, Edge,或其他任何浏览器,添加更多文本,直到您注意到放缓。
如果你正在使用 Chrome,你可以 throttle CPU 内部的性能卡. 这是一个很好的方式来模拟智能手机或较旧的硬件。 有关更多信息,请参阅 Chrome DevTools 文档。
如果组件在维基百科文献中太慢,请删除一些文本,您希望收到明显的延迟,但您不希望使它变得不太慢或破坏您的浏览器。
防止儿童组件的重新渲染
逐项 ' 函数是上一节所查明的延迟的根源。 该功能通过在内容上循环多次,对每个条目做了大量的工作. 在函数本身中可以直接实现一些优化,但此教程的焦点是当文本不改变时如何处理组件重递. 换句话说,您将把
逐项`功能视为无法改变的功能。 目标只是在必要时运行。 这将显示如何处理无法控制的API或第三方库的性能.
首先,你将探索一个父母改变的情况,但孩子的组成部分不会改变。
在App.js
中,添加一个解释组件如何工作的段落和一个按钮来切换信息:
1[label performance-tutorial/src/components/App/App.js]
2import React, { useReducer, useState } from 'react';
3import './App.css';
4
5import CharacterMap from '../CharacterMap/CharacterMap';
6
7function App() {
8 const [text, setText] = useState('');
9 const [showExplanation, toggleExplanation] = useReducer(state => !state, false)
10
11 return(
12 <div className="wrapper">
13 <label htmlFor="text">
14 <p>Your Text</p>
15 <textarea
16 id="text"
17 name="text"
18 rows="10"
19 cols="100"
20 onChange={event => setText(event.target.value)}
21 >
22 </textarea>
23 </label>
24 <div>
25 <button onClick={toggleExplanation}>Show Explanation</button>
26 </div>
27 {showExplanation &&
28 <p>
29 This displays a list of the most common characters.
30 </p>
31 }
32 <CharacterMap text={text} />
33 </div>
34 )
35}
36
37export default App;
使用减速器函数拨打useReducer
链接以逆转当前状态. 将输出保存为显示解释
和toggle解释
。 在<标签>
之后,添加一个按钮来切换解释和一个段落,当显示解释
是真实的时,将显示结果。
保存和退出文件. 当浏览器更新时,点击按钮来转移解释. 注意是否有延迟。
你的用户不应该遇到一个延迟,当他们正在转移一个小量的 JSX 时,延迟发生,因为当母组件更改时,在这种情况下,App.js
组件正在重新渲染和重新计算字符数据。
如果您使用浏览器的开发工具对应用程序进行配置,您会发现该组件会重新渲染,因为其主体变更。 有关使用开发工具的配置文件的审查,请参阅 如何使用 React Developer Tools 调试对应组件。
由于CharacterMap
包含一个昂贵的函数,它只应该在修订程序发生更改时重新渲染。
打开CharacterMap.js
:
1nano src/components/CharacterMap/CharacterMap.js
接下来,导入备忘录
,然后将组件传输到备忘录
,并将结果导出为默认:
1[label performance-tutorial/src/components/CharacterMap/CharacterMap.js]
2import React, { memo } from 'react';
3import PropTypes from 'prop-types';
4
5function itemize(text){
6 ...
7}
8
9function CharacterMap({ text }) {
10 return(
11 <div>
12 Character Map:
13 {itemize(text).map(character => (
14 <div key={character[0]}>
15 {character[0]}: {character[1]}
16 </div>
17 ))}
18 </div>
19 )
20}
21
22CharacterMap.propTypes = {
23 text: PropTypes.string.isRequired
24}
25
26export default memo(CharacterMap);
当你这样做时,浏览器将重新加载,在你点击按钮之前你得到结果后不再有延迟:
如果你看看开发人员的工具,你会发现该组件不再重现:
备忘录
函数将对附件进行较浅的比较,并且只有在附件更改时才会重新进行。
重要的是要记住,比较不是免费的。检查补贴有性能成本,但当你有一个清晰的性能影响,如昂贵的计算,它是值得防止再渲染的。
在此步骤中,您创建了一个应用程序,进行长而缓慢的计算。您了解了家长重现将如何导致儿童组件重现,以及如何使用备忘录
防止重现。
步骤 2 – 使用useMemo
缓存昂贵的数据计算
在此步骤中,您将使用useMemo
夹存储慢数据计算的结果,然后将useMemo
夹嵌入到现有组件中,并为数据重新计算设置条件。
在之前的步骤中,该组件的转移解释是母组件的一部分,但是,您可以将其添加到CharacterMap
组件本身。当您这样做时,CharacterMap
将具有两个属性,即文本
和显示解释
,并且当显示解释
是真实时,它将显示解释。
要开始,打开CharacterMap.js
:
1nano src/components/CharacterMap/CharacterMap.js
在 CharacterMap
中,添加一个新的 showExplanation
属性. 当 showExplanation
的值为 true 时,显示说明文本:
1[label performance-tutorial/src/components/CharacterMap/CharacterMap.js]
2import React, { memo } from 'react';
3import PropTypes from 'prop-types';
4
5function itemize(text){
6 ...
7}
8
9function CharacterMap({ showExplanation, text }) {
10 return(
11 <div>
12 {showExplanation &&
13 <p>
14 This display a list of the most common characters.
15 </p>
16 }
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.propTypes = {
28 showExplanation: PropTypes.bool.isRequired,
29 text: PropTypes.string.isRequired
30}
31
32export default memo(CharacterMap);
保存并关闭文件。
接下来,打开App.js
:
1nano src/components/App/App.js
删除解释的段落,并将显示解释
作为标语符号转移到CharakterMap
:
1[label performance-tutorial/src/components/App/App.js]
2import React, { useReducer, useState } from 'react';
3import './App.css';
4
5import CharacterMap from '../CharacterMap/CharacterMap';
6
7function App() {
8 const [text, setText] = useState('');
9 const [showExplanation, toggleExplanation] = useReducer(state => !state, false)
10
11 return(
12 <div className="wrapper">
13 <label htmlFor="text">
14 <p>Your Text</p>
15 <textarea
16 id="text"
17 name="text"
18 rows="10"
19 cols="100"
20 onChange={event => setText(event.target.value)}
21 >
22 </textarea>
23 </label>
24 <div>
25 <button onClick={toggleExplanation}>Show Explanation</button>
26 </div>
27 <CharacterMap showExplanation={showExplanation} text={text} />
28 </div>
29 )
30}
31
32export default App;
保存并关闭文件. 当您这样做时,浏览器将更新. 如果您切换解释,您将再次收到延迟。
如果你看看配置文件,你会发现该组件重新渲染,因为显示解释
字符串改变了:
备忘录
函数将比较附件并防止再渲染,如果没有附件更改,但在这种情况下,显示解释
附件不会更改,因此整个组件将重新渲染,组件将重新运行itemize
函数。
在这种情况下,您需要记忆某些部分的组件,而不是整个组件。 React 提供了一个名为useMemo
的特殊,您可以使用它来保存部分的组件在重现器中。 需要两个参数。 第一个参数是一个函数,将返回您想记忆的值。 第二个参数是依赖的组合。 如果依赖改变,则useMemo
将重新运行该函数并返回一个值。
要实现useMemo
,先打开CharacterMap.js
:
1nano src/components/CharacterMap/CharacterMap.js
声明一个名为)的值作为第一个参数和包含
text`的数组作为第二个参数。
在 JSX 中,用字符
取代对itemize
的呼叫:
1[label performance-tutorial/src/components/CharacterMap/CharacterMap.js]
2import React, { memo, useMemo } from 'react';
3import PropTypes from 'prop-types';
4
5function itemize(text){
6 ...
7}
8
9function CharacterMap({ showExplanation, text }) {
10 const characters = useMemo(() => itemize(text), [text]);
11 return(
12 <div>
13 {showExplanation &&
14 <p>
15 This display a list of the most common characters.
16 </p>
17 }
18 Character Map:
19 {characters.map(character => (
20 <div key={character[0]}>
21 {character[0]}: {character[1]}
22 </div>
23 ))}
24 </div>
25 )
26}
27
28CharacterMap.propTypes = {
29 showExplanation: PropTypes.bool.isRequired,
30 text: PropTypes.string.isRequired
31}
32
33export default memo(CharacterMap);
当你这样做时,浏览器将重新加载,当你切换解释时不会有延迟。
如果您配置组件,您仍然会发现它重新渲染,但渲染所需的时间会更短。在本示例中,它花费了.7 毫秒,而没有使用Memo Hook 就花费了 916.4 毫秒。 这是因为 React 正在重新渲染组件,但它没有重新运行使用Memo Hook 中包含的函数。
如果您在文本框中更改文本,仍会出现延迟,因为依赖性文本
发生了变化,因此useMemo
将重新运行该函数。
在此步骤中,您将组件的部分备忘录,您将昂贵的函数与其它组件隔离,并使用useMemo
链接只在某些依赖性发生变化时运行该函数。
步骤 3 – 使用useCallback
管理函数平等性检查
在此步骤中,您将处理在JavaScript中难以比较的代理程序。React使用严格的平等检查,当代理程序发生更改时。这个检查决定了何时重新运行Hooks以及何时重新渲染组件。
您可以使用 useCallback
Hook 来保留一个函数在重新渲染器上。这将防止当一个母组件重现一个函数时出现不必要的重新渲染。
当你构建你的CharacterMap
组件时,你可能会遇到一个情况,你需要它更灵活。在itemize
函数中,你总是将字符转换为较低的案例,但某些组件的消费者可能不希望这种功能。
为了方便这一点,添加一个名为变压器
的新支撑,这将改变字符. 变压器
函数将是任何将一个字符作为论点并返回某种类型的字符串的函数。
在CharacterMap
中,将transformer
添加为 prop. 给它一个PropType
函数,默认值为null
:
1[label performance-tutorial/src/components/CharacterMap/CharacterMap.js]
2import React, { memo, useMemo } from 'react';
3import PropTypes from 'prop-types';
4
5function itemize(text){
6 const letters = text.split('')
7 .filter(l => l !== ' ')
8 .reduce((collection, item) => {
9 const letter = item.toLowerCase();
10 return {
11 ...collection,
12 [letter]: (collection[letter] || 0) + 1
13 }
14 }, {})
15 return Object.entries(letters)
16 .sort((a, b) => b[1] - a[1]);
17}
18
19function CharacterMap({ showExplanation, text, transformer }) {
20 const characters = useMemo(() => itemize(text), [text]);
21 return(
22 <div>
23 {showExplanation &&
24 <p>
25 This display a list of the most common characters.
26 </p>
27 }
28 Character Map:
29 {characters.map(character => (
30 <div key={character[0]}>
31 {character[0]}: {character[1]}
32 </div>
33 ))}
34 </div>
35 )
36}
37
38CharacterMap.propTypes = {
39 showExplanation: PropTypes.bool.isRequired,
40 text: PropTypes.string.isRequired,
41 transformer: PropTypes.func
42}
43
44CharacterMap.defaultProps = {
45 transformer: null
46}
47
48export default memo(CharacterMap);
接下来,更新「itemize」以将「transformer」作为参数。 将「.toLowerCase」方法替换为「transformer」。 如果「transformer」是真,请将具有「element」的函数称为参数。
1[label performance-tutorial/src/components/CharacterMap/CharacterMap.js]
2import React, { memo, useMemo } from 'react';
3import PropTypes from 'prop-types';
4
5function itemize(text, transformer){
6 const letters = text.split('')
7 .filter(l => l !== ' ')
8 .reduce((collection, item) => {
9 const letter = transformer ? transformer(item) : item;
10 return {
11 ...collection,
12 [letter]: (collection[letter] || 0) + 1
13 }
14 }, {})
15 return Object.entries(letters)
16 .sort((a, b) => b[1] - a[1]);
17}
18
19function CharacterMap({ showExplanation, text, transformer }) {
20 ...
21}
22
23CharacterMap.propTypes = {
24 showExplanation: PropTypes.bool.isRequired,
25 text: PropTypes.string.isRequired,
26 transformer: PropTypes.func
27}
28
29CharacterMap.defaultProps = {
30 transformer: null
31}
32
33export default memo(CharacterMap);
最后,更新useMemo
链接。添加transformer
作为依赖,并将其传输到itemize
函数中。你想确保你的依赖是完整的。这意味着你需要添加任何可能变更为依赖的东西。如果用户通过在不同的选项之间切换transformer
,你需要重新运行该函数以获得正确的值。
1[label performance-tutorial/src/components/CharacterMap/CharacterMap.js]
2import React, { memo, useMemo } from 'react';
3import PropTypes from 'prop-types';
4
5function itemize(text, transformer){
6 ...
7}
8
9function CharacterMap({ showExplanation, text, transformer }) {
10 const characters = useMemo(() => itemize(text, transformer), [text, transformer]);
11 return(
12 <div>
13 {showExplanation &&
14 <p>
15 This display a list of the most common characters.
16 </p>
17 }
18 Character Map:
19 {characters.map(character => (
20 <div key={character[0]}>
21 {character[0]}: {character[1]}
22 </div>
23 ))}
24 </div>
25 )
26}
27
28CharacterMap.propTypes = {
29 showExplanation: PropTypes.bool.isRequired,
30 text: PropTypes.string.isRequired,
31 transformer: PropTypes.func
32}
33
34CharacterMap.defaultProps = {
35 transformer: null
36}
37
38export default memo(CharacterMap);
保存并关闭文件。
在这个应用程序中,你不想让用户能够在不同的函数之间切换,但你确实希望字符是较低的案例。在App.js
中定义一个转换器
,将字符转换为较低的案例。
打开App.js
:
1nano src/components/App/App.js
然后定义一个名为transformer
的函数,该函数将一个字符转换为较低的例子。
1[label performance-tutorial/src/components/CharacterMap/CharacterMap.js]
2import React, { useReducer, useState } from 'react';
3import './App.css';
4
5import CharacterMap from '../CharacterMap/CharacterMap';
6
7function App() {
8 const [text, setText] = useState('');
9 const [showExplanation, toggleExplanation] = useReducer(state => !state, false)
10 const transformer = item => item.toLowerCase();
11
12 return(
13 <div className="wrapper">
14 <label htmlFor="text">
15 <p>Your Text</p>
16 <textarea
17 id="text"
18 name="text"
19 rows="10"
20 cols="100"
21 onChange={event => setText(event.target.value)}
22 >
23 </textarea>
24 </label>
25 <div>
26 <button onClick={toggleExplanation}>Show Explanation</button>
27 </div>
28 <CharacterMap showExplanation={showExplanation} text={text} transformer={transformer} />
29 </div>
30 )
31}
32
33export default App;
保存文件. 当您这样做时,当您切换解释时,您会发现延迟已经返回。
如果您对组件进行配置,则会发现组件会重新渲染,因为附件的变更和胡克的变更:
如果你仔细观察,你会发现显示解释
插件改变了,这是有意义的,因为你点击了按钮,但变压器
插件也改变了。
当您点击按钮在应用程序
中进行状态更改时,应用程序
组件重新渲染并重新声明转换器
。尽管该函数是相同的,但它不是参考性相同的,这意味着它与之前的函数不完全相同。
如果你打开浏览器控制台并比较相同的函数,你会发现比较是假的,如下列代码块所示:
1const a = () = {};
2const b = () = {};
3
4a === a
5// This will evaluate to true
6
7a === b
8// This will evaluate to false
使用===
比较运算符,此代码显示两个函数不被认为是平等的,即使它们具有相同的值。
为了避免这种问题,React 提供一个名为useCallback
的链接。链接类似于useMemo
:它将一个函数作为第一个参数和一系列依赖作为第二个参数。区别在于useCallback
返回函数而不是函数的结果。
在App.js
中,导入useCallback
,将匿名函数作为第一个参数和空数列作为第二个参数。
1[label performance-tutorial/src/components/App/App.js]
2import React, { useCallback, useReducer, useState } from 'react';
3import './App.css';
4
5import CharacterMap from '../CharacterMap/CharacterMap';
6
7function App() {
8 const [text, setText] = useState('');
9 const [showExplanation, toggleExplanation] = useReducer(state => !state, false)
10 const transformer = useCallback(item => item.toLowerCase(), []);
11
12 return(
13 <div className="wrapper">
14 <label htmlFor="text">
15 <p>Your Text</p>
16 <textarea
17 id="text"
18 name="text"
19 rows="10"
20 cols="100"
21 onChange={event => setText(event.target.value)}
22 >
23 </textarea>
24 </label>
25 <div>
26 <button onClick={toggleExplanation}>Show Explanation</button>
27 </div>
28 <CharacterMap showExplanation={showExplanation} text={text} transformer={transformer} />
29 </div>
30 )
31}
32
33export default App;
保存和关闭文件. 当您这样做时,您将能够转换解释,而无需重新运行该函数。
如果你配置组件,你会发现不再运行:
在这个特定的组件中,你实际上不需要useCallback
链接,你可以声明该函数在组件外部,它永远不会重新渲染,你只应该声明组件内部的函数,如果它们需要某种类型的支持或状态数据,但有时你需要基于内部状态或支持来创建函数,在这些情况下,你可以使用使用Callback
链接来最大限度地减少重新渲染。
在此步骤中,您使用useCallback
链接保存了跨重返器的函数,您还了解了这些函数在链接中相比时如何保持平等性。
结论
现在你有工具来提高昂贵的组件的性能。你可以使用memo
,useMemo
和useCallback
,以避免昂贵的组件重现。但所有这些策略都包括自己的性能成本。
最后,并非所有性能问题都需要技术修复,有时性能成本是不可避免的,例如缓慢的API或大数据转换,在这些情况下,您可以使用设计来解决问题,无论是渲染加载组件,在运行非同步函数时显示位置,还是其他改进用户体验。
如果您想阅读更多 React 教程,请查看我们的 React 主题页面,或返回 如何编码在 React.js 系列页面。