如何减少 React 组件不必要的重新渲染
如果我们想要构建高效和高性能的应用程序就必须理解React是如何渲染组件。因为当组件的状态或属性发生变化时,React会自动更新用户界面(UI)来反映它们的变化。所以,React会再次调用组件的渲染方法来生成更新后的UI。
在本文中,我们将探讨三个React Hooks以及它们如何减少 React 组件不必要的重新渲染
- useMemo
- useCallback
- useRef
上面这三个Hooks能够帮我们减少不必要的重新渲染。并达到优化我们的代码,提高性能,高效地存储值的效果。
通过本文的学习,我们将更好地了解如何使用这些便捷的React Hooks使我们的React应用程序更快速、更具响应性。
在React中使用React的useMem
在React中,useMemo
可以减少不必要的重新渲染并优化性能。
接下来让我们来探讨一下useMemo Hooks如何在我们的React组件中减少不必要的重新渲染。
我们的useMemo是通过记住函数的结果并跟踪其依赖关系来确保只在必要时重新计算该过程。
下面是代码示例:
import { useMemo, useState } from 'react';
// 定义Page函数
function Page() {
// 定义count状态,初始值为0
const [count, setCount] = useState(0);
// 定义items状态,初始值为generateItems函数的返回值
const [items] = useState(generateItems(300));
// 使用useMemo函数,根据count和items的值,返回一个包含id和isSelected属性的对象
const selectedItem = useMemo(() => items.find((item) => item.id === count), [
count,
items,
]);
// 使用useMemo函数,根据count和items的值,返回一个包含id和isSelected属性的对象,并且每次调用generateItems函数,将返回值添加到items中
function generateItems(count) {
// 定义一个空数组用于存放项目
const items = [];
// 循环生成count项目
for (let i = 0; i < count; i++) {
// 将每一项添加到items数组中
items.push({
id: i,
isSelected: i === count - 1,
});
}
// 返回items数组
return items;
}
return (
<div className="tutorial">
<h1>Count: {count}</h1>
<h1>Selected Item: {selectedItem?.id}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
} );
}
// 导出Page函数
export default Page;k={() => setCount(count + 1)}>Increment</button>
</div>
);
}
// 导出Page组件
export default Page;
上面是一个名为"Page"的React组件,我们使用了useMemo
"来优化selectedItem
的计算。
下面是代码解释:
-
page组件使用
useState Hooks
来维护我们的count
的状态变量。 -
然后我们使用了
useState Hooks
初始化了items
状态,其值是generateItems
函数的结果。 -
然后我们使用
useMemo
来计算了selectedItem
,它存储了items.find
操作的结果。只有在count
或items
发生变化时,才会重新计算。 -
generateItems
函数会根据给定的count
生成了一个项目数组。 -
该组件渲染了当前的count值、
selectedItem id
,以及一个用于增加count
的按钮。
使用useMemo
通过记忆items.find
操作的结果来优化性能。它确保只有在依赖项(count
或items
)发生更改时,才会执行selectedItem
的计算,从而避免在后续渲染中进行不必要的重新计算。
React中的useCallback
在React中,useCallback Hooks
允许对函数进行缓存,防止在每次组件渲染时重新创建函数。通过使用useCallback
,一个函数只会被创建一次,并且在后续渲染中被重用,只要其依赖项保持不变。
考虑以下示例:
import React, { useState, useCallback, memo } from 'react';
// 定义所有颜色
const allColors = ['red', 'green', 'blue', 'yellow', 'orange'];
// 定义shuffle函数
const shuffle = (array) => {
// 将数组转换为一个新的数组
const shuffledArray = [...array];
// 遍历数组,从数组最后一个元素开始,随机抽取一个元素,放入新的数组中
for (let i = shuffledArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];
}
// 返回新的数组
return shuffledArray;
};
// 定义Filter组件
const Filter = memo(({ onChange }) => {
console.log('Filter rendered!');
return (
<input
type='text'
placeholder='Filter colors...'
onChange={(e) => onChange(e.target.value)}
/>
);
});
// 定义Page组件
function Page() {
// 初始化颜色数组
const [colors, setColors] = useState(allColors);
console.log(colors[0])
// 定义handleFilter函数,用于更改颜色数组
const handleFilter = useCallback((text) => {
// 过滤掉字符串中的空格
const filteredColors = allColors.filter((color) =>
color.includes(text.toLowerCase())
);
// 设置颜色数组
setColors(filteredColors);
}, [colors]);
return (
<div className='tutorial'>
<div className='align-center mb-2 flex'>
<button onClick={() => setColors(shuffle(allColors))}>
Shuffle
</button>
<Filter onChange={handleFilter} />
</div>
<ul>
{colors.map((color) => (
<li key={color}>{color}</li>
))}
</ul>
</div>
);
}
// 导出Page组件
export default Page;
上面的代码演示了在React组件中实现简单的颜色过滤和洗牌功能。
下面是代码解释:
-
初始颜色数组被定义为allColors。
-
洗牌函数(shuffle)接受一个数组并随机洗牌。我们使用Fisher-Yates算法来实现洗牌。
-
Filter组件是一个记忆化的函数组件,渲染一个输入元素。它接收一个onChange属性,并在输入值改变时触发回调函数。
-
Page组件是主要的组件,渲染了颜色过滤和洗牌功能。
-
颜色状态变量使用useState Hooks初始化,初始值为allColors。它表示经过,过滤的颜色列表。
-
handleFilter函数使用了useCallback Hooks来创建。它接受一个文本参数,并根据提供的文本对allColors数组进行过滤。过滤后的颜色使用useState Hooks的setColors函数进行设置。依赖数组[colors]确保只有在颜色状态变化时才重新创建handleFilter函数,通过防止不必要的重新渲染来优化性能。
-
在Page组件内部有一个用于洗牌颜色的按钮。当按钮被点击时,它使用打乱后的allColors数组调用setColors函数。
-
Filter组件被渲染,其onChange属性设置为handleFilter函数。
-
最后,颜色数组被映射为渲染颜色项列表,以
<li>
元素显示。
useCallback Hooks用于handleFilter函数,这意味着函数只会在第一次创建,如果依赖项(在这种情况下是colors状态)保持不变,则在后续渲染中被重用。
这种优化可以减少子组件不必要的重新渲染,这些子组件将handleFilter函数作为属性接收,比如Filter组件。如果颜色状态未发生变化,它确保Filter组件不会重新渲染,从而提高了性能。
React的useRef
在React应用程序中增强性能并减少不必要的重新渲染的另一种方法是使用useRef Hooks。通过使用useRef,我们可以存储一个可变的值,该值在渲染之时保持不变,从而有效地防止不必要的重新渲染。
这种技术允许我们在值发生变化时维护对值的引用,而不会触发组件更新。通过利用引用的可变性,我们可以在特定情况下优化性能。
下面是代码示例:
import React, { useRef, useState } from 'react';
// 定义App组件
function App() {
// 定义name状态,初始值为空字符串
const [name, setName] = useState('');
// 定义inputRef组件,用于获取焦点
const inputRef = useRef(null);
// 定义handleClick函数,用于获取焦点
function handleClick() {
inputRef.current.focus();
}
// 返回一个div元素,包含input和button
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
ref={inputRef}
/>
<button onClick={handleClick}>Focus</button>
</div>
);
}
上面的示例包含一个简单的输入和一个按钮。
useRef Hooks创建了一个名为inputRef的引用。一旦按钮被点击,将调用handleClick函数,该函数通过访问inputRef引用对象的current属性,使输入元素获取焦点。因此,它防止了在输入值改变时对组件进行不必要的重新渲染。
为了确保最佳使用useRef,应仅将其用于不影响组件渲染的可变值。如果可变值影响组件的渲染,应将其存储在组件的状态中。