1. 什么是useCallback和useMemo?

useCallback 和 useMemo 都是react可用于性能优化的内置hooks。

两者的区别在于:useCallback缓存的是一个函数,而useMemo缓存的是计算结果。

其使用语法如下:

// useCallback
// 第一个参数是一个回调函数,useCallback会缓存这个函数,返回缓存的回调函数
// 第二个参数是依赖项,只有当依赖项改变时,才会重新创建这个函数
const memorizedCallback = useCallback(()=>{doSomething(a,b);
},[a,b])// useMemo
// 第一个参数是一个函数,useMemo会缓存函数运行返回的值,返回缓存的值
// 第二个参数是依赖项,只有当依赖改变时,才会重新计算这个值
const memorizedValue = useMemo(()=>computeValue(a,b),[a,b])

2. 为什么使用useCallback和useMemo?

在函数式组件中,每次UI的变化,都是通过重新执行整个函数来完成的,这和传统的类组件有很大区别:函数组件中并没有一个直接的方式在多次渲染之间维持一个状态。

在重新执行整个函数组件的过程中,其中的函数和引用类型的变量会创建新的(指向新的引用),导致函数组件在re-render前后,其中函数和引用类型变量是不相等的,这又会导致其他非必要的re-render。比如以下例子:

function Counter() {const [count, setCount] = useState(0);// 只要组件状态发生变化,每次都要创建一个新的事件处理函数const handleIncrement = () => setCount(count + 1);// ...// 每次创建新函数的方式会让接收事件处理函数的组件需要重新渲染return <Button handleClick={handleIncrement}/>
}

当Counter组件因为其他数据(非count)发生变化而导致重新渲染的时候,重新执行整个Counter函数,会创建新的handleIncrement函数,而子组件Button会由于props-handleClick传入的handleIncrement函数改变而重新渲染,但其实这个渲染是不必要的,因为只有在count发生变化时,才应该导致Button组件的渲染。

此时,因为需要缓存的事handleIncrement函数,所以用useCallback优化一下。

// 需要做到:只有当count发生变化时,才需要重新定一个回调函数-useCallback
function Counter() {const [count, setCount] = useState(0);const handleIncrement = useCallback(() => setCount(count + 1),[count]  )
// 只有当依赖项count改变时,才会重新生成函数,不然都是返回的缓存的回调函数,不会触发Button子组件的重绘// ...return <Button handleClick={handleIncrement}/>
}

3. 什么时候使用useCallback和useMemo?

         3.1 useCallback
        当子组件接收一个函数props时,一般会使用useCallback来缓存这个函数,减少不必要的re-render。以下例子:向子组件传递一个函数,在父组件每次re-render的时候,函数会重新创建新的,这会导致使用这个函数props的子组件也re-render,但这是不必要的,可以用useCallback来解决。

function ParentComponent() {const onHandleClick = useCallback(() => {// this will return the same function// instance between re-renders});return (<MemoizedSubComponenthandleClick={onHandleClick}/>);
}

          3.2 useMemo
        useMemo常用在以下两种场景的优化中:1)引用类型的变量   2)需要大量时间执行的计算函数。

const UseMemoDemo = () => {// 调用这个函数需要大量时间去计算const slowFunction = (number) => {console.log('calling slow function')for (let i = 0; i <= 1000000000; i++) {}return number * 2}const [inputNumber, setInputNumber] = useState(1)const [dark, setDark] = useState(true)// 场景1:执行某函数需要大量时间,使用useMemo来优化,在不必要执行函数的时候不执行函数const doubleNumber = useMemo(() => {return slowFunction(inputNumber)}, [inputNumber])// 场景2:每次组件更新会重新执行,内部的引用类型变量会重新创建,这会导致使用到引用类型变量的组件重新渲染,使用useMemo来让每次的变量相同const themeStyle = useMemo(() => {return {background: dark ? 'black' : 'white',color: dark ? 'white' : 'black'}}, [dark])useEffect(() => {console.log('themeStyle changed')}, [themeStyle])const handleChange = (e) => {setInputNumber(parseInt(e.target.value))}return (<><input type='text' value={inputNumber} onChange={handleChange}/><button onClick={() => {setDark(prevDark => !prevDark)}}>change theme</button><p style={themeStyle}>{doubleNumber}</p></>)
}
export default UseMemoDemo;

在使用useMemo优化slowFunction这个耗时较长的函数之前,不管是改变input的值,使下方显示input值*2(这是实际需要运行slowFunction这个耗时函数的);还是点击button切换主题(这时不需要运行slowFunction这个耗时函数);都犹豫需要组件重新渲染,导致运行slowFunction这个函数,造成性能问题。点击切换主题时,也有明显的延迟。

在优化后,改变主题不再运行slowFunction函数,因为useMemo缓存了计算结果,只要inputNumber这个依赖项没有改变,就不会重新计算。优化后,点击切换主题不再有明显的延迟。

对于themeStyle引用类型变量的优化,也是相同的道理。当因为改变input值导致组件重新渲染时,实际上themeStyle变量是没有改变的,但由于要重新执行组件函数,所以创建了新的引用。这使得使用到引用类型变量的组件(button)重新渲染,造成性能浪费。

