React 函数式组件性能优化指南(1)

本文讲述了如何在React中使用React.memo和useCallback来优化组件渲染,特别是在父组件的props发生变化时,如何确保子组件只在必要时重新渲染,以提高性能。
摘要由CSDN通过智能技术生成

通过 React.memo 包裹的组件在 props 不变的情况下,这个被包裹的组件是不会重新渲染的,也就是说上面那个例子,在我点击改名字之后,仅仅是 title 会变,但是 Child 组件不会重新渲染(表现出来的效果就是 Child 里面的 log 不会在控制台打印出来),会直接复用最近一次渲染的结果。

这个效果基本跟类组件里面的 PureComponent效果极其类似,只是前者用于函数组件,后者用于类组件。

React.memo 高级用法

默认情况下其只会对 props 的复杂对象做浅层对比(浅层对比就是只会对比前后两次 props 对象引用是否相同,不会对比对象里面的内容是否相同),如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

function MyComponent(props) {

/* 使用 props 渲染 */

}

function areEqual(prevProps, nextProps) {

/*

如果把 nextProps 传入 render 方法的返回结果与

将 prevProps 传入 render 方法的返回结果一致则返回 true,

否则返回 false

*/

}

export default React.memo(MyComponent, areEqual);

此部分来自于 React 官网[1]。

如果你有在类组件里面使用过 `shouldComponentUpdate()`[2] 这个方法,你会对 React.memo 的第二个参数非常的熟悉,不过值得注意的是,如果 props 相等,areEqual会返回 true;如果 props 不相等,则返回 false。这与 shouldComponentUpdate方法的返回值相反。

useCallback


现在根据上面的例子,再改一下需求,在上面的需求上增加一个副标题,并且有一个修改副标题的 button,然后把修改标题的 button 放到 Child 组件里。

把修改标题的 button 放到 Child 组件的目的是,将修改 title 的事件通过 props 传递给 Child 组件,然后观察这个事件可能会引起性能问题。

首先看代码:

父组件 index.js

// index.js

import React, { useState } from “react”;

import ReactDOM from “react-dom”;

import Child from “./child”;

function App() {

const [title, setTitle] = useState(“这是一个 title”);

const [subtitle, setSubtitle] = useState(“我是一个副标题”);

const callback = () => {

setTitle(“标题改变了”);

};

return (

{title}

{subtitle}

<button onClick={() => setSubtitle(“副标题改变了”)}>改副标题

);

}

const rootElement = document.getElementById(“root”);

ReactDOM.render(, rootElement);

子组件 child.js

import React from “react”;

function Child(props) {

console.log(props);

return (

<>

改标题

{props.name}

</>

);

}

export default React.memo(Child);

首次渲染的效果

image-20191031235605228

这段代码在首次渲染的时候会显示上图的样子,并且控制台会打印出桃桃

然后当我点击改副标题这个 button 之后,副标题会变为「副标题改变了」,并且控制台会再次打印出桃桃,这就证明了子组件又重新渲染了,但是子组件没有任何变化,那么这次 Child 组件的重新渲染就是多余的,那么如何避免掉这个多余的渲染呢?

找原因

我们在解决问题的之前,首先要知道这个问题是什么原因导致的?

咱们来分析,一个组件重新重新渲染,一般三种情况:

  1. 要么是组件自己的状态改变

  2. 要么是父组件重新渲染,导致子组件重新渲染,但是父组件的 props 没有改版

  3. 要么是父组件重新渲染,导致子组件重新渲染,但是父组件传递的 props 改变

接下来用排除法查出是什么原因导致的:

第一种很明显就排除了,当点击改副标题 的时候并没有去改变 Child 组件的状态;

第二种情况好好想一下,是不是就是在介绍 React.memo 的时候情况,父组件重新渲染了,父组件传递给子组件的 props 没有改变,但是子组件重新渲染了,我们这个时候用 React.memo 来解决了这个问题,所以这种情况也排除。

那么就是第三种情况了,当父组件重新渲染的时候,传递给子组件的 props 发生了改变,再看传递给 Child 组件的就两个属性,一个是 name,一个是 onClick ,name是传递的常量,不会变,变的就是 onClick 了,为什么传递给 onClick 的 callback 函数会发生改变呢?在文章的开头就已经说过了,在函数式组件里每次重新渲染,函数组件都会重头开始重新执行,那么这两次创建的 callback 函数肯定发生了改变,所以导致了子组件重新渲染。

