欢迎来到皮皮网网首页

【肤源码冻干粉】【易语言视频聊天源码】【工程项目管理软件源码】useeffect源码解读

来源:conv函数 源码 时间:2024-11-25 09:38:04

1.解读useEffect和useLayouEffect原理
2.React组件设计-仿网易有道翻译主页
3.从根上理解ReactHooks的码解闭包陷阱
4.解读useEffect和useLayouEffect原理
5.大家都能看得懂的源码 - ahooks 是怎么处理 DOM 的?
6.slate.js源码分析(一) —— slate渲染机制

useeffect源码解读

解读useEffect和useLayouEffect原理

       èƒŒæ™¯

       å†™è¿™ç¯‡æ–‡ç« æ˜¯å› ä¸ºå·¥ä½œä¸Šä¸æ˜¯éžå¸¸ç¹å¿™ï¼Œå¯ä»¥æŠ½ç©ºå­¦ä¹ è‡ªå·±å¸¸ç”¨æ¡†æž¶å’Œç±»åº“,深入理解它们,在技术上希望有更大的进步,培养学习兴趣;

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的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/

React组件设计-仿网易有道翻译主页

       前言

       React组件化开发非常有利于搭建项目,也提高了组件的码解复用性。由于频繁使用网易有道翻译这个软件,码解让我萌生出想要征服ta的码解冲动。开发过程中遇到了些许问题,码解页面还有很多功能还未完善,码解肤源码冻干粉现在只有一个首页,码解后续功能持续完善中。码解

前期准备

       在组件页面成型之初需要几个开源组件库:

       axios:它是码解一个基于promise的网络请求库,用于获取后端数据(fastmock网站可以让你在没有后端程序的码解情况下能真实地在线模拟ajax请求),是码解前端常用的数据请求工具;

       antd-mobile:由蚂蚁金融团队推出的一个开源的react组件库,这个组件库拥有很多使用的组件;

       swiper:能实现触屏焦点图、触屏Tab切换、码解触屏轮播图切换等常用效果。码解

       styled-compenonts:真正的码解cssinjs,增强CSS以对React组件系统进行样式设置的码解结果,具有简单的动态样式、轻松维护等优点。

正文

       组件展示?

组件设计思路

       顶部:用flex布局,方便快捷(一切皆可flex)

       搜索栏:使用antd-mobile组件库的SearchBar,点击转跳到搜索页面

       图标轮播和轮播图:主要使用swiper进行设计,实现自动轮播效果

       底部栏:用fixed固定住

组件封装

       先对项目进行脚手架的建构(使用vite脚手架,使用起来快速方便)

npminit@viteja/app

       src下的目录内容

       api:存放与数据相关的链接,组件所有的数据将会在这一个文件夹下的request.js中使用ajax进行数据请求

       assets:存放静态资源,font、image等

       components:放置重复使用的组件

       config:存放页面标题配置

       modules:配置页面自适应横竖屏

       pages:各个页面

       routes:页面的路由

搜索栏

       直接使用antd-mobile的SearchBar

