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

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

日常开发场景

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

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.memocount2 组件进行优化:

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 记忆化了基于count1shouldActivate的回调函数。

总结

我们先看一个表格总结,这表格总结了 React.memouseMemouseCallback 的主要特性。

特性 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.memouseMemouseCallback 的运用,我们能够有效地降低组件的重新渲染次数,确保组件只在必要时进行重新渲染,提升应用的性能表现。希望这篇文章,对你有所帮助!

相关推荐
Z兽兽3 小时前
React@18+Vite项目配置env文件
前端·react.js·前端框架
SuniaWang3 小时前
《Spring AI + 大模型全栈实战》学习手册系列 · 专题六:《Vue3 前端开发实战:打造企业级 RAG 问答界面》
java·前端·人工智能·spring boot·后端·spring·架构
A_nanda4 小时前
根据AI提示排查vue前端项目
前端·javascript·vue.js
happymaker06265 小时前
web前端学习日记——DAY05(定位、浮动、视频音频播放)
前端·学习·音视频
~无忧花开~5 小时前
React状态管理完全指南
开发语言·前端·javascript·react.js·前端框架
LegendNoTitle5 小时前
计算机三级等级考试 网络技术 选择题考点详细梳理
服务器·前端·经验分享·笔记·php
@大迁世界5 小时前
1.什么是 ReactJS?
前端·javascript·react.js·前端框架·ecmascript
BJ-Giser6 小时前
Cesium 基于EZ-Tree的植被效果
前端·可视化·cesium
王码码20357 小时前
Flutter for OpenHarmony:Flutter 三方库 algoliasearch 毫秒级云端搜索体验(云原生搜索引擎)
android·前端·git·flutter·搜索引擎·云原生·harmonyos
发现一只大呆瓜7 小时前
深入浅出 AST:解密 Vite、Babel编译的底层“黑盒”
前端·面试·vite