皮皮网
皮皮网

【勇ol源码】【拷贝源码 离职】【复制静态源码】ahooks源码

来源:locale 源码 发表时间:2024-11-30 00:58:26

1.一文读懂 react-router 原理
2.解读useEffect和useLayouEffect原理
3.大家都能看得懂的源码源码 - ahooks 是怎么处理 DOM 的?
4.react 中的网络请求库
5.实现一个自定义 React Hook:useLocalStorageState

ahooks源码

一文读懂 react-router 原理

       react-router是react生态系统中关键的一部分,它帮助我们管理URL以及实现页面组件间的源码切换。本文将深入react-router源码,源码探究其工作原理。源码

       在开发中,源码我们通常不会直接使用react-router的源码勇ol源码核心API,而是源码从react-router-dom中导出所有API和组件。此外,源码还有我们不直接接触的源码history库,它们共同构成了完整的源码router功能,它们之间的源码关系如下:

       这就是三个关键模块间的关系。

       react-router:基于Context的源码全局状态下发

       router是一个“Provider-Consumer”模型,你在最外层提供一个Provider,源码在内部任意位置都可以用Consumer接到数据。源码显然,源码这里使用了React.Context。

       这里下发了两个Context:RouterContext、HistoryContext,都是隔壁模块导入的单例,所以一个项目中只能用一套react-router。那这两个有什么不同?各自下发了哪些状态?

       RouterContext

       RouterContext下发一个对象,主要包含三个信息:

       其中,拷贝源码 离职history来自history库提供的统一API,包括history的读取、操作、订阅等。

       location来自Router的一个状态,Router会在mount的时候监听history,并在改变时更新location:

       match用来描述当前Route对URL的匹配信息。Router来自Class的静态方法,写死了根路由的信息。

       HistoryContext

       HistoryContext更简单,直接下发了history:value={ this.props.history}。但是HistoryContext为什么要单独给呢?

       总结下来,Router的结构如下:

       :路由递归

       Route用来匹配路由,特性如下:

       根据当前路由匹配规则渲染对应组件

       首先Route要判断自己是否匹配:

       Route嵌套的实现

       为了实现Route套Route,Route每次渲染都会重建一个RouterContext.Provider,把值更新为当前Route下计算后的router信息。

       总结如下:

       matchPath方法细节

       最后打开matchPath方法看细节:

       组件取值

       组件可能需要哪些值?

       可能通过哪些方式拿到?

       消费Context的方式显然更通用,因此react-router的消费实现大多用这种方式。

       withRouter()

       在没hook前,withRouter是我们取route信息的主要方式。它是复制静态源码个简单的HOC:

       hooks

       有hook后,react-router提供了几个hook,也都是基于useContext来做的。

       其他路由组件

       react-router还提供了一些其他组件来丰富调用方式,举个的例子看看。

       react-router-dom组件: 和

       只是在用不同的history调:

       history接口

       主要几个信息:

       实现思路

       如图:

       模块划分:

       主要调用逻辑:

       createBrowserHistory和createHashHistory的差异

       两个方法向外暴露的接口完全一样,为了抹平差异,实现上做了如下两点适配:

       1、location属性计算

       createBrowserHistory下,location中的pathname, search, hash直接来自于window.location。

       createHashHistory下则都是从#后的hash中解析出来的,比如hash部分是#/a/b?c=1#/d,解析出{ hash: '#/d', search: '?c=1', pathname: '/a/b'}。

       2、event listener事件监听

       createBrowserHistory只需监听popstate,而createHashHistory还要监听hashchange,而且这里要判断下前后location是否相等,因为hashchange可能是无效的。

       小结

解读useEffect和useLayouEffect原理

       背景

       写这篇文章是因为工作上不是非常繁忙,可以抽空学习自己常用框架和类库,深入理解它们,在技术上希望有更大的xargs命令源码进步,培养学习兴趣;

useEffect

       和其它hooks一样,加载和更新执行不一样的方法(mountEffect和updateEffect);

1.mountEffect

       页面加载时,执行mountEffect;

       创建hook对象,加入组件的hook单向链表;

       在组件的fiber的flag中加入副作用相关的effectTag;(加载期间默认有layoutEffect和effect的副作用)

       创建effect对象,给hook对象的memoizedState和加入组件fiber的updateQueue中形成effect环状链表;在渲染工作完成后,会循环这个环状链表,执行每个effect对象的destory和create;