importReactfrom'react'import{ SearchBar}from'antd-mobile'import{ Link}from'react-router-dom'import{ Wrapper}from'./style'

       exportdefaultfunctionSearch(){ return(

{ /*点击搜索框跳转搜索页面*/}

       )}```

数据请求

       前端页面数据的展示不能写死在代码里面,需要数据请求,fastmock则走入了我的视野,在线接口Mock工具fastmock?在线模拟ajax请求(fastmock在没有后端程序的情况下可以实现ajax请求,有需要的小伙伴可以去尝试)

       api文件夹下的request.js进行axios数据请求

importaxiosfrom'axios'exportconstgetBanners=()=>axios.get('/post/

从根上理解ReactHooks的闭包陷阱

       现在开发React组件基本都是用hooks了,hooks很方便,但一不注意也会遇到闭包陷阱的坑。

       相信很多用过hooks的易语言视频聊天源码人都遇到过这个坑,今天我们来探究下hooks闭包陷阱的原因和怎么解决吧。

       首先这样一段代码,大家觉得有问题没:

import?{ ?useEffect,?useState?}?from?'react';function?Dong()?{ const?[count,setCount]?=?useState(0);useEffect(()?=>?{ setInterval(()?=>?{ setCount(count?+?1);},?);},?[]);useEffect(()?=>?{ setInterval(()?=>?{ console.log(count);},?);},?[]);return?<div>guang</div>;}export?default?Dong;

       用useState创建了个count状态,在一个useEffect里定时修改它,另一个useEffect里定时打印最新的count值。

       我们跑一下:

       打印的并不是我们预期的0、1、2、3,而是0、0、0、0,这是为什么呢?

       这就是所谓的闭包陷阱。

       首先,我们回顾下hooks的原理:hooks就是在fiber节点上存放了memorizedState链表,每个hook都从对应的链表元素上存取自己的值。

       比如上面useState、useEffect、useEffect的3个hook就对应了链表中的3个memorizedState:

       然后hook是存取各自的那个memorizedState来完成自己的逻辑。

       hook链表有创建和更新两个阶段,也就是mount和update,第一次走mount创建链表,后面都走update。

       比如useEffect的实现:

       特别要注意deps参数的处理,如果deps为undefined就被当作null来处理了。

       那之后又怎么处理的呢?

       会取出新传入的deps和之前存在memorizedState的deps做对比,如果没有变,就直接用之前传入的那个函数,否则才会用新的函数。

       deps对比的工程项目管理软件源码逻辑很容易看懂,如果是之前的deps是null,那就返回false也就是不相等,否则遍历数组依次对比:

       所以:

       如果useEffect第二个参数传入undefined或者null,那每次都会执行。

       如果传入了一个空数组,只会执行一次。

       否则会对比数组中的每个元素有没有改变,来决定是否执行。

       这些我们应该比较熟了,但是现在从源码理清了。

       同样,useMemo、useCallback等也是同样的deps处理:

       理清了useEffect等hook是在哪里存取数据的,怎么判断是否执行传入的函数的之后,再回来看下那个闭包陷阱问题。

       我们是这样写的:

useEffect(()?=>?{ const?timer?=?setInterval(()?=>?{ setCount(count?+?1);},?);},?[]);useEffect(()?=>?{ const?timer?=?setInterval(()?=>?{ console.log(count);},?);},?[]);

       deps传入了空数组,所以只会执行一次。

       对应的源码实现是这样的:

       如果是需要执行的effect会打上HasEffect的标记,然后后面会执行:

       因为deps数组是空数组,所以没有HasEffect的标记,就不会再执行。

       我们知道了为什么只执行一次,那只执行一次有什么问题呢?定时器确实只需要设置一次呀?

       定时器确实只需要设置一次没错,但是在定时器里用到了会变化的state,这就有问题了:

       deps设置了空数组,那多次render,只有第一次会执行传入的函数:

       但是state是变化的呀,执行的那个函数却一直引用着最开始的state。

       怎么解决这个问题呢?

       每次state变了重新创建定时器,用新的state变量不就行了:

       也就是这样的:

import?{ ?useEffect,?useState?}?from?'react';function?Dong()?{ const?[count,setCount]?=?useState(0);useEffect(()?=>?{ setInterval(()?=>?{ setCount(count?+?1);},?);},?[count]);useEffect(()?=>?{ setInterval(()?=>?{ console.log(count);},?);},?[count]);return?<div>guang</div>;}export?default?Dong;

       这样每次count变了就会执行引用了最新count的函数了:

       现在确实不是全0了,但是unity3d棋牌游戏源码这乱七八遭的打印是怎么回事?

       那是因为现在确实是执行传入的fn来设置新定时器了,但是之前的那个没有清楚呀,需要加入一段清除逻辑:

import?{ ?useEffect,?useState?}?from?'react';function?Dong()?{ const?[count,setCount]?=?useState(0);useEffect(()?=>?{ const?timer?=?setInterval(()?=>?{ setCount(count?+?1);},?);return?()?=>?clearInterval(timer);},?[count]);useEffect(()?=>?{ const?timer?=?setInterval(()?=>?{ console.log(count);},?);return?()?=>?clearInterval(timer);},?[count]);return?<div>guang</div>;}export?default?Dong;

       加上了clearInterval,每次执行新的函数之前会把上次设置的定时器清掉。

       再试一下:

       现在就是符合我们预期的了,打印0、1、2、3、4。

       很多同学学了useEffect却不知道要返回一个清理函数,现在知道为啥了吧。就是为了再次执行的时候清掉上次设置的定时器、事件监听器等的。

       这样我们就完美解决了hook闭包陷阱的问题。

总结

       hooks虽然方便,但是也存在闭包陷阱的问题。

       我们过了一下hooks的实现原理:

       在fiber节点的memorizedState属性存放一个链表,链表节点和hook一一对应,每个hook都在各自对应的节点上存取数据。

       useEffect、useMomo、useCallback等都有deps的参数,实现的时候会对比新旧两次的deps,如果变了才会重新执行传入的函数。所以undefined、null每次都会执行,[]只会执行一次,[state]在state变了才会再次执行。

       闭包陷阱产生的原因就是useEffect等hook里用到了某个state,但是没有加到deps数组里,这样导致state变了却没有执行新传入的一千零一夜秒赞源码函数,依然引用的之前的state。

       闭包陷阱的解决也很简单,正确设置deps数组就可以了,这样每次用到的state变了就会执行新函数,引用新的state。不过还要注意要清理下上次的定时器、事件监听器等。

       要理清hooks闭包陷阱的原因是要理解hook的原理的,什么时候会执行新传入的函数,什么时候不会。

       hooks的原理确实也不难,就是在memorizedState链表上的各节点存取数据,完成各自的逻辑的,唯一需要注意的是deps数组引发的这个闭包陷阱问题。

解读useEffect和useLayouEffect原理

       背景

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

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的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操作场景,内部进行封装处理,使用户能快速上手并灵活运用。

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

slate.js源码分析(一) —— slate渲染机制

       富文本编辑器中的可见内容主要由文档内容和光标两部分组成。本文将详细介绍Slate在文档内容和光标方面的渲染机制。

       Slate文档的结构包含元素(Element)和文本(Text)两类节点。这些节点类似于DOM树,可以嵌套结构。用户在元素或文本上添加扩展属性,以提供渲染节点所需的数据。

       文档的截图与对应的Slate值之间存在对应关系,这种关系帮助开发者直观理解文档的渲染过程。

       Slate组件树类似于DOM树,对应于Slate值的数据结构。文档区域的顶部负责更新选择数据、文档树内容,并提供DOM事件API(如onKeydown和onClick)。

       节点数据被渲染为HTML,允许用户自定义渲染过程,通过renderElement方法实现。根据装饰的不同,文本会被分割成相应数量的leaf。

       文本内容的渲染则通过renderLeaf方法来控制文本内容的样式。

       Slate值的更新逻辑利用React技术,将文档数据实时渲染为DOM结构。当contenteditable为true的元素被修改时,会触发beforInput事件,通过监听这一事件,实现文档内容的实时同步。

       在使用Slate时,输入法问题是一个常见挑战。本文将简要介绍输入法的工作原理及其常见bug,并分析解决方法。

       正常键盘输入仅触发beforInput事件,而使用输入法时,除了beforInput事件,还会触发Composition事件。这三个事件分别对应输入法开始、内容更新和结束的过程。在输入法输入期间,如果实时修改文档内容,会导致与输入法冲突。因此,在CompositionUpdate期间,Slate Value不会做任何更新,直至CompositionEnd时再进行更新。遇到报错情况时,通常是因为在CompositionStart时文档内容被删除,而在CompositionEnd时找不到对应的DOM节点,引发错误。解决办法是在CompositionStart时更新文档值以避免冲突。

       解决输入法问题的一个方案是fork源码。通过这种方式,可以确保Slate与输入法协同工作,提高用户体验。

       Slate Selection数据结构与DOM Selection类似,由锚点(anchor)和焦点(focus)两个点组成。了解详细信息可以参考MDN Selection文档。

       Selection的更新机制依赖于React完成渲染。在每次Selection值发生变化时,会在useEffect中更新DOMSelection。同时,监听window.document上的selectionchange事件以更新Slate Selection值。

       后续计划继续深入探讨Slate源码分析,包括历史记录机制、从Slate 0.升级到0.的实战指南、数据模型、序列化机制、normalize机制等,敬请期待。

       最后,附上招聘广告。百度如流团队正面向北京、上海、深圳等地招聘,提供丰富的岗位选择,欢迎有意者进行内推。