React性能优化实战,解决不必要的重新渲染

在日常 React 开发中,我们通常会面临一个常见的问题:组件的不必要重新渲染。这不仅影响应用性能,还可能导致不必要的资源浪费。在本文中,我们将通过一个简单的实例来演示在日常开发中遇到的问题,讨论如何运用 React.memo、useMemo 和 useCallback 来优化组件性能,避免不必要的重新渲染。

日常开发场景

假设我们正在开发一个计数器应用,用户可以通过点击按钮分别增加两个计数器的数值。同时,我们希望在某些特定条件下触发一些状态的变化。下面是一个简化的代码示例:

jsx 复制代码
import React, { useState } from 'react';

const App = () => {
    const [count1, setCount1] = useState(0);
    const [count2, setCount2] = useState(0);
    const [active, setActive] = useState(false);

    const increaseCounter = () => {
        setCount1(count1 + 1);
        // 需要在某些条件下触发状态变化
        if (count2 > 5) {
            setActive(true);
        }
    }

    return (
        <>
            <button onClick={increaseCounter}>Increase counter</button>
            <div>Counter 1: {count1}</div>
            <div>Counter 2: {count2}</div>
            {active && <div>特定条件已触发</div>}
        </>
    );
}

export default App;

在这个简单的例子中,存在潜在的性能问题:

  1. 频繁的重新渲染: 每次增加计数器时,都会导致 App 组件重新渲染,即使只有 count1 发生了变化。
  2. 性能开销: 随着应用规模的增大,不必要的重新渲染会导致性能开销的增加。

引入 React.memo 优化:函数组件的记忆化

为了避免不必要的重新渲染,我们可以使用 React.memo 对子组件进行优化。这个工具通过浅层比较 props 的方式来实现,确保只有在相关数据发生变化时才触发组件的重新渲染。在下面的例子中,我们演示如何使用 React.memo 对 count2 组件进行优化:

jsx 复制代码
// Counter 组件
const Counter = ({ value, children }) => {
    console.log('Render: ', children);

    return (
        <div>
            {children}: {value}
        </div>
    );
}

export default React.memo(Counter);

经过这一步优化后,我们发现只有 count1 在每次点击按钮后重新渲染,而 count2 不再频繁重新渲染。这是因为 React.memo 在浅比较 props 的情况下,只有在 count2 发生变化时才重新渲染。

若涉及到复杂的数据结构,我们还可以通过 React.memo 的第二个参数传递自定义的比较函数,实现深层比较。

jsx 复制代码
const Counter = ({ value, children }) => {
    console.log('Render: ', children);

    return (
        <div>
            {children}: {value}
        </div>
    );
}

const isEqual = (prevProps, nextProps) => {
    if (prevProps.name !== nextProps.name) {
        return false;
    }
    return true;
}

export default React.memo(Counter, isEqual);

进一步优化:useMemo 和 useCallback

在上述例子中,我们只是解决了 count2 组件的重新渲染问题。然而,我们还可以在组件中应用更深层次的优化。假设我们希望在点击按钮时,还能显示一个文字提示,告诉用户按钮是否已被点击。为了避免不必要的计算,我们可以使用 useMemo 来记忆化计算结果,同时使用 useCallback 记忆化回调函数。

jsx 复制代码
const App = () => {
    const [count1, setCount1] = useState(0);
    const [count2, setCount2] = useState(0);
    const [active, setActive] = useState(false);

    // 使用 useMemo 记忆化计算结果
    const shouldActivate = React.useMemo(() => count2 > 5, [count2]);

    // 使用 useCallback 记忆化回调函数
    const increaseCounter = React.useCallback(() => {
        setCount1(count1 + 1);
        // 只有在特定条件下才触发状态变化
        if (shouldActivate) {
            setActive(true);
        }
    }, [count1, shouldActivate]);

    return (
        <>
            <button onClick={increaseCounter}>Increase counter</button>
            <Counter value={count1}>Counter 1</Counter>
            <Counter value={count2}>Counter 2</Counter>
            {active && <div>特定条件已触发</div>}
        </>
    );
}

