React函数式组件的性能优化思路详解
优化思路
主要优化的方向有2个:
- 减少重新 render 的次数。因为在 React 里最重(花时间最长)的一块就是 reconction(简单的可以理解为 diff),如果不 render,就不会 reconction。
- 减少计算的量。主要是减少重复计算,对于函数式组件来说,每次 render 都会重新从头开始执行函数调用。
在使用类组件的时候,使用的 React 优化 API 主要是:shouldComponentUpdate和 PureComponent
那么在函数式组件中,我们怎么做性能优化?主要用到下面几个方法去优化
- React.memo
- useCallback
- useMemo
React.memo
看个例子:
我们在父组件中放一个按钮用于修改子标题,并引入Child子组件
可以看到,第一次进来子组件打印了console.log('我是子组件')
当点击修改子标题,Child子组件也打印了,造成了不必要的重复渲染次数
//父组件 import {useState} from 'react' import Child from "./Child"; const Index = ()=>{ const [subTitle, setSubTitle] = useState('我是子标题') const updateSubTitle = ()=>{ setSubTitle('修改子标题') } return ( <div> <div>函数式组件性能优化</div> <div>{subTitle}</div> <button onClick={updateSubTitle}>修改子标题</button> <Child/> </div> ); } export default Index; //子组件Child.js const Child = ()=>{ console.log('我是子组件') return ( <div>我是子组件</div> ) } export default Child
优化一下,使用React.memo包裹子组件
import React from "react"; const Child = ()=>{ console.log('我是子组件') return ( <div>我是子组件</div> ) } export default React.memo(Child)
再观察一下,发现Child子组件没有重复渲染了
useCallback
这里我们再改造一下,给Child子组件添加一个onclick事件,然后点击修改子标题按钮,发现我们的Child子组件又重新渲染了,这里主要是因为修改子标题的时候handlerClick函数重新渲染变化,造成子组件重新渲染
// 父组件 const Index = ()=>{ const [subTitle, setSubTitle] = useState('我是子标题') const updateSubTitle = ()=>{ setSubTitle('修改子标题') } const handlerClick = ()=>{ console.log('子组件点击') } return ( <div> <div>函数式组件性能优化</div> <div>{subTitle}</div> <button onClick={updateSubTitle}>修改子标题</button> <Child onClick={handlerClick}/> </div> ); } // Child子组件 const Child = (props)=>{ console.log('我是子组件') return ( <div> <div>我是子组件</div> <button onClick={props.onClick}>子组件按钮</button> </div> ) } export default React.memo(Child)
优化一下,使用useCallback包裹处理子组件的handlerClick函数,再次点击updateSubTitle修改子标题,发现Child子组件没有重新再渲染
// 父组件 const Index = ()=>{ const [subTitle, setSubTitle] = useState('我是子标题') const updateSubTitle = ()=>{ setSubTitle('修改子标题') } const handlerClick = useCallback(()=>{ console.log('子组件点击') },[]) return ( <div> <div>函数式组件性能优化</div> <div>{subTitle}</div> <button onClick={updateSubTitle}>修改子标题</button> <Child onClick={handlerClick}/> </div> ); } export default Index;
这里关于useCallback的用法
const callback = () => { doSomething(a, b); } const memoizedCallback = useCallback(callback, [a, b])
把函数以及依赖项作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,这个 memoizedCallback 只有在依赖项有变化的时候才会更新。
useMemo
useMemo用于计算结果缓存
我们先看个例子,在之前基础上添加一个calcCount计算函数,然后点击updateSubTitle更新子标题,发现calcCount重新计算了,也就是每次渲染都会造成重复计算,如果是计算量比较大的情况下,会极大的影响性能
// 父组件 const Index = ()=>{ const [subTitle, setSubTitle] = useState('我是子标题') const updateSubTitle = ()=>{ setSubTitle('修改子标题') } const handlerClick = useCallback(()=>{ console.log('子组件点击') },[]) const calcCount = ()=>{ let totalCount = 0 for(let i=0;i<10000;i++){ totalCount+=i } console.log('totalCount',totalCount) return totalCount } const count = calcCount() return ( <div> <div>函数式组件性能优化</div> <div>{subTitle}</div> <button onClick={updateSubTitle}>修改子标题</button> <div>count:{count}</div> <Child onClick={handlerClick}/> </div> ); }
优化一下,使用useMemo缓存计算结果,我们再次点击updateSubTitle修改子标题按钮,可以发现calcCount函数不再重复计算
const calcCount = ()=>{ let totalCount = 0 for(let i=0;i<10000;i++){ totalCount+=i } console.log('totalCount',totalCount) return totalCount } const count = useMemo(calcCount,[])
最后,需要注意的是不能盲目的使用useMemo,要根据具体的场景,比如对于一个数据计算量比较大,那么使用是比较适用的,而对于普通的一些值得计算,可以不使用,因为本身useMemo也是会消耗一些性能,盲目使用反而会适得其反
参考阅读
https://mp.weixin.qq.com/s/YGvmSrr-yhPUNHbwlLSFsA
http://www.ptbird.cn/react-hook-useMemo-purerender.html
到此这篇关于React函数式组件的性能优化的文章就介绍到这了,更多相关React性能优化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!