React18中useMemo、useCallback和memo怎么使用


本篇内容介绍了“React18中useMemo、useCallback和memo怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

用法

useMemo

useMemo 是一个用于优化性能的 React 钩子。它可以帮助我们避免在组件重新渲染时执行昂贵的计算。useMemo 接受两个参数:一个函数和一个依赖数组。当依赖数组中的值发生变化时,useMemo 会重新计算并返回新的值。否则,它将返回上一次计算的值。

一个简单的例子:

importReact,{useMemo}from"react";functionExpensivponent({a,b}){constresult=useMemo(()=>{console.log("Expensivecalculation...");returna*b;},[a,b]);return<div>Result:{result}</div>;}

我们创建了一个名为 Expensivponent 的组件,它接受两个属性 ab 并使用 useMemo 钩子来计算 ab 的乘积。当 ab 发生变化时,useMemo 会重新计算结果。否则,它将返回上一次计算的值,避免了不必要的计算。

useCallback

useCallback 是另一个用于优化性能的 React 钩子。它可以帮助我们避免在组件重新渲染时创建新的函数实例。useCallback 接受两个参数:一个函数和一个依赖数组。当依赖数组中的值发生变化时,useCallback 会返回一个新的函数实例。否则,它将返回上一次创建的函数实例。

再看一个简单的例子:

importReact,{useCallback}from"react";functionButtoponent({onClick,children}){return<buttononClick={onClick}>{children}</button>;}functionParenponent(){consthandleClick=useCallback(()=>{console.log("Buttonclicked");},[]);return(<div><ButtoponentonClick={handleClick}>Clickme</Buttoponent></div>);}

在这个例子中,我们创建了一个名为 Buttoponent 的组件,它接受一个 onClick 函数属性。我们还创建了一个名为 Parenponent 的组件,它使用 useCallback 钩子来创建一个 handleClick 函数。当 Parenponent 重新渲染时,useCallback 会返回上一次创建的 handleClick 函数实例,避免了不必要的函数创建。

memo

memo 是一个用于优化性能的 React 高阶组件。它可以帮助我们避免在父组件重新渲染时重新渲染子组件。memo 接受一个组件作为参数,并返回一个新的组件。当新组件的属性发生变化时,它会重新渲染。否则,它将跳过渲染并返回上一次渲染的结果。

继续举例子:

importReact,{memo}from"react";constChilponent=memo(functionChilponent({text}){console.log("Chilponentrendered");return<div>{text}</div>;});functionParenponent({showChild}){return(<div>{showChild&&<Chilponenttext="Hello,world!"/>}<buttononClick={()=>setShowChild(!showChild)}>Togglechild</button></div>);}

在这个例子中,我们创建了一个名为 Chilponent 的组件,并使用 memo 高阶组件对其进行了优化。我们还创建了一个名为 Parenponent 的组件,它可以切换 Chilponent 的显示。当 Parenponent 重新渲染时,Chilponent 的属性没有发生变化,因此它不会重新渲染。

区别

用法都很清楚了,接下来总结一下它们之间的区别:

  • useMemo用于避免在组件重新渲染时执行昂贵的计算,只有在依赖发生变化时重新计算值。

  • useCallback用于避免在组件重新渲染时创建新的函数实例,只有在依赖发生变化时返回新的函数实例。

  • memo用于避免在父组件重新渲染时重新渲染子组件,只有在属性发生变化时重新渲染组件。

虽然这些功能都可以帮助我们优化性能,但它们的使用场景和工作原理有所不同。在实际开发中,需要因地制宜合理选用。

源码分析

为了更深入地了解 useMemouseCallbackmemo 的工作原理,我们将继续分析 React 18 的源码。我们将关注这些功能的核心逻辑,并详细解释它们的功能。

调度器

众所周知,在React hooks的体系中,每个钩子都有自己各个阶段的执行逻辑,并且存到对应的Dispatcher中。

就拿useMemo来举例:

//挂载时的调度器constHooksDispatcherOnMount:Dispatcher={//useMemo挂载时的执行函数useMemo:mountMemo,//otherhooks...};//数据更新时的调度器constHooksDispatcherOnUpdate:Dispatcher={//useMemo挂载时的执行函数useMemo:updateMemo,//otherhooks...};//其他生命周期调度器...

上面代码可以看出,useMemo 在挂载时执行了的是 mountMemo, 而在更新数据时执行的是 updateMemo。但为了更好了解 useMemouseCallbackmemo 的区别,我们只看更新部分就足够了。

useMemo 源码分析

源码在packages/react-reconciler/src/ReactFiberHooks.js 中可以找到:

functionupdateMemo<T>(nextCreate:()=>T,deps:Array<mixed>|void|null,):T{consthook=updateWorkInProgressHook();constnextDeps=deps===undefined?null:deps;constprevState=hook.memoizedState;//Assumethesearedefined.Ifthey'renot,areHookInputsEqualwillwarn.if(nextDeps!==null){constprevDeps:Array<mixed>|null=prevState[1];if(areHookInputsEqual(nextDeps,prevDeps)){returnprevState[0];}}if(shouldDoubleInvokeUserFnsInHooksDEV){nextCreate();}constnextValue=nextCreate();hook.memoizedState=[nextValue,nextDeps];returnnextValue;}

updateMemo 的实现中,有一个关键函数 areHookInputsEqual,它用于比较依赖项数组:

functionareHookInputsEqual(nextDeps:Array<mixed>,prevDeps:Array<mixed>|null,):boolean{if(__DEV__){if(ignorePreviousDependencies){//Onlytruewhenthis&nbspponentisbeinghotreloaded.returnfalse;}}if(prevDeps===null){if(__DEV__){console.error('%sreceivedafinalargumentduringthisrender,butnotduring'+'thepreviousrender.Eventhoughthefinalargumentisoptional,'+'itstypecannotchangebetweenrenders.',currentHookNameInDev,);}returnfalse;}if(__DEV__){//Don'tbother&nbspparinglengthsinprodbecausethesearraysshouldbe//passedinline.if(nextDeps.length!==prevDeps.length){console.error('Thefinalargumentpassedto%schangedsizebetweenrenders.The'+'orderandsizeofthisarraymustremainconstant.\n\n'+'Previous:%s\n'+'Iing:%s',currentHookNameInDev,`[${prevDeps.join(',')}]`,`[${nextDeps.join(',')}]`,);}}//$FlowFixMe[ipatible-use]foundwhenupgradingFlowfor(leti=0;i<prevDeps.length&&i<nextDeps.length;i++){//$FlowFixMe[ipatible-use]foundwhenupgradingFlowif(is(nextDeps[i],prevDeps[i])){continue;}returnfalse;}returntrue;}

areHookInputsEqual 函数接受两个依赖项数组 nextDepsprevDeps。它首先检查两个数组的长度是否相等,如果不相等,将在开发模式下发出警告。然后,它遍历数组并使用 is 函数(类似于 Object.is)逐个比较元素。如果发现任何不相等的元素,函数将返回 false。否则,返回 true

这个函数在 useMemo 的实现中起到了关键作用,因为它决定了是否需要重新计算值。如果依赖项数组相等,useMemo 将返回上一次计算的值;否则,它将执行 nextCreate 函数并返回一个新的值。

useCallback 源码分析

由于 useCallbackuseMemo 实现一致,其原理都是通过areHookInputsEqual 函数进行依赖项比对,区别在于 useMemo 返回是新数据对象,而 useCallback 返回是回调函数。源码如下:

functionupdateCallback<T>(callback:T,deps:Array<mixed>|void|null):T{consthook=updateWorkInProgressHook();constnextDeps=deps===undefined?null:deps;constprevState=hook.memoizedState;if(nextDeps!==null){constprevDeps:Array<mixed>|null=prevState[1];if(areHookInputsEqual(nextDeps,prevDeps)){returnprevState[0];}}hook.memoizedState=[callback,nextDeps];returncallback;}

memo 源码分析

memo 的实现中,有一个关键函数 updateMemponent,它用于更新 memo 组件。这个函数位于 packages/react-reconciler/src/ReactFiberBeginWork.js 文件中:

functionupdateMemponent(current:Fiber|null,workInProgress:Fiber,&nbspponent:any,nextProps:any,updateLanes:Lanes,renderLanes:Lanes,):null|Fiber{if(current!==null){//...constprevProps=current.memoizedProps;const&nbsppare=&nbspponentpare;const&nbsppareFn=&nbsppare!==null?&nbsppare:shallowEqual;ifpareFn(prevProps,nextProps)){returnbailoutOnAlreadyFinishedWork(current,workInProgress,renderLanes,);}}//...renderthe&nbspponentandreturntheresult}

updateMemponent 函数首先检查当前组件是否具有上一次的属性 prevProps。如果存在,它将获取 memo 组件的比较函数 <codepare。如果没有提供比较函数,React 将使用默认的浅比较函数 shallowEqual

接下来,React 使用比较函数来检查上一次的属性 prevProps 是否与新的属性 nextProps 相等。如果相等,React 将调用 bailoutOnAlreadyFinishedWork 函数来阻止组件重新渲染。否则,它将继续渲染组件并返回结果。

bailoutOnAlreadyFinishedWork 函数的实现位于同一个文件中,它的核心逻辑如下:

functionbailoutOnAlreadyFinishedWork(current:Fiber|null,workInProgress:Fiber,renderLanes:Lanes,):null|Fiber{if(current!==null){//ReusepreviousdependenciesworkInProgress.dependencies=current.dependencies;}//...somecode//Checkifthechildrenhaveanypendingworkif((workInProgress.childLanes&renderLanes)!==NoLanes){//...somecode}else{//Thechildrendon'thaveanywork.Setthebailoutstate.workInProgress.lanes=NoLanes;workInProgress.childLanes=NoLanes;returnnull;}//...somecode}

bailoutOnAlreadyFinishedWork 函数首先复用上一次的依赖项。然后,它检查子组件是否有任何待处理的工作。如果没有,它将设置 workInProgress.lanesworkInProgress.childLanesNoLanes,并返回 null,从而阻止组件重新渲染。

“React18中useMemo、useCallback和memo怎么使用”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注主机评测网网站,小编将为大家输出更多高质量的实用文章!


上一篇:Python怎么实现希尔伯特变换

下一篇:Python中深浅拷贝的使用及注意事项是什么


Copyright © 2002-2019 测速网 www.inhv.cn 皖ICP备2023010105号
测速城市 测速地区 测速街道 网速测试城市 网速测试地区 网速测试街道
温馨提示:部分文章图片数据来源与网络,仅供参考!版权归原作者所有,如有侵权请联系删除!

热门搜索 城市网站建设 地区网站制作 街道网页设计 大写数字 热点城市 热点地区 热点街道 热点时间 房贷计算器