在使用useMemo优化后,themeStyle只会在变量dark的改变时改变,其他时候是useMemo缓存的值,不会因为其他无关变量改变或组件重绘而改变,造成不必要的组件re-render。

最后,更多的关于什么时候使用useCallback和useMemo需要在项目实践中花时间去思考性能优化的点。不能盲目地使用useCallback和useMemo,因为两者都需要内存去缓存,过多的非必要的使用也是不利于应用的性能的。

React---关于useCallback和useMemo的详解相关推荐

  1. webpack打包后引用cdn的js_利用CDN加速react webpack打包后的文件详解

    此文不介绍webpack基本配置,如果对基本配置有疑问请查阅官方文档. 1.配置webpack.config.js 将output.publicPath改成上传到的cdn地址, 例(对应上面上传配置) ...

  2. React Native 手势触摸事件机制详解(进阶篇)

    源码已开源到Github,详细代码可以查看:<React Native 触摸事件代码实践>. 在基础篇,对RN中的触摸事件做了详细的介绍.相信大家对于触摸事件流程机制有了更为清晰的认识.没 ...

  3. Taro3.2 适配 React Native 之运行时架构详解

    导读 由 58 前端团队主导的 Taro 3 适配 React Native 工作已完成有一段时间了.目前发布了多个体验版,也将在3月底迎来正式版.基于 Taro 的良好架构演变,适配 React N ...

  4. React Native 手势触摸事件机制详解(基础篇)

          欢迎大家关注[跨平台开发那些事]公众号,定期推送跨平台开发技术实践.        源码已开源到Github,详细代码可以查看:<React Native 触摸事件代码实践>. ...

  5. React - Redux Hooks的使用细节详解

    文章目录 Redux Hooks Redux中Hooks介绍 Redux中Hooks使用 Redux Hooks Redux中Hooks介绍 在之前的redux开发中,为了让组件和redux结合起来, ...

  6. react进阶系列 - 高阶组件详解四:高阶组件的嵌套使用

    前面有讲到过很多页面会在初始时验证登录状态与用户角色.我们可以使用高阶组件来封装这部分验证逻辑.封装好之后我们在使用的时候就可以如下: export default withRule(Home); 但 ...

  7. React Native之ScrollView控件详解

    概述 ScrollView在Android和ios原生开发中都比较常见,是一个 滚动视图控件.在RN开发中,系统也给我们提供了这么一个控件.不过在RN开发中 ,使用ScrollView必须有一个确定的 ...

  8. React 源码系列 | React Context 详解

    目前来看 Context 是一个非常强大但是很多时候不会直接使用的 api.大多数项目不会直接使用 createContext 然后向下面传递数据,而是采用第三方库(react-redux). 想想项 ...

  9. 视频教程-React Hooks 案例详解(React 进阶必备)-其他

    React Hooks 案例详解(React 进阶必备) Leo 1978年8月生,河北石家庄人. 曾任职中软股份. 计算科学导师: 刘坤起博士. 开发的<电商分销系统>获得淘宝Top10 ...

最新文章

  1. python基础day4
  2. java.net.UnknownHostException: unknown host:xxxx异常解决办法
  3. 各品牌交换机常用命令整理
  4. WordPress Kyma插件里Connect和disconnect按钮的动态显示逻辑
  5. Python(24)-面向对象3-可迭代类对象Pokemon
  6. 人生这场牌,怎么打才是最优解?
  7. sql server 2005 几个常用的存储过程或函数
  8. input[type=radio]自定义样式
  9. 零基础搭建完全免费个人静态博客
  10. 谷歌浏览器怎么设置成暗黑模式
  11. cherry-pick 用法
  12. 大学一年级计算机组成语结构试题,一年级语文上册期末:词语类考题全面练习(汇总版)...
  13. 去掉迅雷右侧内置浏览器
  14. 湖北商贸学院计算机二级领取,湖北商贸学院教务管理系统入口http://jw.hbc.edu.cn/...
  15. 三星Android Pie软件,至少在AndroidPie版本上的三星GalaxyS9现在具有缺陷检测功能
  16. 【C++】读取txt文件中指定行的内容
  17. Symfony框架系列----常用命令
  18. LayUI使用Echarts实现统计图
  19. 《剑指offer》专题—算法训练 day02
  20. MySQL数据库中的数据表

热门文章

  1. 分享一个很香的k8s.gcr.io Docker镜像拉取方法
  2. 词性标注:基于MaxEnt的中文词性标注模型实现
  3. python_1_初识python
  4. PKU2506Tiling
  5. 第七篇 indicators(2)指标的绘制
  6. iCCP: cHRM chunk does not match sRGB
  7. 原麦格纳亚洲区总裁布鲁诺兰伯特出任宝沃汽车全球总裁
  8. PDD卖百度网盘超级会员,是怎么做到销量10万+的?
  9. hdf heg 批量拼接_python调用HEG工具批量处理MODIS数据
  10. 【火车头采集教程】轻而易举学会火车头采集(附带采集案例)