React Hooks 性能优化与自定义 Hook 全解析
1. React Memo 介绍
React Memo 是一个高阶组件(HOC),它可以包裹其他组件,对组件进行记忆化处理,仅在组件的 props 发生变化时才重新渲染组件。需要注意的是,并非所有情况都需要使用记忆化,使用 React Memo 的目的是优化性能,因此只有当它能够提升应用性能时才使用。可以使用 Profiler 来检查应用的性能,在使用 React Memo 前后都运行性能分析,以确定是否值得进行记忆化。
2. useMemo Hook
- 原理 :React Memo 可以记忆组件,防止在组件 props 未改变时重新渲染;而 useMemo() Hook 可以在函数的依赖项未改变时,记忆函数的输出。useMemo() 函数会调用一个函数并记忆其输出,下次调用该函数时,它会检查依赖项是否改变。如果依赖项未改变,它将返回之前记忆的值;否则,它将再次调用该函数。
-
示例
:
-
首先,在
Combinations组件中添加shelfName属性,更新Combinations.js代码如下:
-
首先,在
import React from "react";
const Combinations = React.memo(function ({countBooks, shelfName }) {
console.log("Combinations component is re-rendered");
let arrangements = 1;
for (let i = 2; i <= countBooks; i++) {
arrangements *= i;
}
return `The total number of ways you can arrange the books on the shelf ${shelfName} is : ${arrangements}`;
});
export default Combinations;
- 然后,在 `App.js` 中更新 `Combinations` 标签以设置两个 props:
<Combinations countBooks={bookCount} shelfName={shelfName} />
- 当输入书架名称和书籍数量,然后更新书架名称时,会发现 `Combinations` 组件会重新渲染 8 次,这是因为 `shelfName` 属性发生了变化,尽管 `countBooks` 没有改变,但组件仍然重新渲染,导致阶乘计算多次重复。
- 为了避免这种情况,可以使用 `useMemo()` Hook。更新 `Combinations.js` 代码如下:
import React, { useMemo } from "react";
const Combinations = React.memo(function ({countBooks, shelfName }) {
console.log("Combinations component is re-rendered");
let arrangements = useMemo(() => {
console.log("Total number of ways being calculated");
let arrs = 1;
for (let i = 2; i <= countBooks; i++) {
arrs *= i;
}
return arrs;
}, [countBooks]);
return `The total number of ways you can arrange books on the shelf, ${shelfName} is : ${arrangements}`;
});
export default Combinations;
- 此时,重新测试,会发现计算只发生了两次,即输入第一个数字 2 和输入 0 使数字变为 20 时。虽然 `Combinations` 组件仍然会因为 `shelfName` 的变化而重新渲染 8 次,但阶乘计算不会再发生,因为 `countBooks` 没有改变,`useMemo()` 会返回记忆的输出。
3. useCallback Hook
-
原理
:
useCallback是 React 中的另一种记忆化技术,它的语法与useMemo相同,但useCallback记忆的是函数,而useMemo记忆的是值。 -
示例
:
-
假设要根据书籍数量检查书架空间,并且不再需要显示书架名称。更新
Combinations.js代码如下:
-
假设要根据书籍数量检查书架空间,并且不再需要显示书架名称。更新
import React, { useMemo } from "react";
const Combinations = React.memo(function ({ countBooks, checkSpace }) {
console.log("Combinations component is re-rendered");
let space = "";
switch (countBooks) {
case 1 - 5: space = "Free Space available"; break;
case 5 - 10: space = "Perfect"; break;
case 10 - 15: space = "Need extra storage"; break;
default: space = "Not Sufficient";
}
let arrangements = useMemo(() => {
console.log("Total number of ways being calculated");
let arrs = 1;
for (let i = 2; i <= countBooks; i++) {
arrs *= i;
}
return arrs;
}, [countBooks]);
return (
<>
<p>The total number of ways you can arrange books is : {arrangements}</p>
<button onClick={() => checkSpace(space)}>Check Space</button>
</>
);
});
export default Combinations;
- 在 `App.js` 中更新代码:
import React, { useState } from "react";
const [shelfSpace, setShelfSpace] = useState("");
const handleClick = (theSpace) => {
setShelfSpace(theSpace);
};
<Combinations countBooks={bookCount} checkSpace={handleClick} />
{shelfSpace && (
<p style={fieldStyle}>The space at the shelf is - {shelfSpace}</p>
)}
- 当输入书架名称、书籍数量并更新书架名称时,会发现 `Combinations` 组件会重新渲染 8 次,即使 props 没有改变。这是因为在 JavaScript 中,每次渲染时函数对象都是不同的,即使函数的定义相同。
- 为了解决这个问题,可以使用 `useCallback()` Hook。更新 `App.js` 中的 `handleClick` 函数如下:
import React, { useState, useCallback } from "react";
const handleClick = useCallback((theSpace) => {
setShelfSpace(theSpace);
}, []);
- 此时,更新名称后组件将不会重新渲染,`useCallback` 记忆了 `handleClick` 函数,使得 `React.memo` 认为 `checkSpace` prop 没有改变,从而避免了 `Combinations` 组件的重新渲染。
4. 更多内置 Hooks
| Hook 名称 | 作用 | 使用场景 |
|---|---|---|
| useImperativeHandle |
与
useRef
钩子一起使用,可在父组件发生变化时聚焦子组件内的元素
|
例如,在
App
组件中有一个名为
Name
的输入字段,创建一个 ref 并将其转发到子组件
Child1
中的
Country
输入字段,当
Name
字段发生变化时,聚焦
Country
字段
|
| useLayoutEffect |
与
useEffect
类似,但在 DOM 渲染时并行运行,在浏览器显示 DOM 更改之前调用
|
仅在
useEffect
导致视觉阻塞时使用,这种情况非常罕见
|
| useDebugValue | 在创建自定义 Hooks 时很有用,可在 React DevTools 中显示日志 |
可替代
console.log
语句
|
5. 自定义 Hooks
-
定义
:自定义 Hook 是一个以
use开头的 JavaScript 函数,并且可以调用其他 Hooks。通过实现自定义 Hooks,可以提取特定逻辑并在应用的不同部分重用。 -
示例
:
-
创建一个自定义 Hook
useFactorial.js来计算阶乘:
-
创建一个自定义 Hook
import { useMemo } from "react";
const useFactorial = (number) => {
let factorial = useMemo(() => {
let fact = 1;
for (let i = 2; i <= number; i++) {
fact *= i;
}
return fact;
}, [number]);
return factorial;
};
export default useFactorial;
- 在 `Combinations` 组件中使用 `useFactorial` 自定义 Hook,更新 `Combinations.js` 代码如下:
import React from "react";
import useFactorial from "./Hooks/useFactorial";
const Combinations = React.memo(function ({ countBooks, checkSpace }) {
console.log("Combinations component is re-rendered");
let space = "";
switch (countBooks) {
case 1 - 5: space = "Free Space available"; break;
case 5 - 10: space = "Perfect"; break;
case 10 - 15: space = "Need extra storage"; break;
default: space = "Not Sufficient";
}
const arrangements = useFactorial(countBooks);
return (
<>
<p>The total number of ways you can arrange books is : {arrangements}</p>
<button onClick={() => checkSpace(space)}>Check Space</button>
</>
);
});
export default Combinations;
- 这样,就可以使用一行代码计算书籍排列的方式,并且无需担心记忆化问题,因为自定义 Hook 已经处理了这个问题。
综上所述,React.memo 用于记忆组件,
useMemo
Hook 用于记忆值,
useCallback
用于记忆函数。在开发应用时,不建议一开始就使用这些性能优化技术,而是在应用开发完成或完成大部分开发后,使用 Profiler 检查性能漏洞,然后确定原因并使用这些技术进行修复。同时,还可以创建自定义 Hooks 来提取和重用逻辑,但需要注意 Hooks 的使用规则。
React Hooks 性能优化与自定义 Hook 全解析
6. 记忆化技术总结
记忆化技术在 React 应用性能优化中起着关键作用,下面对 React.memo、useMemo 和 useCallback 进行总结对比:
| 记忆化技术 | 作用对象 | 记忆内容 | 使用场景 |
| ---- | ---- | ---- | ---- |
| React.memo | 组件 | 组件整体 | 当组件的 props 不变时,避免组件重新渲染 |
| useMemo | 函数输出 | 值 | 对于昂贵的计算,避免在依赖项未改变时重复计算 |
| useCallback | 函数 | 函数 | 当函数作为 prop 传递,避免因函数对象每次渲染不同而导致组件重新渲染 |
通过合理使用这些记忆化技术,可以显著提升 React 应用的性能。例如,在处理大量数据或复杂计算时,使用 useMemo 可以避免不必要的重复计算;在传递回调函数时,使用 useCallback 可以避免因函数对象的变化而触发组件的重新渲染。
7. 自定义 Hooks 的优势与局限
-
优势
-
逻辑复用
:自定义 Hooks 可以将特定的逻辑封装起来,在应用的不同部分进行复用。例如,上述的
useFactorial自定义 Hook 可以在多个组件中使用,避免了代码的重复编写。 - 代码组织 :将相关的逻辑集中在一个自定义 Hook 中,使组件代码更加简洁,提高了代码的可读性和可维护性。
- 社区共享 :可以将自己开发的自定义 Hooks 分享给社区,供其他开发者使用,促进代码的共享和交流。
-
逻辑复用
:自定义 Hooks 可以将特定的逻辑封装起来,在应用的不同部分进行复用。例如,上述的
-
局限
- 功能封装 :自定义 Hooks 本质上是对功能的封装,与直接使用 React Hooks 相比,它不会直接与 React 的生命周期和状态进行交互。
- 使用规则 :需要遵循 Hooks 的使用规则,不能在循环、条件语句、嵌套函数或回调函数中调用 Hooks。
8. 开发 React 应用的思考方式
React Hooks 强调了在 React 开发中思考的重要性。在选择使用特定的 Hook 时,需要仔细考虑应用的需求和场景。以下是开发 React 应用时的一些思考建议:
graph LR
A[明确需求] --> B[分析场景]
B --> C{选择合适的 Hook}
C -->|状态管理| D[useState/useReducer]
C -->|生命周期事件| E[useEffect]
C -->|引用| F[useRef]
C -->|上下文| G[useContext]
C -->|性能优化| H[React.memo/useMemo/useCallback]
C -->|自定义逻辑| I[自定义 Hooks]
D --> J[开发实现]
E --> J
F --> J
G --> J
H --> J
I --> J
J --> K[性能检查]
K -->|有问题| C
K -->|无问题| L[完成开发]
- 明确需求 :在开始开发之前,需要明确应用的功能需求和业务逻辑。
- 分析场景 :根据需求分析具体的使用场景,确定需要使用哪些 Hooks 来实现相应的功能。
-
选择合适的 Hook
:根据场景选择合适的 React Hooks 或自定义 Hooks。例如,对于简单的状态管理,可以使用
useState;对于复杂的状态管理,可以使用useReducer;对于性能优化,可以使用记忆化技术。 - 开发实现 :根据选择的 Hooks 进行代码开发和实现。
- 性能检查 :在开发完成或完成大部分开发后,使用 Profiler 检查应用的性能,发现性能漏洞并进行修复。
- 不断优化 :根据性能检查的结果,不断调整和优化代码,选择更合适的 Hooks 或优化记忆化技术的使用。
9. 总结与展望
在 React 开发中,Hooks 是一项非常重要的技术,它为函数式组件提供了处理状态和副作用的能力,使代码更加简洁和易于维护。通过学习和掌握 React.memo、useMemo、useCallback 等记忆化技术,可以显著提升 React 应用的性能。同时,自定义 Hooks 的使用可以帮助我们提取和重用逻辑,提高开发效率。
在未来的 React 开发中,随着技术的不断发展,可能会出现更多强大的 Hooks 和性能优化技术。开发者需要不断学习和探索,以适应不断变化的技术环境。同时,在开发过程中,要始终保持思考和实践,根据具体的需求和场景选择合适的技术和方法,才能开发出高质量的 React 应用。
总之,React Hooks 为我们提供了一种全新的开发方式,通过合理使用各种 Hooks 和记忆化技术,我们可以更好地管理状态、处理副作用和优化性能,从而打造出更加高效、稳定和可维护的 React 应用。
超级会员免费看
1771

被折叠的 条评论
为什么被折叠?