在这个例子中,我们使用了 useMemo 来记忆化了基于 count2 计算的结果,并使用 useCallback 记忆化了基于count1、shouldActivate的回调函数。

总结

我们先看一个表格总结,这表格总结了 React.memo、useMemo 和 useCallback 的主要特性。

特性 React.memo useMemo useCallback
用途 避免不必要的重新渲染 记忆化计算结果 记忆化回调函数
适用场景 函数组件 记忆化计算结果 记忆化回调函数
参数 一个组件 一个计算函数和依赖项数组 一个回调函数和依赖项数组
返回值 一个记忆化的组件 一个记忆化的值 一个记忆化的回调函数
作用方式 对比 props 的变化 对比依赖项数组的变化 对比依赖项数组的变化

下面我们看下具体的区别和示例,加深理解~

React.memo

  • 用途:用于函数组件,通过对比组件的 props 变化来避免不必要的重新渲染。

  • 工作原理:React.memo 对比组件接收到的 props 是否发生变化。仅当 props 发生变化时,才会重新渲染组件,否则会使用上一次的渲染结果。

  • 示例:

    jsx 复制代码
    const memoComponent = React.memo(MyComponent);

useMemo

  • 用途:用于记忆化计算结果,避免在每次渲染时都重新计算。

  • 工作原理:useMemo 接收一个计算函数和一个依赖项数组。它仅在依赖项数组中的值发生变化时,才会重新计算,并返回记忆化的值。

  • 示例:

    jsx 复制代码
    const useMemoValue = useMemo(() => computeExpensiveValue(dep1, dep2), [dep1, dep2]);

useCallback

  • 用途:用于记忆化回调函数,避免在每次渲染时都重新创建回调。

  • 工作原理:useCallback 接收一个回调函数和一个依赖项数组。它仅在依赖项数组中的值发生变化时,才会返回上一次的记忆化的回调函数。

  • 示例:

    jsx 复制代码
    const useCallbackValue = useCallback(() => {
        // 回调逻辑
    }, [dep1, dep2]);

注意:useMemo是计算函数,useCallback是回调函数。

收尾

通过 React.memo、useMemo 和 useCallback 的运用,我们能够有效地降低组件的重新渲染次数,确保组件只在必要时进行重新渲染,提升应用的性能表现。希望这篇文章,对你有所帮助!

相关推荐
Penge6669 小时前
React 事件处理:bind(this) 原理与实际开发常用写法
前端
Yuzhiyuxia9 小时前
【AI Agent面试系列】AI Agent实战设计
人工智能·面试·职场和发展
ZhengEnCi9 小时前
小白用 AI 做官网完全指南:不用框架的六格清单,双击就能打开
前端·html·ai编程
zhanlang02179 小时前
HTML 零基础入门到企业实战【第五章 5.2】图片路径精讲:绝对路径、相对路径、网络路径(新手必踩坑点)
前端·html
夕水10 小时前
我用trae重构了ew-color-picker,一个插件化架构的现代颜色选择器
前端·javascript·开源
Penge66610 小时前
React JSX 核心语法:从入门到业务开发实战
前端
zhanlang021710 小时前
HTML 零基础入门到企业实战全套教程【第五章 5.5 综合案例:图文影音展示页面开发】
前端·html
时间的拾荒人10 小时前
Redis 哨兵(Sentinel)详解:原理、选举机制与实战部署
redis·面试·架构·sentinel
zhanlang021710 小时前
HTML 零基础入门到企业实战全套教程【连载 5.3】audio、video 音频视频标签|兼容格式、自动播放踩坑、企业规范
前端·html
nice_lcj52010 小时前
MySQL 存储引擎入门:InnoDB 和 MyISAM 到底怎么选
java·数据库·mysql·面试·innodb·存储引擎·myisam