1.React组件设计-仿网易有道翻译主页
2.解读useEffectåuseLayouEffectåç
3.大家都能看得懂的源码 - ahooks 是怎么处理 DOM 的?
4.slate.js源码分析(一) —— slate渲染机制
React组件设计-仿网易有道翻译主页
前言
React组件化开发非常有利于搭建项目,也提高了组件的复用性。由于频繁使用网易有道翻译这个软件,让我萌生出想要征服ta的冲动。开发过程中遇到了些许问题,页面还有很多功能还未完善,台湾28源码现在只有一个首页,后续功能持续完善中。
前期准备在组件页面成型之初需要几个开源组件库:
axios:它是一个基于promise的网络请求库,用于获取后端数据(fastmock网站可以让你在没有后端程序的情况下能真实地在线模拟ajax请求),是前端常用的数据请求工具;
antd-mobile:由蚂蚁金融团队推出的一个开源的react组件库,这个组件库拥有很多使用的组件;
swiper:能实现触屏焦点图、触屏Tab切换、触屏轮播图切换等常用效果。
styled-compenonts:真正的源码交流分享cssinjs,增强CSS以对React组件系统进行样式设置的结果,具有简单的动态样式、轻松维护等优点。
正文组件展示?
组件设计思路顶部:用flex布局,方便快捷(一切皆可flex)
搜索栏:使用antd-mobile组件库的SearchBar,点击转跳到搜索页面
图标轮播和轮播图:主要使用swiper进行设计,实现自动轮播效果
底部栏:用fixed固定住
组件封装先对项目进行脚手架的建构(使用vite脚手架,使用起来快速方便)
npminit@viteja/appsrc下的目录内容
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则走入了我的scrapy源码在哪视野,在线接口Mock工具fastmock?在线模拟ajax请求(fastmock在没有后端程序的情况下可以实现ajax请求,有需要的小伙伴可以去尝试)
api文件夹下的request.js进行axios数据请求
importaxiosfrom'axios'exportconstgetBanners=()=>axios.get('/post/解读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机制等,敬请期待。
最后,附上招聘广告。百度如流团队正面向北京、上海、深圳等地招聘,提供丰富的岗位选择,欢迎有意者进行内推。