17、React Hooks 性能优化与自定义 Hook 全解析

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 来计算阶乘:
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 本质上是对功能的封装,与直接使用 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 应用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值