React中这些优化手段,你会了吗?

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 8 天,点击查看活动详情

大家好,我是爱吃鱼的桶哥Z,在我们平时的开发中,经常会用到React/Vue,并且在面试中也会经常问到关于它们的一些问题,最常见的问题莫过于如何在React/Vue做性能优化,并且会让你讲一下为什么要这么实现。今天我们就一起来学习一下React相关的性能优化手段有哪些,你准备好了吗?我们出发吧!

useCallback

我们在日常的工作也好,面试也好,经常都会用到防抖/节流,主要用于性能的优化,最常见的例子大概就是限制输入框频繁的输入造成不停跟后端数据进行交互的问题了,使用了防抖后,不仅仅前端输入框能够提升一定的性能,后端的资源也降低很大的开销,但是我们在React如果直接使用防抖函数,那么可能就没什么效果,让我们一起来看一下代码吧,如下:

import { useState } from 'react';
import debounce from 'lodash/debounce';

const Demo = () => {
    const [name, setName] = useState('');
    
    const onChange = (e: React.FormEvent<HTMLInputElement>): void => {
        setName(e.currentTarget.value)
        console.log(e.currentTarget.value);
    };
    
    const search = (value: string): void => {
        console.log('debounce', value);
    };
    
    const debounceSearch = debounce(search, 1000);
    
    return (
        <div>
            <h1>React Hooks</h1>
            <input onChange={onChange} value={name} />
        </div>
    )
};
复制代码

上述的代码,虽然我们添加了debounce功能,但是在实际的执行过程中,还是没有起到防抖的作用,具体的例子可以狠戳这里,当我们频繁输入内容时,可以明显的看到控制台中一直在更新内容,而不是间隔1秒。

遇到这样的问题,我们该如何解决才能实现我们最终的效果呢?其实很简单,只需要使用useCallback将函数缓存一下即可,因为我们在每次输入后,防抖函数都是重新生成一个新的来执行,所以并没有完成真正的防抖,下面我们一起改造一下代码:

import { useState, useCallback } from 'react';
...other code

const debounceSearch = useCallback(debounce(search, 1000), []);

...other code
复制代码

只需要简单的修改一下即可,具体的效果可以狠戳这里

useCallback + memo

在一些视频里面建议大家多用useCallback,但是我的建议是,还是需要根据实际的场景来,而不是什么函数都给它套一下useCallback。当然,说到useCallback就不得不说useMemomemo了,面试的时候经常会问它们之间的区别,并且如果有童鞋看过React相关的源码,就会知道它们内部的实现是怎么样的了,下面我们一起来看一下使用useCallback配合memo做性能优化,代码如下:

// 先看没有使用 useCallback 和 memo 的情况
import React, { useState } from 'react';
import MyButton from './myButton';

const Demo = () => {
    const [num, setNum] = useState(0);
    
    const handleClick = () => {
        setNum(num + 1);
    };
    
    return (
        <div>
            <p>parent: {num}</p>
            <MyButton handleClick={handleClick}>点击按钮</MyButton>
        </div>
    )
}
复制代码

当我们点击按钮时,父组件的状态会发生变化,但是子组件也重新渲染了,这其实不是我们想要看到的,具体的效果可以狠戳这里。那么我们能够怎么修改一下重新使子组件不会重新渲染呢?下面我们一起来看一下修改后的代码:

...other code 

const handleClick = useCallback(() => {
    setNum((num) => num + 1);
}, []);

...other code

// button.tsx
... other code
export default memo(MyButton);
复制代码

只需要将父组件的点击事件通过useCallback缓存住,并且子组件通过memo包裹一层,这样当父组件状态发生改变时,子组件本身是不会重新渲染的,这样可以大大的提高子组件的性能,具体的效果可以狠戳这里。之所有通过memo包裹的子组件不会重新发生渲染,是因为memo会检查父组件传递的props是否发生变更,并且默认情况下其只会对复杂对象做浅层对比,如果想要控制对比过程,那么就需要将自定义的比较函数通过第二个参数传入来实现。

memo只是做为性能优化的一个手段来使用,但是请不要依赖它来“阻止”渲染,因为这样会产生bug

useMemo

前面我们介绍了useCallback主要用于缓存函数,memo则用于判断父组件传递给子组件的props是否发生变化,而useMemo则用于缓存计算返回的值,主要有助于避免组件在每次渲染时都进行高开销的计算,下面我们一起来举一个例子看看就明白了,大致代码如下:

扫描二维码关注公众号,回复: 14437564 查看本文章
... other code
const [num, setNum] = useState("0");
const [count, setCount] = useState(0);

// 不使用 useMemo 前
const expensive = () => {
    let sum = 0;
    for (let i = 0; i < count * 1000; i++) {
        sum += i;
    }
    console.log("组件更新了");
    return sum;
};

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    console.log(event.currentTarget.value);
    setNum(event.currentTarget.value);
};

const handleClick = () => {
    setCount((count) => count + 1);
};
... other code
复制代码

当我们改变num的值时,expensive这个函数却在重复的执行,具体的效果可以狠戳这里。在控制台中我们可以看到组件expensive执行了很多遍,但是我们并没有重复的计算,这对组件的性能就会有很大的浪费,如图所示:

image.png

从上图中就可以看出expensivenum发生变化的时候也跟着变化,这其实是不必要的,下面我们一起来对代码进行整改,整改后的代码就能明显的避免组件重复计算的问题,代码如下:

...other code
const expensive = useMemo(() => {
    let sum = 0;
    for (let i = 0; i < count * 100; i++) {
        sum += i;
    }
    console.log("组件更新了");
    return sum;
}, [count]);
...other code
复制代码

优化后的组件我们可以在控制台中明显的看到变化,当num值发生改变时,expensive中依赖的count没有变化,所以这个函数不会重新计算,具体的效果可以狠戳这里。通过下图我们也可以明显的看出差异来,如下:

image.png

对不上图中,每当num发生变化时,expensive都会重新执行一次,添加了useMemo后的组件明显的避免在每次渲染时都进行高开销的计算,这样较大的提高组件的性能,但是值得我们注意的是,你可以把useMemo作为性能优化的手段,但不要把它当成语义上的保证,因为React可能会选择“遗忘”以前的一些memoized值,并在下次渲染时重新计算它们。

最后

我们来总结一下useCallbackuseMmeomemo之间的区别,以及它们的适用场景。

差异 适用场景
useCallback 主要用于优化针对于子组件渲染,返回一个memoized的函数 主要用于函数的缓存
useMemo 主要用于优化针对于当前组件高开销的计算,返回一个memoized的值 主要用于组件中需要计算的函数
memo 主要用于检测父组件props的变更,用来优化函数组件的重复渲染行为 主要用于子组件的缓存

它们之间的区别在上表中已经列出来了,在面试的时候能够说出它们具体如何使用以及它们的区别,那么这道题基本就没有什么问题了。

最后,如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,谢谢大家。

往期回顾

React中这几个好用的Hook你还不会吗?快来学习一下

React中自定义hook,提高开发效率,你学会了吗?

React中这几个常用的自定义Hook,你学会了吗?

React中这几个常用的自定义Hook,你学会了吗?(2)

猜你喜欢

转载自juejin.im/post/7127585439841517605