如何对 React 函数式组件进行优化
文章首发个人博客
前言
目的
本文只介绍函数式组件特有的性能优化方式,类组件和函数式组件都有的不介绍,比如 key 的使用。另外本文不详细的介绍 API 的使用,后面也许会写,其实想用好 hooks 还是蛮难的。
面向读者
有过 React 函数式组件的实践,并且对 hooks 有过实践,对 useState、useCallback、useMemo API 至少看过文档,如果你有过对类组件的性能优化经历,那么这篇文章会让你有种熟悉的感觉。
React 性能优化思路
我觉得React 性能优化的理念的主要方向就是这两个:
减少重新 render 的次数。因为在 React 里最重(花时间最长)的一块就是 reconction(简单的可以理解为 diff),如果不 render,就不会 reconction。
减少计算的量。主要是减少重复计算,对于函数式组件来说,每次 render 都会重新从头开始执行函数调用。
在使用类组件的时候,使用的 React 优化 API 主要是:shouldComponentUpdate和 PureComponent,这两个 API 所提供的解决思路都是为了减少重新 render 的次数,主要是减少父组件更新而子组件也更新的情况,虽然也可以在 state 更新的时候阻止当前组件渲染,如果要这么做的话,证明你这个属性不适合作为 state,而应该作为静态属性或者放在 class 外面作为一个简单的变量 。
但是在函数式组件里面没有声明周期也没有类,那如何来做性能优化呢?
React.memo
首先要介绍的就是 React.memo,这个 API 可以说是对标类组件里面的 PureComponent,这是可以减少重新 render 的次数的。
可能产生性能问题的例子
举个🌰,首先我们看两段代码:
在根目录有一个 index.js,代码如下,实现的东西大概就是:上面一个 title,中间一个 button(点击 button 修改 title),下面一个木偶组件,传递一个 name 进去。
// index.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Child from './child'
function App() {
const [title, setTitle] = useState("这是一个 title")
return (
);
}
const rootElement = document.getElementById("root");
ReactDOM.render( , rootElement);
在同级目录有一个 child.js
// child.js
import React from "react";
function Child(props) {
console.log(props.name)
return
);
}
const rootElement = document.getElementById("root");
ReactDOM.render( , rootElement);
子组件 child.js
import React from "react";
function Child(props) {
console.log(props);
return (
<>
);
}
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 (
);
}
首次渲染的效果如下:
useMemo
这个例子功能很简单,就是点击 +1 按钮,然后会将现在的值(num) 与 计算函数 (expensiveFn) 调用后的值相加,然后将和设置给 num 并显示出来,在控制台会输出 49995000。
可能产生性能问题
就算是一个看起来很简单的组件,也有可能产生性能问题,通过这个最简单的例子来看看还有什么值得优化的地方。
首先我们把 expensiveFn 函数当做一个计算量很大的函数(比如你可以把 i 换成 10000000),然后当我们每次点击 +1 按钮的时候,都会重新渲染组件,而且都会调用 expensiveFn 函数并输出 49995000。由于每次调用 expensiveFn 所返回的值都一样,所以我们可以想办法将计算出来的值缓存起来,每次调用函数直接返回缓存的值,这样就可以做一些性能优化。
useMemo 做计算结果缓存
针对上面产生的问题,就可以用 useMemo 来缓存 expensiveFn 函数执行后的值。
首先介绍一下 useMemo 的基本的使用方法,详细的使用方法可见官网:
function computeExpensiveValue() {
// 计算量很大的代码
return xxx
}
const memoizedValue = useMemo(computeExpensiveValue, [a, b]);
useMemo 的第一个参数就是一个函数,这个函数返回的值会被缓存起来,同时这个值会作为 useMemo 的返回值,第二个参数是一个数组依赖,如果数组里面的值有变化,那么就会重新去执行第一个参数里面的函数,并将函数返回的值缓存起来并作为 useMemo 的返回值 。
了解了 useMemo 的使用方法,然后就可以对上面的例子进行优化,优化代码如下:
function App() {
const [num, setNum] = useState(0);
function expensiveFn() {
let result = 0;
for (let i = 0; i < 10000; i++) {
result += i;
}
console.log(result)
return result;
}
const base = useMemo(expensiveFn, []);
return (
);
}
执行上面的代码,然后现在可以观察无论我们点击 +1多少次,只会输出一次 49995000,这就代表 expensiveFn 只执行了一次,达到了我们想要的效果。
小结
useMemo 的使用场景主要是用来缓存计算量比较大的函数结果,可以避免不必要的重复计算,有过 vue 的使用经历同学可能会觉得跟 Vue 里面的计算属性有异曲同工的作用。
不过另外提醒两点
一、如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值;
二、计算量如果很小的计算函数,也可以选择不使用 useMemo,因为这点优化并不会作为性能瓶颈的要点,反而可能使用错误还会引起一些性能问题。
总结
对于性能瓶颈可能对于小项目遇到的比较少,毕竟计算量小、业务逻辑也不复杂,但是对于大项目,很可能是会遇到性能瓶颈的,但是对于性能优化有很多方面:网络、关键路径渲染、打包、图片、缓存等等方面,具体应该去优化哪方面还得自己去排查,本文只介绍了性能优化中的冰山一角:运行过程中 React 的优化。
React 的优化方向:减少 render 的次数;减少重复计算。
如何去找到 React 中导致性能问题的方法,见 useCallback 部分。
合理的拆分组件其实也是可以做性能优化的,你这么想,如果你整个页面只有一个大的组件,那么当 props 或者 state 变更之后,需要 reconction 的是整个组件,其实你只是变了一个文字,如果你进行了合理的组件拆分,你就可以控制更小粒度的更新。
合理拆分组件还有很多其他好处,比如好维护,而且这是学习组件化思想的第一步,合理的拆分组件又是一门艺术了,如果拆分得不合理,就有可能导致状态混乱,多敲代码多思考。
推荐文章
我这里只介绍了函数式组件的优化方式,更多的 React 优化技巧可以阅读下面的文章:
21 个 React 性能优化技巧
浅谈React性能优化的方向
后记
我是桃翁,一个爱思考的前端er,想了解关于更多的前端相关的,请关注我的公号:「前端桃园」,如果想加入交流群关注公众号后回复「微信」拉你进群
本文由博客一文多发平台 OpenWrite 发布!ttps://www.cnblogs.com/taoweng/p/11897040.html