如何解决

找到问题的原因了,那么解决办法就是在函数没有改变的时候,重新渲染的时候保持两个函数的引用一致,这个时候就要用到 useCallback 这个 API 了。

useCallback 使用方法

const callback = () => {

doSomething(a, b);

}

const memoizedCallback = useCallback(callback, [a, b])

把函数以及依赖项作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,这个 memoizedCallback 只有在依赖项有变化的时候才会更新。

那么可以将 index.js 修改为这样:

// index.js

import React, { useState, useCallback } from “react”;

import ReactDOM from “react-dom”;

import Child from “./child”;

function App() {

const [title, setTitle] = useState(“这是一个 title”);

const [subtitle, setSubtitle] = useState(“我是一个副标题”);

const callback = () => {

setTitle(“标题改变了”);

};

// 通过 useCallback 进行记忆 callback,并将记忆的 callback 传递给 Child

const memoizedCallback = useCallback(callback, [])

return (

{title}

{subtitle}

<button onClick={() => setSubtitle(“副标题改变了”)}>改副标题

);

}

const rootElement = document.getElementById(“root”);

ReactDOM.render(, rootElement);

这样我们就可以看到只会在首次渲染的时候打印出桃桃,当点击改副标题和改标题的时候是不会打印桃桃的。

如果我们的 callback 传递了参数,当参数变化的时候需要让它重新添加一个缓存,可以将参数放在 useCallback 第二个参数的数组中,作为依赖的形式,使用方式跟 useEffect 类似。

useMemo


在文章的开头就已经介绍了,React 的性能优化方向主要是两个:一个是减少重新 render 的次数(或者说减少不必要的渲染),另一个是减少计算的量。

前面介绍的 React.memo 和 useCallback 都是为了减少重新 render 的次数。对于如何减少计算的量,就是 useMemo 来做的,接下来我们看例子。

function App() {

const [num, setNum] = useState(0);

// 一个非常耗时的一个计算函数

// result 最后返回的值是 49995000

function expensiveFn() {

let result = 0;

for (let i = 0; i < 10000; i++) {

result += i;

}

console.log(result) // 49995000

return result;

}

const base = expensiveFn();

return (

count:{num}

<button onClick={() => setNum(num + base)}>+1

);

}

首次渲染的效果如下:

useMemo

这个例子功能很简单,就是点击 +1 按钮,然后会将现在的值(num) 与 计算函数 (expensiveFn) 调用后的值相加,然后将和设置给 num 并显示出来,在控制台会输出 49995000

可能产生性能问题

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
img

最后

javascript是前端必要掌握的真正算得上是编程语言的语言,学会灵活运用javascript,将对以后学习工作有非常大的帮助。掌握它最重要的首先是学习好基础知识,而后通过不断的实战来提升我们的编程技巧和逻辑思维。这一块学习是持续的,直到我们真正掌握它并且能够灵活运用它。如果最开始学习一两遍之后,发现暂时没有提升的空间,我们可以暂时放一放。继续下面的学习,javascript贯穿我们前端工作中,在之后的学习实现里也会遇到和锻炼到。真正学习起来并不难理解,关键是灵活运用。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

css源码pdf

JavaScript知识点
[外链图片转存中…(img-a3juDmBa-1710690100714)]

最后

javascript是前端必要掌握的真正算得上是编程语言的语言,学会灵活运用javascript,将对以后学习工作有非常大的帮助。掌握它最重要的首先是学习好基础知识,而后通过不断的实战来提升我们的编程技巧和逻辑思维。这一块学习是持续的,直到我们真正掌握它并且能够灵活运用它。如果最开始学习一两遍之后,发现暂时没有提升的空间,我们可以暂时放一放。继续下面的学习,javascript贯穿我们前端工作中,在之后的学习实现里也会遇到和锻炼到。真正学习起来并不难理解,关键是灵活运用。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

[外链图片转存中…(img-fa4mBet4-1710690100715)]

[外链图片转存中…(img-xwlcCLhT-1710690100715)]

  • 30
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值