consteffect={ tag,create,destroy,deps,next:null};tag是effect的类型tag为9是useEffect,5是useLayoutEffectcreate是useEffect或useLayoutEffect的回调函数destroy是create返回的回调函数deps是useEffect或useLayoutEffect的依赖数组next指向下个effect对象;1.1.effect环状链表图functionpushEffect(tag,create,destroy,deps){ consteffect={ tag,create,destroy,deps,next:null};//新创建的effect对象为最后为effect链表的一个effect对象,componentUpdateQueue.lastEffect会指向新创建的effect对象//新创建的effect对象的next会指向第一个effct对象;letcomponentUpdateQueue=(currentlyRenderingFiber.updateQueue);if(componentUpdateQueue===null){ //当前没有updateQueuecomponentUpdateQueue=createFunctionComponentUpdateQueue();//创建updateQueuecurrentlyRenderingFiber.updateQueue=componentUpdateQueue;//形成一个环状链表componentUpdateQueue.lastEffect=effect.next=effect}else{ constlastEffect=componentUpdateQueue.lastEffect;if(lastEffect===null){ componentUpdateQueue.lastEffect=effect.next=effect;}else{ //第一个effect对象为最先创建的的effect对象constfirstEffect=lastEffect.next;//获取第一个effect对象lastEffect.next=effect;//旧的最后一个effect对象的next,指向新创建的effecteffect.next=firstEffect;//新创建的effect对象的next指向第一个effectcomponentUpdateQueue.lastEffect=effect;//updateQueue的lastEffect指向effect,新创建的effect变为最后一个effect对象}}returneffect;}2.updateEffect

       页面更新时,执行updateEffect;

       根据hook单向链表获取对应的更新时的hook对象,创建新的hook对象,加入hook单向链表;

       如果effect的deps不为null,或者undefined,会从当前hook对象拿到上一次effect对象,再从effect对象拿到deps和destroy,用新的deps与之比较;

       如果新老deps相等,push一个不带HookHasEffect的tag给effect对象,加入updateQueue环状链表(这个effect不会被标记为有副作用,所以,effect的cad标注源码create和destroy不会被执行),不更新hook.memoizedState;

       如果新老deps不相等,更新effect对象,在effect的tag中加入HookHasEffect和上一次create执行的destroy,更新hook.memoizedState;

3.useEffct的回调函数和销毁函数的执行时机

       在render时期构建effect链表;在commit时执行先执行之前没有执行完的useEffect,然后,在beforeMutation阶段操作dom前,以NormalPriority常规优先级添加一个异步任务到任务队列(这个异步任务是用来执行useEffect的destroy和create的),在layout阶段完成,页面完成渲染后,执行在beforeMutation阶段添加的异步任务;

3.1.commit开始时

       主要是为了执行之前没有执行的useEffect

       进入commit阶段,这和useEffect异步调度的特点有关,它以一般的优先级被调度,意味着一旦有更高优先级的任务进入到commit阶段,上一次任务的useEffect还没得到执行。所以在本次更新开始前,需要先将之前的useEffect都执行掉,以保证本次调度的useEffect都是本次更新产生的。

functioncommitRootImpl(root,recoverableErrors,renderPriorityLevel){ do{ //`flushPassiveEffects`willcall`flushSyncUpdateQueue`attheend,which//means`flushPassiveEffects`willsometimesresultinadditional//passiveeffects.Soweneedtokeepflushinginaloopuntilthereare//nomorependingeffects.//TODO:Mightbebetterif`flushPassiveEffects`didnotautomatically//flushsynchronousworkattheend,toavoidfactoringhazardslikethis.flushPassiveEffects();}while(rootWithPendingPassiveEffects!==null);...省略代码}3.2.beforeMutation

       只会发起一次useEffect调度,是异步调度,以NormalPriority常规优先级添加一个异步任务在任务队列中(push(timerQueue,newTask)),在页面渲染完成时,会执行这个异步任务

functioncommitRootImpl(root,recoverableErrors,renderPriorityLevel){ ...省略代码if((finishedWork.subtreeFlags&PassiveMask)!==NoFlags||(finishedWork.flags&PassiveMask)!==NoFlags){ if(!rootDoesHavePassiveEffects){ rootDoesHavePassiveEffects=true;scheduleCallback$1(NormalPriority,function(){ //添加一个异步任务到任务队列flushPassiveEffects();//Thisrendertriggeredpassiveeffects:releasetherootcachepool//*after*passiveeffectsfiretoavoidfreeingacachepoolthatmay//bereferencedbyanodeinthetree(HostRoot,Cacheboundaryetc)returnnull;});}}...省略代码}3.3.layout

       加载时,只执行useEffect的create函数即可;

       如果pendingPassiveEffectsLanes是同步赛道,就在页面渲染完直接执行useEffect的create和destroy,在beforeMutation时添加的异步任务,不会执行useEffect的create和destory

if(includesSomeLane(pendingPassiveEffectsLanes,SyncLane)&&root.tag!==LegacyRoot){ //加载期间默认是不走这里的//这里也是执行useEffect的create,如果pendingPassiveEffectsLanes是同步赛道,//就在渲染完成后直接执行useEffect的create和destory//在beforeMutation时添加的异步任务执行时,不会执行useEffect的create和destoryflushPassiveEffects();}

       执行上一次useEffect的create返回的destroy,拿到函数组件fiber的updateQueue,循环这个effect环状链表,拿到effect对象的destroy执行;

functioncommitHookEffectListUnmount(flags,finishedWork,nearestMountedAncestor){ varupdateQueue=finishedWork.updateQueue;varlastEffect=updateQueue!==null?updateQueue.lastEffect:null;if(lastEffect!==null){ varfirstEffect=lastEffect.next;vareffect=firstEffect;do{ if((effect.tag&flags)===flags){ //Unmountvardestroy=effect.destroy;effect.destroy=undefined;if(destroy!==undefined){ { if((flags&Passive$1)!==NoFlags$1){ markComponentPassiveEffectUnmountStarted(finishedWork);}elseif((flags&Layout)!==NoFlags$1){ markComponentLayoutEffectUnmountStarted(finishedWork);}}safelyCallDestroy(finishedWork,nearestMountedAncestor,destroy);//执行destroy{ if((flags&Passive$1)!==NoFlags$1){ markComponentPassiveEffectUnmountStopped();}elseif((flags&Layout)!==NoFlags$1){ markComponentLayoutEffectUnmountStopped();}}}}effect=effect.next;}while(effect!==firstEffect);}}

       执行完所有组件的destroy,再执行create;同理,也是拿到函数组件fiber的updateQueue,循环这个effect环状链表,拿到effect对象的create执行,然后把create返回的destroy给effect对象(留着下着更新执行useEffect时用);

functioncommitHookEffectListMount(flags,finishedWork){ varupdateQueue=finishedWork.updateQueue;varlastEffect=updateQueue!==null?updateQueue.lastEffect:null;if(lastEffect!==null){ varfirstEffect=lastEffect.next;vareffect=firstEffect;do{ if((effect.tag&flags)===flags){ { if((flags&Passive$1)!==NoFlags$1){ markComponentPassiveEffectMountStarted(finishedWork);}elseif((flags&Layout)!==NoFlags$1){ markComponentLayoutEffectMountStarted(finishedWork);}}//Mountvarcreate=effect.create;effect.destroy=create();{ if((flags&Passive$1)!==NoFlags$1){ markComponentPassiveEffectMountStopped();}elseif((flags&Layout)!==NoFlags$1){ markComponentLayoutEffectMountStopped();}}{ vardestroy=effect.destroy;if(destroy!==undefined&&typeofdestroy!=='function'){ varhookName=void0;if((effect.tag&Layout)!==NoFlags){ hookName='useLayoutEffect';}elseif((effect.tag&Insertion)!==NoFlags){ hookName='useInsertionEffect';}else{ hookName='useEffect';}varaddendum=void0;if(destroy===null){ addendum='Youreturnednull.Ifyoureffectdoesnotrequireclean'+'up,returnundefined(ornothing).';}elseif(typeofdestroy.then==='function'){ addendum='\n\nItlookslikeyouwrote'+hookName+'(async()=>...)orreturnedaPromise.'+'Instead,writetheasyncfunctioninsideyoureffect'+'andcallitimmediately:\n\n'+hookName+'(()=>{ \n'+'asyncfunctionfetchData(){ \n'+'//Youcanawaithere\n'+'constresponse=awaitMyAPI.getData(someId);\n'+'//...\n'+'}\n'+'fetchData();\n'+"},[someId]);//Or[]ifeffectdoesn'tneedpropsorstate\n\n"+'LearnmoreaboutdatafetchingwithHooks:/post/

大家都能看得懂的源码 - ahooks 是怎么处理 DOM 的?

       深入浅出ahooks源码系列文章之十三,完整文档地址如下。

       本文主要探讨ahooks在处理DOM类Hooks时的规范及源码实现。

       ahooks中的大部分DOM类Hooks会接收一个名为target的参数,用于表示要处理的元素。target可以接受三种类型:React.MutableRefObject(通过`useRef`保存的DOM)、`HTMLElement`、或者函数(用于SSR场景)。

       目标元素支持动态变化,这在实际应用中是常见的需求。

       ahooks通过`useTargetElement`方法实现目标元素的获取,兼容第一点的参数规范。

       `useEffectWithTarget`和`useLayoutEffectWithTarget`是针对第二点,支持target动态变化的实现,分别调用`createEffectWithTarget`函数。

       在`packages/hooks/src/utils/useEffectWithTarget.ts`和`packages/hooks/src/utils/useLayoutEffectWithTarget.ts`中,`useEffect`和`useLayoutEffect`被调用,它们在内部封装处理逻辑。

       `createEffectWithTarget`是核心函数,用于创建相应的副作用效果。

       总结,ahooks通过规范的输入输出,支持丰富的DOM操作场景,内部进行封装处理,使用户能快速上手并灵活运用。

       本文已收录至个人博客,欢迎关注。

react 中的网络请求库

       在React开发中,网络请求库的选择显得尤为重要。本文旨在探讨几种常用的网络请求库,以及它们在React项目中的应用和区别。

       首先,我们关注的是`@umijs/plugin-request`。这一插件是基于`umi-request`和`ahooks`的`useRequest`构建的,提供了一套统一的网络请求和错误处理方案。在项目中,通过引入`umi-request`,可以利用其强大的功能处理各种网络请求,而`ahooks`中的`useRequest`则提供了简洁易用的Hooks方式来管理网络请求。

       `umi-request`作为核心组件,其源码位于`github.com/umijs/umi-request`。它的设计旨在简化网络请求的处理,包括请求的发起、响应的解析以及错误的捕捉等。而`ahooks`,一个由阿里团队维护的库,通过`useRequest`实现了高阶函数与React Hooks的结合,使得开发者能够以更优雅的方式管理网络请求。

       进一步,我们探讨`@ahooksjs/use-request`。这一组件是`ahooks`提供的用于实现网络请求的钩子函数,其源码位于`github.com/alibaba/hook/use-request`。通过`useRequest`,开发者可以方便地在React组件中执行异步操作,比如发送HTTP请求,并在请求完成时处理响应数据。

       在实际应用中,引入`@umijs/plugin-request`意味着你将获得一套完整的网络请求解决方案。这一插件的源码位于`github.com/umijs/plugin-request`,其文档则在`umijs.org/plugins/plugin-request`上提供了详细的使用指导和示例。通过`@umijs/plugin-request`,开发者能够轻松地在React应用中执行网络请求,同时享受到统一的错误处理机制,确保应用在面对网络异常时能够保持稳定。

       综上所述,`@umijs/plugin-request`基于`umi-request`和`ahooks`的`useRequest`,为React开发者提供了一套功能强大且易于使用的网络请求和错误处理方案。通过合理选择和应用这些库,可以显著提高React应用的开发效率和用户体验。

实现一个自定义 React Hook:useLocalStorageState

       实现一个自定义 React Hook:useLocalStorageState

       在需求中,需要将数据保存到 localStorage,并在组件初始化时获取,修改时保存到本地。

       创建一个名为 useLocalStorageState 的 Hook,封装读写逻辑。

       此实现简单,但支持仅字符串格式,需手动序列化和反序列化以扩展数据类型。

       增加序列化和反序列化支持,以适应不同数据类型。

       扩展序列化反序列化方法,提供自定义序列化反序列化选项。

       考虑使用成熟的第三方库,如 ahooks,其提供现成的 useLocalStorageState 实现。

       ahooks 的 useLocalStorageState 源码可供参考和学习。

相关栏目:探索