热门推荐
-
Vite6+React18+Ts项目-04.使用zustand状态管理React1.简单使用安装zustandnpminstallzustand创建用来保存状态的store.ts文件import{create}from'zustand'typeState={count:number}typeActions={increment:(qty:number)=>void}constuseCountStore=create((set)=>({count:0,increment:(qty:number)=>set((state)=>({count:state.count+qty})),}))在app.ts组件中使用functionApp(){constcount=useCountStore((state)=>state.count)constincrement=useCountStore((state)=>state.increment)return(increment(1)}>增加当前值:{count})}2.一个保存用户语言的例子store.ts下面的代码使用了两个中间件immer:更加方便的操作对象,改变对象的值时,可直接操作对象persist:持久化存储,将对象存储到localStorageimport{create}from'zustand'import{createJSONStorage,persist,subscribeWithSelector}from'zustand/middleware';import{immer}from'zustand/middleware/immer';//定义类型interfaceLang{code:string;name:string;}//定义初始值constinit:Lang={code:'zh-CN',name:'中文简体',}//存储store的keyconststoreKey='store-lang';//创建一个storeconstuseLangStore=create()(immer(persist(()=>({...init}),{//持久化时存储到localStorage的keyname:storeKey,//存储方式,默认localStoragestorage:createJSONStorage(()=>localStorage),},)))exportdefaultuseLangStore;//设置语言exportconstsetLang=(state:Lang)=>{useLangStore.setState(state);}//重置语言exportconstresetLang=()=>{useLangStore.setState(init);}//获取语言编码,通过这种方法获取,在组件里不会同步刷新状态,即当code改变时,不会触发组件渲染exportconstgetLangCodee=():string=>{returnuseLangStore.getState().code;}app.tsimport{useState}from'react'import'./app.css'importuseLangStore,{setLang}from'./stores/lang/lang.store'functionApp(){const[inputLang,setInputLang]=useState('en-Es')//如果同时获取多个值可使用useShallowconstlangCode=useLangStore((state)=>state.code)constsetLangData=()=>{setLang({code:inputLang,name:inputLang});}return(设置语言setInputLang(e.target.value)}/>当前语言编码:{langCode})}exportdefaultApp3.监听localStorage变化通过监听localStorage的变化,可以使用当一个页签的内容发送改变时,其它页签的store也会发生改变//监听localStorage变化window.addEventListener('storage',(event)=>{if(event.key===storeKey&&event.newValue!==null){constval=JSON.parse(event.newValue);//这里判断用户状态,如果用户变化了,则进入登录页面或进入首页,根据具体业务自行处理if(JSON.stringify(useLangStore.getState())!=JSON.stringify(val.state)){useLangStore.setState(val.state);}}});4.使用useShallow的例子可以更加方便的同时获取多个值,而不会因为其它未使用到值的改变触发组件渲染使用前constlangCode=useLangStore((state)=>state.code)constlangName=useLangStore((state)=>state.name)使用后const{code,name}=useLangStore(useShallow((state)=>({code:state.code,name:state.name})))5.使用订阅的例子可优化组件渲染次数,如下示例,只有达到一定条件才会触发渲染,如果是在外层直接取出state的值,则state的值每次改变都会触发渲染useEffect(()=>{returnuseCountStore.subscribe((state,prevState)=>{if(state.count>=7&&prevState.count=7&&prevState.countdevtools->subscribeWithSelector->persist本文未使用到devtools,devtools中间件可以帮助你在开发过程中调试和检查应用程序的状态变化,因为我们将状态存储到了localStorage,可直接通过浏览器控制台查看,故无需使用该中间件示例constuseCountStore=create()(immer(subscribeWithSelector(persist(()=>({...init}),{//持久化时存储到localStorage的keyname:storeKey,//存储方式,默认localStoragestorage:createJSONStorage(()=>localStorage),},))))用法useEffect(()=>{returnuseCountStore.subscribe(state=>state.count,(count,prevCount)=>{if(count>=7&&prevCount=7&&prevCount
-
React18.2x源码解析(一)react应用加载React前言react和vue作为最流行的前端框架,只要你熟悉其中一个框架,上手另外一个也是非常快的。刚开始看react源码时进度非常缓慢,不仅是因为时间比较有限,平时基本是利用空闲时间和周末来学习,更多的还是因为React源码比较复杂,代码量也非常庞大,之前也看过Vue2和Vue3的源码,但是难度方面确实也是显而易见的。后面也是经常看网上一些资料文章进行参考学习,期间也正好是看到了卡颂的《React设计原理》,就买了这本书辅助学习,这本书对我来说最大的帮助就是梳理了react源码整体的架构和源码中一些抽象概念的理解,所以想要学习react源码的也推荐参考这本书帮助学习,当然最重要的还是要自己去看源码,去调试实操,不然收获是比较有限的。个人喜欢在学习新的内容之后书写markdown文档进行总结,大大小小也已经积累了几十个文档了。所以这类文章算是我学习源码之后的一个笔记总结,当然还是进行了一定的优化,也绘制了一些相关的图例。因为水平有限可能存在部分理解有误,也希望理解和指出以便修正。本系列预计会有十篇左右,目前已经完成了七篇,后面可能每一两天更新一篇;第一到第四章节是讲解一个react应用加载的核心流程,所以一些细节会有所省略,比如组件详细的加载过程和hooks的处理等,这些内容会放在新的章节里面讲解。源码解析的文章其实书写起来麻烦而且阅读体验可能也并不友好,主要是内容太多且枯燥,只能尽力而为,补充相关的调试截图以及案例配图,能对正在学习react源码的朋友有一点帮助即可。1.源码结构React18.2的目录结构如下:#根目录├──fixtures#一些React测试项目├──packages#react框架的各种源码包,比如react/react-dom/react-server等├──scripts#各种工具的脚本文件,比如babel/devtools/eslint等我们主要关注packages目录,里面是react框架的各种核心源码包:├──react#通用的react框架核心API;比如Component、createRef、useState、useRef等├──react-dom#dom平台相关的源码├──react-art#处理canvas,svg等├──react-server#ssr服务器渲染相关的源码├──react-native-renderer#native环境├──react-reconciler#Fiber架构源码├──scheduler#调度器源码├──shared#整个项目通用的工具包,包含各种utils公用方法...2.应用入口根据React18版本,每一个React应用的入口都是从react-dom这个源码包中引入一个ReactDOM对象,然后调用createRoot方法进行一系列的初始化准备,这个方法会返回一个root对象【ReactDOMRoot构造函数的实例】,最后调用root.render()方法开始一个React应用的加载渲染。本源码系列都以浏览器端为视角,展开对React源码的学习。importReactfrom'react';importReactDOMfrom'react-dom/client';constroot=ReactDOM.createRoot(document.getElementById('root'));root.render();createRoot接下来就从createRoot这个方法开始React源码的学习。首先查看createRoot的源码://packages\react-dom\src\client\ReactDOM.jsfunctioncreateRoot(container:Element|Document|DocumentFragment,options?:CreateRootOptions,):RootType{returncreateRootImpl(container,options);}这里createRootImpl方法为ReactDOMRoot.js文件中的createRoot方法。继续深入源码://packages\react-dom\src\client\ReactDOMRoot.jsexportfunctioncreateRoot(container:Element|Document|DocumentFragment,options?:CreateRootOptions,):RootType{#container:默认是#rootdom元素//校验container是否为合法的dom元素if(!isValidContainer(container)){thrownewError('createRoot(...):TargetcontainerisnotaDOMelement.');}//开发环境校验警告warnIfReactDOMContainerInDEV(container);//默认严格模式为falseletisStrictMode=false;letconcurrentUpdatesByDefaultOverride=false;letidentifierPrefix='';letonRecoverableError=defaultOnRecoverableError;lettransitionCallbacks=null;...#创建root应用根节点对象,为FiberRootNode类型://注:ConcurrentRoot为1,代表了v18版本的并发渲染模式constroot=createContainer(container,ConcurrentRoot,//1默认开启并发模式null,isStrictMode,concurrentUpdatesByDefaultOverride,identifierPrefix,onRecoverableError,transitionCallbacks,);//给#rootDOM元素设置一个内部属性,存储root.current即HostFiber对象markContainerAsRoot(root.current,container);constrootContainerElement:Document|Element|DocumentFragment=container.nodeType===COMMENT_NODE?(container.parentNode:any):container;//监听[#rootdom元素]的所有事件listenToAllSupportedEvents(rootContainerElement);#创建ReactDOMRoot实例对象并返回将root应用根节点对象存储为ReactDOM的内部对象returnnewReactDOMRoot(root);}createRoot方法首先对传入的container【#root】容器元素进行合法校验:校验失败,抛出异常,停止应用加载。校验成功,继续向下执行应用加载。然后初始化了一些默认的状态,这个有一个变量需要注意:letisStrictMode=false;react18默认没有开启严格模式,需要自己使用组件包裹App根组件,才能开启整个应用的严格模式。但是React18会默认开启并发渲染模式://历史遗留模式,针对16,17版本LegacyRoot=0;//并发模式【react18默认开启】ConcurrentRoot=1;注意:react18虽然创建的是ConcurrentRoot并发渲染模式的应用,但是默认还是同步创建Fiber树,只有在使用useTransition等并发hook时,触发的更新才能开启真正的并发渲染,即启动react的时间切片功能。createRoot方法中主要有三个重点逻辑处理:调用createContainer方法,创建root应用根节点对象。在#root应用容器元素上监听所有事件,这是React事件系统的关键。创建一个ReactDOMRoot实例对象并返回,这就是createRoot方法最后返回的root对象。下面我们开始逐个解析每个逻辑过程。createContainer首先进入createContainer源码://packages\react-reconciler\src\ReactFiberReconciler.new.jsexportfunctioncreateContainer(containerInfo:Container,tag:RootTag,hydrationCallbacks:null|SuspenseHydrationCallbacks,isStrictMode:boolean,concurrentUpdatesByDefaultOverride:null|boolean,identifierPrefix:string,onRecoverableError:(error:mixed)=>void,transitionCallbacks:null|TransitionTracingCallbacks,):OpaqueRoot{//hydrate代表ssr,默认为falseconsthydrate=false;constinitialChildren=null;//只调用了一个方法returncreateFiberRoot(containerInfo,tag,hydrate,initialChildren,hydrationCallbacks,isStrictMode,concurrentUpdatesByDefaultOverride,identifierPrefix,onRecoverableError,transitionCallbacks,);}继续查看createFiberRoot源码://packages\react-reconciler\src\ReactFiberRoot.new.js//创建root应用根节点对象exportfunctioncreateFiberRoot(containerInfo:any,tag:RootTag,hydrate:boolean,...):FiberRoot{#创建root应用根节点对象【FiberRootNode】constroot:FiberRoot=newFiberRootNode(containerInfo,tag,//1hydrate,identifierPrefix,onRecoverableError,);#创建一个FiberNode对象【HostRootFiber】,它是Fiber树的根节点,非常重要constuninitializedFiber=createHostRootFiber(tag,isStrictMode,concurrentUpdatesByDefaultOverride,);//【关联起来,以便在后续的渲染过程中能够正确地处理该组件树的更新和重新渲染。】//将root应用根节点对象的current属性指向了当前CurrentFiberTree组件树的根节点【HostRootFiber】root.current=uninitializedFiber;//然后将HostFiber.stateNode属性值:设置为root应用根节点对象uninitializedFiber.stateNode=root;if(enableCache){constinitialCache=createCache();retainCache(initialCache);root.pooledCache=initialCache;retainCache(initialCache);constinitialState:RootState={element:initialChildren,isDehydrated:hydrate,cache:initialCache,transitions:null,pendingSuspenseBoundaries:null,};uninitializedFiber.memoizedState=initialState;}else{constinitialState:RootState={element:initialChildren,isDehydrated:hydrate,cache:(null:any),//notenabledyettransitions:null,pendingSuspenseBoundaries:null,};//初始化数据uninitializedFiber.memoizedState=initialState;}//初始化HostRootFiber根节点对象的updateQueue属性initializeUpdateQueue(uninitializedFiber);#返回root应用根节点对象【容器】returnroot;}createFiberRoot方法一进来就创建了一个root实例对象,它的类型为FiberRootNode。它是根据#root容器元素创建的对象,可以称为应用根节点。constroot=newFiberRootNode()我们要理解这个对象的内容,就要查看FiberRootNode构造函数:functionFiberRootNode(containerInfo,tag,hydrate,identifierPrefix,onRecoverableError,){this.tag=tag;//应用模式:1表示并发渲染模式this.containerInfo=containerInfo;//存储#rootdom对象this.pendingChildren=null;this.current=null;#这个属性指向CurrentFiberTree的根节点也就是HostFiberRootthis.pingCache=null;this.finishedWork=null;//存储创建完成的FiberTreethis.timeoutHandle=noTimeout;this.context=null;this.pendingContext=null;this.callbackNode=null;//回调节点,存储当前任务taskthis.callbackPriority=NoLane;//回调任务优先级this.eventTimes=createLaneMap(NoLanes);this.expirationTimes=createLaneMap(NoTimestamp);this.pendingLanes=NoLanes;//默认0this.suspendedLanes=NoLanes;this.pingedLanes=NoLanes;this.expiredLanes=NoLanes;this.mutableReadLanes=NoLanes;this.finishedLanes=NoLanes;this.entangledLanes=NoLanes;this.entanglements=createLaneMap(NoLanes);...}下图示例:为debug调试中创建的FiberRootNode实例对象:FiberRootNode中的实例属性很多,我们只需要先了解几个常用的。下面我们继续回到createFiberRoot方法,然后又创建了一个Fiber对象://hostFiberconstuninitializedFiber=createHostRootFiber()//packages\react-reconciler\src\ReactFiber.new.jsexportfunctioncreateHostRootFiber(tag:RootTag,//1isStrictMode:boolean,concurrentUpdatesByDefaultOverride:null|boolean,):Fiber{letmode;...省略#创建Fiber对象tag为3即代表HostRoot节点returncreateFiber(HostRoot,null,null,mode);}查看createFiber方法,这个内部直接调用FiberNode构造函数,创建了一个Fiber对象:constcreateFiber=function(tag:WorkTag,pendingProps:mixed,key:null|string,mode:TypeOfMode,):Fiber{//创建FiberNode实例对象returnnewFiberNode(tag,pendingProps,key,mode);};FiberNode继续查看FiberNode构造函数:functionFiberNode(tag:WorkTag,pendingProps:mixed,key:null|string,mode:TypeOfMode,){//Instancethis.tag=tag;//节点类型,非常重要,不同的值代表不同的节点对象this.key=key;//节点keythis.elementType=null;//大部分情况同type,存储组件对象Elementthis.type=null;#组件原始定义//存储FiberNode对象对应的dom元素【hostCompoent】,//函数组件此属性无值,//类组件此属性存储的是组件实例instancethis.stateNode=null;#FiberNode节点之间的链接this.return=null;//指向父级节点对象FiberNodethis.child=null;//指向第一个子节点FiberNodethis.sibling=null;//指向下一个兄弟节点FiberNodethis.index=0;this.ref=null;//ref引用#hooks相关this.pendingProps=pendingProps;//新的,等待处理的propsthis.memoizedProps=null;//旧的,上一次存储的propsthis.updateQueue=null;//存储update更新对象链表this.memoizedState=null;//类组件:旧的,上一次存储的state;函数组件:存储hook链表this.dependencies=null;this.mode=mode;//存储的是当前应用渲染模式:默认是concurrentmode//各种effect副作用相关的执行标记this.flags=NoFlags;this.subtreeFlags=NoFlags;//子树节点的副作用标记,默认无副作用this.deletions=null;//删除标记//优先级调度,默认为0this.lanes=NoLanes;//节点自身的更新Lanesthis.childLanes=NoLanes;//子树节点的更新lanes#这个属性指向另外一个缓冲区对应的FiberNode//current.alternate===workInProgress//workInProgress.alternate===currentthis.alternate=null;...}可以看到FiberNode同样定义了很多的实例属性,讲到这里必须要对FiberNode进行一些说明:Fiber是VDOM是在React源码中的实现,FiberNode即虚拟dom,在Vue源码中定义为VNode。在React源码中有两种Fiber节点定义:FiberRootNode和FiberNode,但实际上我们说的Fiber节点是指的普通的FiberNode,我们从上面两个构造函数的定义可以发现,它们的实例属性完全不同,因为它们本来就是不一样的作用。FiberRootNode:是针对React应用挂载的容器元素【#root】创建的一个对象,它是应用根节点。它的作用是负责应用加载相关的内容【应用级】,比如应用加载模式mode,存储本次应用更新的回调任务以及优先级,存储创建完成的FiberTree等。FiberNode:这才是针对普通DOM元素或者组件创建的Fiber对象,是虚拟DOM的真实体现,它的属性存储了元素的或者组件的类型,对应的DOM信息,以及数据State和组件更新的相关信息,所以FiberNode才是Fiber架构的核心。回到createContainer方法内:constuninitializedFiber=createHostRootFiber()这里要注意创建的对象为HostFiber,虽然它也是普通的FiberNode类型,但是它的tag属性为3,代表着它是虚拟DOM树的根节点。exportconstHostRoot=3;然后将FiberRootNode与HostRootFiber进行关联,方便在后续使用中可以互相访问:root.current=uninitializedFiber;uninitializedFiber.stateNode=root;以一张图表示它们的关系:FiberRootNode是React应用的根节点【容器】,HostRootFiber是虚拟DOM树的根节点【FiberTree】。通过属性关联之后,它们就可以在需要的时候互相访问。回到createContainer方法内:functioncreateContainer(){...省略//初始化HostFiber节点的updateQueue属性initializeUpdateQueue(uninitializedFiber);//返回root应用根节点对象returnroot;}再返回root对象之前,还需要对HostRootFiber进行处理://初始化一个Fiber对象的updateQueue属性exportfunctioninitializeUpdateQueue(fiber:Fiber):void{//创建一个更新队列对象constqueue:UpdateQueue={baseState:fiber.memoizedState,//初始state数据firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:NoLanes,},effects:null,};//设置更新队列对象fiber.updateQueue=queue;}初始化HostFiber节点的updateQueue属性,为一个更新队列对象。总结createContainer方法:创建FiberRootNode应用根节点对象。创建HostFiber虚拟DOM树根节点对象。关联两个对象,可以互相访问。初始化HostFiber的updateQueue属性。返回FiberRootNode节点。事件监听回到createRoot方法,在创建FiberRootNode应用根节点对象后,然后给#root根元素绑定了所有事件,任何子孙元素触发的该类型事件都会委托给【根元素的事件回调】处理。listenToAllSupportedEvents(rootContainerElement);这里涉及到了React的合成事件系统,它的实现非常复杂,后面如果有时间会考虑单开一个章节详细解析,这里暂时只简单介绍一下它的基本原理,想了解更多的朋友也可以查阅相关资料学习。在#root根元素上绑定所有的事件回调,任何子孙元素触发的改类型事件都会委托给【根元素的事件回调】处理。寻找触发事件的dom元素,找到对应的FiberNode节点。收集从当前FiberNode节点到HostFiber之间的所有注册该事件的回调函数。循环执行一次收集的所有回调函数。回到createRoot方法最后,创建了一个ReactDOMRoot实例对象并返回。returnnewReactDOMRoot(root);查看ReactDOMRoot构造函数的定义:functionReactDOMRoot(internalRoot:FiberRoot){//存储应用根节点[FiberRootNode]this._internalRoot=internalRoot;}//原型方法ReactDOMRoot.prototype.render=function(){}ReactDOMRoot.prototype.unmount=function(){}ReactDOMRoot构造函数只定义了一个实例属性_internalRoot,它的作用是存储创建完成的应用根节点FiberRootNode对象。还在原型对象上定义了两个静态方法render和unmount。importReactfrom'react';importReactDOMfrom'react-dom/client';constroot=ReactDOM.createRoot(document.getElementById('root'));//调用render方法,开始应用加载root.render();最后对createRoot方法做一个总结:它的主要作用就是为react应用加载做准备工作,初始化了一些根对象和基础信息,最后返回了一个ReactDOMRoot对象,调用它的render方法开启React应用的加载流程。3.应用加载最后我们再来看一下这个render方法:ReactDOMRoot.prototype.render=function(children){constroot=this._internalRoot;if(root===null){thrownewError('Cannotupdateanunmountedroot.');}//开始加载react应用children为App根组件,root为应用根节点对象FiberRootNode/***react组件都会被转换为react-element对象:*{*$$typeof:Symbol(react.element),*key:null,*props:{},*ref:null,*type:funApp(){}*}**/updateContainer(children,root,null,null);}updateContainer继续查看updateContainer源码://packages\react-reconciler\src\ReactFiberReconciler.new.js#加载应用:触发调度更新任务exportfunctionupdateContainer(element:ReactNodeList,//App根组件container:OpaqueRoot,parentComponent:?React$Component,callback:?Function,):Lane{//取出current对象,为HostFiber【它是当前Fiber树的根节点】constcurrent=container.current;//获取当前的程序执行时间【默认是performenct.now返回的微秒时间】consteventTime=requestEventTime();#获取更新优先级laneconstlane=requestUpdateLane(current);//获取父组件的上下文,因为parentComponent为null,所以这里context为空对象constcontext=getContextForSubtree(parentComponent);if(container.context===null){//从null变为{}//如果容器的上下文为null,则把父级上下文赋值container.context=context;}else{//如果容器存在上下文,则把父级的上下文设置为等待处理的上下文container.pendingContext=context;}#创建一个update更新对象constupdate=createUpdate(eventTime,lane);//将更新对象的payload属性设置为App根组件的内容,【即:本次应用加载的内容为App根组件】update.payload={element};//本来就没有,设置完还是nullcallback=callback===undefined?null:callback;//检查回调函数callback是否为空,如果不为空,则将其添加到更新对象的callback属性中if(callback!==null){update.callback=callback;}#将更新对象update:添加到当前current对象的更新队列中constroot=enqueueUpdate(current,update,lane);if(root!==null){#开启一个新的调度更新任务scheduleUpdateOnFiber(root,current,lane,eventTime);entangleTransitions(root,current,lane);}returnlane;}updateContainer方法的内容非常重要,主要是比较典型。你可以在react源码中的很多地方看到类似的执行逻辑,react应用的加载会执行这些逻辑,一个state状态的变化,触发的更新也会执行这些逻辑。我们可以把它的执行过程分为四个重点部分:获取更新优先级lane,也就是生成一个本次更新对应的lane。创建update更新对象。将update更新对象添加到目标Fiber对象的更新队列中。开启一个新的调度更新任务。接下来我们逐个分析执行过程:(一)获取更新优先级laneconstlane=requestUpdateLane(current);//HostFiber查看requestUpdateLane方法://packages\react-reconciler\src\ReactFiberWorkLoop.new.js#获取更新优先级laneexportfunctionrequestUpdateLane(fiber:Fiber):Lane{constmode=fiber.mode;if((mode&ConcurrentMode)===NoMode){//1,模式为0时,返回同步优先级return(SyncLane:Lane);}elseif(!deferRenderPhaseUpdateToNextBatch&&(executionContext&RenderContext)!==NoContext&&workInProgressRootRenderLanes!==NoLanes){//2,render阶段产生的update【比如调用fun()组件的过程中又触发了更新】,返回render阶段进行中最高的优先级returnpickArbitraryLane(workInProgressRootRenderLanes);}constisTransition=requestCurrentTransition()!==NoTransition;//3,Transition相关优先级:如果当前不是渲染上下文,并且请求的更新是过渡(transition),则进入下一个条件。if(isTransition){transition._updatedFibers.add(fiber);}if(currentEventTransitionLane===NoLane){currentEventTransitionLane=claimNextTransitionLane();}returncurrentEventTransitionLane;}//4,使用手动设置的优先级:获取当前更新通道优先级:默认为0constupdateLane:Lane=(getCurrentUpdatePriority():any);//NoLane0if(updateLane!==NoLane){//只要更新通道不等于0,则返回有效的更新通道returnupdateLane;}//5,获取当前事件级的优先级:默认为16consteventLane:Lane=(getCurrentEventPriority():any);returneventLane;}在react中,为了实现并发渲染和优先级控制,使用了多个更新通道(lane)来管理更新任务。每个更新通道都对应一个优先级,根据不同的优先级,react可以决定哪些更新任务可以同时进行,哪些需要等待之前的更新任务完成后才能进行。requestUpdateLane函数的作用是:接收一个Fiber对象作为参数,并返回一个新的更新通道【更新优先级】,这个优先级lane会根据当前Fiber对象的相关属性或者当前的更新环境确定的。requestUpdateLane方法内有以下五种情况对应的优先级lane:判断当前是否为并发渲染模式,如果不是则返回syncLane同步优先级。判断当前是否为render中的更新,即在组件渲染中修改了状态,又触发了更新。如果是则会从当前workInProgressRootRenderLanes中选择最高优先级的Lane,作为本次更新的优先级。判断当前是否为useTransitionhook相关的更新,如果是则在此计算对应的优先级lane,并设置为当前的更新优先级。判断是否存在调度中的更新优先级,如果存在则使用此优先级lane。在以上情况都不满足时,就会从事件中来寻找对应的优先级。当前为react应用首次加载,所以不满足前面的条件,最后就会返回eventLane,作为本次更新的优先级lane。consteventLane=getCurrentEventPriority()exportfunctiongetCurrentEventPriority(){constcurrentEvent=window.event;//应用首次加载,这里为undefinedif(currentEvent===undefined){#返回默认事件优先级16returnDefaultEventPriority;}//返回事件对应的优先级returngetEventPriority(currentEvent.type);}因为react应用的首次加载并不是由某个具体的事件触发的,所以window.event的值为undefined,这里就会返回默认的事件优先级lane来作为应用初始加载的优先级。优先级体系在react源码中,Scheduler调度器是一个独立的包,React与Scheduler没有共用一套优先级体系:react中有四种优先级,称为事件优先级,Scheduler有五种优先级,称为调度优先级。react的事件优先级://1,对应离散事件的优先级,就是普通的事件比如click,input,blur等,这些事件对应的是同步优先级exportconstDiscreteEventPriority:EventPriority=SyncLane;//2,对应的连续事件的优先级,比如drag,mousemove,scroll,wheel等,exportconstContinuousEventPriority:EventPriority=InputContinuousLane;//3,默认的优先级exportconstDefaultEventPriority:EventPriority=DefaultLane;//4,空闲情况的优先级,最低exportconstIdleEventPriority:EventPriority=IdleLane;scheduler的调度优先级:#值越小优先级越高exportconstImmediatePriority=1;//最高优先级,同步执行exportconstUserBlockingPriority=2;exportconstNormalPriority=3;//默认优先级exportconstLowPriority=4;exportconstIdlePriority=5;//最低优先级React与Scheduler的优先级转换需要:通过lane转化。从React到Scheduler的优先级转换:consteventLane=lanesToEventPriority(nextLanes)letschedulerPriorityLevel;#lanes转换成事件优先级,匹配符合的事件优先级,然后赋值对应的scheduler的优先级switch(eventLane){//同步优先级caseDiscreteEventPriority:schedulerPriorityLevel=ImmediateSchedulerPriority;break;//连续事件优先级caseContinuousEventPriority:schedulerPriorityLevel=UserBlockingSchedulerPriority;break;//默认事件优先级caseDefaultEventPriority:schedulerPriorityLevel=NormalSchedulerPriority;break;//空闲事件优先级caseIdleEventPriority:schedulerPriorityLevel=IdleSchedulerPriority;break;//默认default:schedulerPriorityLevel=NormalSchedulerPriority;break;}从Scheduler到React的优先级转换://获取当前调度的优先级,然后根据优先级匹配到对应的优先级,最后返回对应的事件优先级constschedulerPriority=getCurrentSchedulerPriorityLevel();switch(schedulerPriority){caseImmediateSchedulerPriority:returnDiscreteEventPriority;caseUserBlockingSchedulerPriority:returnContinuousEventPriority;caseNormalSchedulerPriority:caseLowSchedulerPriority:returnDefaultEventPriority;caseIdleSchedulerPriority:returnIdleEventPriority;default:returnDefaultEventPriority;}react中的位运算这里还有一个注意点:exportfunctionrequestUpdateLane(fiber:Fiber):Lane{constmode=fiber.mode;//位运算if((mode&ConcurrentMode)===NoMode){return(SyncLane:Lane);}}mode&ConcurrentMode这里算是我们在react源码中遇到的第一个位运算处理,所以需要提前说明一下。如果你阅读过react源码,就可以发现其内部使用了大量的位运算操作,其中最典型的就是lanes优先级和flags副作用标记。这里只介绍三种基本的位运算,因为react源码中主要使用的就是三种,关于位运算的更多信息可以参考《JavaScript高级程序设计4》中的位操作符章节。按位与&第一个数值的位第二个数值的位结果111100010000按位与操作在两个位都是1时返回1,在任何一位是0时返回0。这里直接以上面的代码为例:此时mode为3,ConcurrentMode为1:3&1计算3&1,首先将操作数转化为Int32://30b000000000000000000000000000011//10b000000000000000000000000000001//&计算0b000000000000000000000000000001////结果13&1计算结果为1,不等于NoMode【0】,所以不满足条件。按位或|第一个数值的位第二个数值的位结果111101011000按位或操作在至少一位是1时返回1,两位都是0时返回0。计算3|1://30b000000000000000000000000000011//10b000000000000000000000000000001//3|1计算0b000000000000000000000000000011//结果为3按位非~一个操作数的bit位:取反结果1001按位或操作是对一个操作数进行取反操作【0,1互换】。计算~3://30b000000000000000000000000000011//~3逐位取反0b111111111111111111111111111100//结果为-4lanes计算常见的几个通过位运算来计算lanes的方法:合并lanes://packages\react-reconciler\src\ReactFiberLane.new.js//合并lanesexportfunctionmergeLanes(a:Lanes|Lane,b:Lanes|Lane):Lanes{returna|b;}移除lanes://移除lanesexportfunctionremoveLanes(set:Lanes,subset:Lanes|Lane):Lanes{returnset&~subset;}是否包含lanes://判断当前的lanes是否包含某个laneexportfunctionincludesSomeLane(a:Lanes|Lane,b:Lanes|Lane){return(a&b)!==NoLanes;}(二)创建update更新对象constupdate=createUpdate(eventTime,lane);//packages\react-reconciler\src\ReactFiberClassUpdateQueue.new.js#创建更新对象exportfunctioncreateUpdate(eventTime:number,lane:Lane):Update{//定义update对象constupdate:Update={eventTime,lane,//更新优先级tag:UpdateState,//不同的tag对应不同的更新场景payload:null,//更新的内容==fun组件用action字段callback:null,//回调函数next:null,//链表形式:指向下一个更新的对象【保证了update之间的顺序】};//返回创建的update对象returnupdate;}update对象的存储了更新相关的一些基本信息。tag属性:不同值对应着不同的更新场景://1,默认情况:通过ReactDOM.createRoot或者this.setState触发exportconstUpdateState=0;//2,在classCompont组件生命周期函数中使用this.setState触发更新exportconstReplaceState=1;//3,通过this.forceUpdate触发exportconstForceUpdate=2;//4,发生错误的情况下在classComponent或者HostRoot中触发更新exportconstCaptureUpdate=3;当前是通过ReactDOM.createRoot触发的应用加载,所以update对象的tag属性值为0。payload属性值对应的情况:#1,ReactDOM.reacteRoot{payload:{element}//即组件对应的element对象}#2,classComponent组件//this.setState({count:1}){payload:{count:1}//也有可能是一个函数}当前的payload属性存储的是App根组件对应的element对象。callback属性:当前为null。如果是this.setState触发的更新,则为此方法传入的第二个参数【回调函数】。next属性:指向下一个update更新对象,形成一个单向环状链表。总结:根据上面的tag值,我们可以看出类组件的更新与ReactDOM.createRoot共用同一个update更新对象结构,对于ReactDOM.createRoot来说,当前创建的这个update对象,它的作用主要在于在react应用的首次加载【提供的初始信息】。(三)将update更新对象添加到目标Fiber对象的更新队列中constroot=enqueueUpdate(current,update,lane);//packages\react-reconciler\src\ReactFiberClassUpdateQueue.new.jsexportfunctionenqueueUpdate(fiber:Fiber,//要更新的节点,当前也就是hostFiberupdate:Update,//更新对象lane:Lane,//更新的优先级):FiberRoot|null{#取出当前Fiber节点的updateQueue属性constupdateQueue=fiber.updateQueue;if(updateQueue===null){//Onlyoccursifthefiberhasbeenunmounted.//表明当前节点已经被卸载returnnull;}//一个Fiber节点会先初始化updateQueue属性,后创建Update对象//取出updateQueue.shared对象{lanes:0,pending:null}constsharedQueue:SharedQueue=(updateQueue:any).shared;//判断当前是否为不安全的渲染阶段更新if(isUnsafeClassRenderPhaseUpdate(fiber)){constpending=sharedQueue.pending;if(pending===null){update.next=update;}else{update.next=pending.next;pending.next=update;}sharedQueue.pending=update;returnunsafe_markUpdateLaneFromFiberToRoot(fiber,lane);}else{#默认是处于安全的渲染阶段更新:讲update对象添加到queue之中returnenqueueConcurrentClassUpdate(fiber,sharedQueue,update,lane);}}前面我们已经知道了update对象是存储更新相关的一些基本信息,可以说它是一个基本的更新装置,而updateQueue很明显就是来保存或者说管理update对象的数据结构。经过上面的处理,最终会将update更新对象存储到当前Fiber节点中,形成一个单向环状链表。这里描述的比较简单,因为对于react应用加载的场景,update和updateQueue的作用并不明显。它们更重要的使用场景,在于组件更新时的逻辑处理。关于类组件updateQueue这部分详细内容可以查看《React18.2x源码解析:类组件的加载过程》。关于函数组件updateQueue这部分详细内容可以查看《React18.2x源码解析:函数组件的加载过程》。(四)开启一个新的调度更新任务//开启一个新的的调度更新任务scheduleUpdateOnFiber(root,current,lane,eventTime);scheduleUpdateOnFiber函数非常重要,在react源码中有很多地方在调用,它是react开始执行调度更新的入口函数。从这个函数的名称我们也可以知道它的作用:开启一个新的调度更新任务。下一章节我们将从这个函数开始进入scheduler调度程序的执行过程。
-
React 19 内置HooksReact数据驱动更新StateHookuseStateuseState是React中的一个Hook,用于在函数组件中添加状态。它接受一个初始状态作为参数,并返回一个包含当前状态值和更新状态值的数组。参数说明:初始状态:可以是任何JavaScript数据类型,用来初始化状态的值。返回说明:数组:包含当前状态值和更新状态值的数组,第一个元素是当前状态值,第二个元素是更新状态值的函数。应用场景:在函数组件中添加和管理状态。替代类组件中的this.state和this.setState。用法举例:importReact,{useState}from'react';constExampleComponent:React.FC=()=>{const[count,setCount]=useState(0);constincrement=()=>{setCount(count+1);};return(Count:{count}Increment);};exportdefaultExampleComponent;在上面的例子中,我们使用useState创建了一个名为count的状态变量,并初始化为0。我们还定义了一个increment函数,用于增加count的值。最后,我们在JSX中展示了当前count的值,并在按钮点击事件中调用increment函数来更新count的值。useReduceruseReducer是React中的另一个Hook,用于管理复杂的状态逻辑。它接受一个reducer函数和初始状态作为参数,并返回当前状态值和dispatch函数。参数说明:reducer函数:接受两个参数,当前状态和action,返回新的状态。初始状态:可以是任何JavaScript数据类型,用来初始化状态的值。返回说明:数组:包含当前状态值和dispatch函数的数组,第一个元素是当前状态值,第二个元素是派发action的函数。应用场景:状态逻辑较复杂,需要根据不同action进行状态更新的情况。替代useState,在某些情况下更方便和易于维护。用法举例:importReact,{useReducer}from'react';typeState={count:number;};typeAction={type:'increment'|'decrement';};constinitialState:State={count:0,};constreducer=(state:State,action:Action):State=>{switch(action.type){case'increment':return{count:state.count+1};case'decrement':return{count:state.count-1};default:returnstate;}};constExampleComponent:React.FC=()=>{const[state,dispatch]=useReducer(reducer,initialState);constincrement=()=>{dispatch({type:'increment'});};constdecrement=()=>{dispatch({type:'decrement'});};return(Count:{state.count}IncrementDecrement);};exportdefaultExampleComponent;在上面的例子中,我们使用useReducer创建了一个名为count的状态变量,并初始化为0。我们定义了一个reducer函数来处理不同的action类型并更新状态。然后,我们使用dispatch函数来派发不同的action,从而更新状态。最后,在JSX中展示当前count的值,并在按钮点击事件中调用对应的dispatch函数来更新count的值。状态传递ContextHookuseContextuseContext是React中的一个Hook,用于在函数组件中访问Context。它接受一个Context对象作为参数,并返回该Context的当前值。参数说明:Context对象:通过React.createContext创建的Context对象。返回说明:任意类型:返回该Context的当前值。应用场景:在函数组件中访问全局的状态或其他共享数据。避免在组件树中层层传递props。用法举例:importReact,{createContext,useContext}from'react';typeTheme='light'|'dark';constThemeContext=createContext('light');constApp:React.FC=()=>{return();};constToolbar:React.FC=()=>{consttheme=useContext(ThemeContext);return(CurrentTheme:{theme});};exportdefaultApp;在上面的例子中,我们创建了一个ThemeContext,其默认值为'light'。在App组件中,我们使用ThemeContext.Provider来提供当前主题值为'dark',并渲染了Toolbar组件。在Toolbar组件中,我们使用useContext(ThemeContext)来获取当前主题值,并在JSX中展示出来。这样就实现了在函数组件中访问全局的主题值而不需要通过props层层传递。元素获取RefHookuseRefuseRef是React中的一个Hook,主要用于访问和存储对DOM元素的引用或保存不需要触发重新渲染的可变值。参数说明:initialValue:可选,初始值。可以是任何类型,通常用于存储一个对象或DOM引用。返回说明:返回一个可变的ref对象,ref对象的.current属性可以用来存储任何值。应用场景:访问DOM元素:通过useRef可以直接访问组件中的DOM元素,常用于获取焦点、选择文本等。存储可变值:在函数组件中,useRef可以用来存储不需要重新渲染的可变值,例如计时器ID、先前的状态等。保持引用:useRef可以保持对某个值的引用,而不会在组件重新渲染时重置该值。用法举例:importReact,{useRef}from'react';constExampleComponent:React.FC=()=>{constinputRef=useRef(null);constfocusInput=()=>{inputRef.current?.focus();//使用可选链安全地访问current};return(Focustheinput);};exportdefaultExampleComponent;在上面的例子中,我们使用useRef创建了一个inputRef,它将用于引用输入框元素。我们通过ref属性将inputRef关联到输入框上。当用户点击按钮时,focusInput函数会调用inputRef.current?.focus(),使输入框获得焦点。importReact,{useRef,useEffect}from'react';constTimerComponent:React.FC=()=>{consttimerRef=useRef(null);constcountRef=useRef(0);useEffect(()=>{timerRef.current=setInterval(()=>{countRef.current+=1;console.log(countRef.current);},1000);//清理定时器return()=>{if(timerRef.current){clearInterval(timerRef.current);}};},[]);returnChecktheconsoleforcountupdateseverysecond.;};exportdefaultTimerComponent;在这个例子中,我们使用useRef来存储定时器的引用和计数器的值。countRef用于记录计数器的当前值,而不会导致组件重新渲染。定时器每秒增加计数器的值并输出到控制台。组件卸载时,定时器会被清理。useImperativeHandleuseImperativeHandle是React中的一个Hook,通常与forwardRef一起使用,用于自定义组件暴露给父组件的实例值。它允许你在函数组件中控制通过ref传递给父组件的值。参数说明:ref:传递给forwardRef的ref对象。createHandle:一个函数,返回一个对象,该对象包含要暴露给父组件的实例值。返回说明:void:该Hook不返回任何值。应用场景:自定义组件的实例方法:当你需要在父组件中调用子组件的方法时,可以使用useImperativeHandle来暴露这些方法。控制子组件的行为:允许父组件通过ref直接控制子组件的某些行为。用法举例:importReact,{useImperativeHandle,forwardRef,useRef,useState}from'react';typeChildRef={focus:()=>void;};constChildComponent=forwardRef((_,ref)=>{constinputRef=useRef(null);useImperativeHandle(ref,()=>({focus:()=>{inputRef.current?.focus();//暴露focus方法},}));return;});constParentComponent:React.FC=()=>{constchildRef=useRef(null);consthandleFocus=()=>{childRef.current?.focus();//调用子组件的focus方法};return(FocustheinputinChild);};exportdefaultParentComponent;在上面的例子中,我们创建了一个ChildComponent,它使用forwardRef来接收父组件传递的ref。在ChildComponent中,我们使用useImperativeHandle来暴露一个focus方法,该方法可以使输入框获得焦点。在ParentComponent中,我们创建了一个childRef,并将其传递给ChildComponent。当用户点击按钮时,handleFocus函数会调用childRef.current?.focus(),从而触发子组件的focus方法,使输入框获得焦点。总结:useImperativeHandle使得父组件能够控制子组件的行为,提供了一种灵活的方式来管理组件间的交互。副作用EffectHookuseEffectuseEffect是React中的一个Hook,用于在函数组件中执行副作用操作。它接受一个函数和一个依赖数组作为参数,并在每次渲染后执行该函数。参数说明:effect:一个函数,用于执行副作用操作。deps:一个数组,包含影响副作用操作的依赖项。当依赖项发生变化时,effect函数会被重新执行。返回说明:void:该Hook不返回任何值。应用场景:数据获取:在组件挂载后获取数据。订阅事件:在组件挂载后订阅事件,组件卸载前取消订阅。手动操作DOM:在组件挂载后或更新后手动操作DOM元素。清理副作用:在组件卸载前清理副作用。用法举例:importReact,{useEffect,useState}from'react';constExampleComponent:React.FC=()=>{const[data,setData]=useState('');//模拟数据获取useEffect(()=>{constfetchData=async()=>{constresponse=awaitfetch('https://api.example.com/data');constresult=awaitresponse.json();setData(result.data);};fetchData();return()=>{//在组件卸载前清理副作用console.log('Cleaningup');};},[]);//空依赖数组表示只在组件挂载和卸载时执行return(Data:{data});};exportdefaultExampleComponent;在这个示例中,我们使用useEffect在组件挂载后获取数据,并在组件卸载前清理副作用。在useEffect的回调函数中,我们定义了一个fetchData函数来获取数据,并在组件挂载后调用它。同时,我们返回一个清理函数,用于在组件卸载前执行清理操作。在依赖数组中传入空数组表示useEffect只在组件挂载和卸载时执行。这种方式可以帮助我们管理组件中的副作用操作,确保在适当的时机执行相应的操作,并在组件卸载时清理副作用,避免内存泄漏和其他问题。useLayoutEffectuseLayoutEffect是React中的一个Hook,与useEffect类似,用于在函数组件中执行副作用操作。不同之处在于useLayoutEffect会在DOM变更之后同步触发,而useEffect则是在浏览器绘制完成后异步触发。参数说明:effect:一个函数,用于执行副作用操作。deps:一个数组,包含影响副作用操作的依赖项。当依赖项发生变化时,effect函数会被重新执行。返回说明:void:该Hook不返回任何值。应用场景:DOM操作:在DOM变更之后立即执行操作,确保操作不会影响布局。测量元素尺寸:在DOM变更后立即测量元素的尺寸或位置。动画控制:在DOM变更后立即控制动画效果。同步更新布局:需要在DOM变更后立即更新布局的情况。用法举例:importReact,{useLayoutEffect,useState,useRef}from'react';constExampleComponent:React.FC=()=>{const[width,setWidth]=useState(0);constelementRef=useRef(null);useLayoutEffect(()=>{consthandleResize=()=>{if(elementRef.current){const{width}=elementRef.current.getBoundingClientRect();setWidth(width);}};window.addEventListener('resize',handleResize);handleResize();//立即执行一次return()=>{window.removeEventListener('resize',handleResize);};},[]);//空依赖数组表示只在组件挂载和卸载时执行return(Width:{width}px);};exportdefaultExampleComponent;在这个示例中,我们使用useLayoutEffect来在DOM变更之后立即测量元素的宽度,并在窗口大小变化时实时更新宽度。在useLayoutEffect的回调函数中,我们定义了一个handleResize函数,用于获取元素的宽度并更新状态。然后我们通过window.addEventListener监听窗口大小变化事件,并在组件挂载时立即执行一次handleResize。在依赖数组中传入空数组表示useLayoutEffect只在组件挂载和卸载时执行。通过使用useLayoutEffect,我们可以在DOM变更后立即执行操作,确保操作不会影响布局,适用于需要同步更新布局或执行DOM操作的场景。useInsertionEffectuseInsertionEffect是React18中引入的一个Hook,主要用于在DOM更新之前插入样式或执行其他副作用。它的执行时机比useLayoutEffect更早,适用于需要在浏览器绘制之前进行样式插入的场景。参数说明:effect:一个函数,用于执行副作用操作。deps:一个数组,包含影响副作用操作的依赖项。当依赖项发生变化时,effect函数会被重新执行。返回说明:void:该Hook不返回任何值。应用场景:动态样式插入:在组件渲染之前插入动态生成的样式,确保样式在浏览器绘制之前生效。CSS-in-JS库:在使用CSS-in-JS库时,确保样式在DOM更新之前插入。避免闪烁:在需要避免样式闪烁的情况下,使用useInsertionEffect可以确保样式在渲染之前就已经应用。用法举例:importReact,{useInsertionEffect,useState}from'react';constExampleComponent:React.FC=()=>{const[color,setColor]=useState('blue');useInsertionEffect(()=>{conststyle=document.createElement('style');style.textContent=`.dynamic{color:${color};}`;document.head.appendChild(style);return()=>{document.head.removeChild(style);//清理样式};},[color]);//当color变化时重新插入样式return(Thistextwillchangecolor.setColor(color==='blue'?'red':'blue')}>ToggleColor);};exportdefaultExampleComponent;在这个示例中,我们使用useInsertionEffect来动态插入样式。每当color状态变化时,我们都会创建一个新的元素,并将其插入到文档的中。这样,文本的颜色会根据按钮的点击而变化。通过使用useInsertionEffect,我们确保样式在浏览器绘制之前就已经插入,从而避免了可能的闪烁或样式不一致的问题。清理函数会在组件卸载或依赖项变化时移除插入的样式,确保不会造成内存泄漏。useInsertionEffect和useEffect的区别执行时机:useInsertionEffect在DOM变更之前同步执行,这意味着它会在浏览器绘制之前运行,适合需要插入样式或进行布局计算的场景。useEffect在DOM变更后异步执行,适合大多数副作用操作,如数据获取、事件监听等。适用场景:useInsertionEffect主要用于需要确保样式或其他操作在用户看到变化之前完成的场景。useEffect则更为通用,可以处理各种副作用,且通常是开发者最常用的选择。总结虽然在某些情况下useInsertionEffect和useEffect可以实现相同的功能,但它们的执行时机和适用场景有所不同。useInsertionEffect是用于特定场景的优化工具,通常在处理样式和布局时使用,而useEffect则是处理一般副作用的主要手段。选择使用哪一个Hook取决于你的具体需求。性能HookuseMemouseMemo是React中的一个Hook,用于对计算昂贵的值进行缓存,避免在每次渲染时重新计算。它接受一个函数和依赖数组作为参数,并返回计算结果。参数说明:factory:一个函数,用于计算需要缓存的值。deps:一个数组,包含影响计算结果的依赖项。当依赖项发生变化时,将重新计算值。返回说明:T:计算结果的类型,根据传入的factory函数的返回类型确定。应用场景:性能优化:缓存昂贵的计算结果,避免在每次渲染时重新计算。避免不必要的重渲染:通过缓存值,可以避免不必要的组件重渲染。优化组件性能:在计算量大的场景下,使用useMemo可以提高组件的性能。用法举例:importReact,{useMemo,useState}from'react';constExampleComponent:React.FC=()=>{const[count,setCount]=useState(0);//计算平方值constsquaredValue=useMemo(()=>{console.log('Calculatingsquaredvalue');returncount**2;},[count]);//当count变化时重新计算return(Count:{count}SquaredValue:{squaredValue}setCount(count+1)}>IncrementCount);};exportdefaultExampleComponent;在这个示例中,我们使用useMemo缓存了计算的平方值。每当count状态变化时,squaredValue会重新计算,但在同一个count值下,不会重复计算平方值。这可以减少不必要的计算,提高性能。通过使用useMemo,我们可以避免在每次渲染时都重新计算昂贵的值,优化组件性能,并确保只在必要时才进行计算。useCallbackuseCallback是React中的一个Hook,用于缓存回调函数,避免在每次渲染时重新创建新的回调函数。它接受一个回调函数和依赖数组作为参数,并返回一个memoized(记忆化)版本的回调函数。参数说明:callback:一个回调函数,需要被缓存。deps:一个数组,包含影响回调函数的依赖项。当依赖项发生变化时,将重新创建新的回调函数。返回说明:T:缓存的回调函数的类型,根据传入的callback函数的类型确定。应用场景:避免不必要的子组件重渲染:缓存回调函数可以避免子组件在每次渲染时重新创建新的回调函数,提高性能。传递给子组件的回调函数:在将回调函数传递给子组件时,使用useCallback可以确保子组件不会因为父组件的重新渲染而触发不必要的更新。优化性能:在性能敏感的场景下,使用useCallback可以优化组件的性能,避免不必要的计算。用法举例:importReact,{useCallback,useState}from'react';constExampleComponent:React.FC=()=>{const[text,setText]=useState('');const[count,setCount]=useState(0);consthandleChange=useCallback((event:React.ChangeEvent)=>{setText(event.target.value);},[]);consthandleIncrement=useCallback(()=>{setCount((prevCount)=>prevCount+1);},[]);return(Text:{text}Count:{count}IncrementCount);};exportdefaultExampleComponent;在这个示例中,我们使用useCallback缓存了处理输入框变化和增加计数的回调函数handleChange和handleIncrement。这样,在每次父组件重新渲染时,这两个回调函数不会被重新创建,从而避免不必要的性能消耗。通过使用useCallback,我们可以确保只在依赖项发生变化时重新创建回调函数,提高性能并避免不必要的更新。这对于传递给子组件的回调函数或在性能敏感的场景下非常有用。感谢您的指正,希望这个例子更符合您的要求。useTransitionuseTransition是React18中引入的一个Hook,用于管理UI中的过渡状态,特别是在处理长时间运行的状态更新时。它允许你将某些更新标记为“过渡”状态,这样React可以优先处理更重要的更新,比如用户输入,同时延迟处理过渡更新。参数说明:useTransition不需要任何参数返回说明:useTransition返回一个数组,包含两个元素isPending(boolean),告诉你是否存在待处理的transition。startTransition(function)函数,你可以使用此方法将状态更新标记为transition。应用场景:优化用户体验:在数据加载或其他过渡时显示加载指示器,提高用户体验。控制交互:在需要进行一些耗时操作时,可以使用useTransition控制界面的交互,避免用户误操作。用法举例:想象一个场景,页面上有多个tab,其中一个请求耗时较长,我们快速点击tab切换内容,但总是会在请求耗时的tab上卡顿一下,代码如下:importReact,{useState,memo}from'react';constTabContainer=()=>{const[tab,setTab]=useState('about');//核心:切换选项卡functionselectTab(nextTab){setTab(nextTab);}return(selectTab('about')}>AboutselectTab('posts')}>Posts(slow)selectTab('contact')}>Contact{tab==='about'&&}{tab==='posts'&&}{tab==='contact'&&});};constPostsTab=memo(()=>{letitems=[];for(leti=0;i{letstartTime=performance.now();while(performance.now()-startTime{setTab(nextTab);});}这样我们快速切换tab,无论点到哪一个tab都不会卡顿。注意事项:在React18中,不支持async和await,但是从React19开始,已经支持async和await了在React18中的用法//错误做法startTransition(()=>{//❌在调用startTransition后更新状态setTimeout(()=>{setPage('/about');},1000);});//正确做法setTimeout(()=>{startTransition(()=>{//✅在调用startTransition中更新状态setPage('/about');});},1000);//错误做法startTransition(async()=>{awaitsomeAsyncFunction();//❌在调用startTransition后更新状态setPage('/about');});//正确做法awaitsomeAsyncFunction();startTransition(()=>{//✅在调用startTransition中更新状态setPage('/about');});在React19中的用法import{useState,useTransition}from'react';import{updateQuantity}from'./api';functionCheckoutForm(){const[isPending,startTransition]=useTransition();const[quantity,setQuantity]=useState(1);//支持awaitasyncfunctiononSubmit(newQuantity){startTransition(asyncfunction(){constsavedQuantity=awaitupdateQuantity(newQuantity);startTransition(()=>{setQuantity(savedQuantity);});});}//……}原理剖析:useTransition的核心原理是将一部分状态更新处理为低优先级任务,这样可以将关键的高优先级任务先执行,而低优先级的过渡更新则会稍微延迟处理。这在渲染大量数据、进行复杂运算或处理长时间任务时特别有效。React通过调度机制来管理优先级:高优先级更新:直接影响用户体验的任务,比如表单输入、按钮点击等。低优先级更新:相对不影响交互的过渡性任务,比如大量数据渲染、动画等,这些任务可以延迟执行。+-----------------------+|App||||+--------------+|||Input|||+--------------+||||+--------------+|||Display|||+--------------+|+-----------------------+用户输入|v[高优先级更新]--->[调度器]--->[React更新组件]|+--->[低优先级过渡更新]-->[调度器]-->[等待处理]useDeferredValueuseDeferredValue是React18中引入的一个Hook,用于延迟获取某个值,以提高性能。它可以将某个值的获取推迟到下一个渲染周期,从而避免在当前渲染周期内进行不必要的计算。参数说明:value:要延迟获取的值。返回说明:返回一个代表延迟获取的值的对象。应用场景:性能优化:在某些情况下,获取某个值可能会引起性能问题,通过延迟获取可以避免不必要的计算,提高性能。优化渲染:在某些值不是立即需要的情况下,可以延迟获取以避免不必要的渲染。用法举例:importReact,{useDeferredValue,useState}from'react';constExampleComponent:React.FC=()=>{const[count,setCount]=useState(0);constdeferredCount=useDeferredValue(count);constincrementCount=()=>{setCount((prevCount)=>prevCount+1);};return(DeferredCount:{deferredCount}ActualCount:{count}IncrementCount);};exportdefaultExampleComponent;在这个示例中,我们使用useDeferredValue将count的值延迟获取。在每次用户点击增加按钮时,count的值会增加,但deferredCount的值会在下一个渲染周期才会更新。这样可以避免在当前渲染周期内进行不必要的计算,提高性能。通过使用useDeferredValue,我们可以延迟获取某个值,从而优化性能并减少不必要的渲染。这对于一些不是立即需要的值的场景非常有用。希望这个例子能够帮助您理解useDeferredValue的用法和应用场景。其他HookuseSyncExternalStoreuseSyncExternalStore是React18中引入的一个Hook,用于从外部存储(如Redux、MobX或其他状态管理库)中同步获取数据。它提供了一种安全的方式来订阅外部存储的变化,并确保组件在外部存储更新时能够正确地重新渲染。参数说明:useSyncExternalStore接受三个参数:subscribe:一个函数,用于订阅外部存储的变化。它应该返回一个取消订阅的函数。getSnapshot:一个函数,用于获取当前的快照值(即外部存储的当前状态)。getServerSnapshot(可选):在服务器端渲染时使用的函数,返回服务器快照的值。返回说明:返回外部存储的当前快照值。应用场景:与外部状态管理库集成:在使用Redux、MobX等状态管理库时,可以使用useSyncExternalStore来同步外部状态。确保一致性:确保在外部存储更新时,组件能够正确地重新渲染,避免不一致的状态。用法举例:一、订阅浏览器Api实现自定义hook(useStorage)我们实现一个useStorageHook,用于订阅localStorage数据。这样做的好处是,我们可以确保组件在localStorage数据发生变化时,自动更新同步。实现代码我们将创建一个useStorageHook,能够存储数据到localStorage,并在不同浏览器标签页之间同步这些状态。此Hook接收一个键值参数用于存储数据的键名,还可以接收一个默认值用于在无数据时的初始化。在hooks/useStorage.ts中定义useStorageHook:import{useSyncExternalStore}from"react"/****@paramkey存储到localStorage的key*@paramdefaultValue默认值*/exportconstuseStorage=(key:any,defaultValue?:any)=>{constsubscribe=(callback:()=>void)=>{window.addEventListener('storage',(e)=>{console.log('触发了',e)callback()})return()=>window.removeEventListener('storage',callback)}//从localStorage中获取数据如果读不到返回默认值constgetSnapshot=()=>{return(localStorage.getItem(key)?JSON.parse(localStorage.getItem(key)!):null)||defaultValue}//修改数据constsetStorage=(value:any)=>{localStorage.setItem(key,JSON.stringify(value))window.dispatchEvent(newStorageEvent('storage'))//手动触发storage事件}//返回数据constres=useSyncExternalStore(subscribe,getSnapshot)return[res,setStorage]}在App.tsx中,我们可以直接使用useStorage,来实现一个简单的计数器。值会存储在localStorage中,并且在刷新或其他标签页修改数据时自动更新。import{useStorage}from"./hooks/useStorage"constApp=()=>{const[val,setVal]=useStorage('data',1)return({val}setVal(val+1)}>设置val)}exportdefaultApp效果演示值的持久化:点击按钮增加val,页面刷新后依然会保留最新值。跨标签页同步:在多个标签页打开该应用时,任意一个标签页修改val,其他标签页会实时更新,保持同步状态。二、订阅history实现路由跳转实现一个简易的useHistoryHook,获取浏览器url信息+参数import{useSyncExternalStore}from"react"exportconstuseHistory=()=>{constsubscribe=(callback:()=>void)=>{window.addEventListener('popstate',callback)window.addEventListener('hashchange',callback)return()=>{window.removeEventListener('popstate',callback)window.removeEventListener('hashchange',callback)}}constgetSnapshot=()=>{returnwindow.location.href}constpush=(path:string)=>{window.history.pushState(null,'',path)window.dispatchEvent(newPopStateEvent('popstate'))}constreplace=(path:string)=>{window.history.replaceState(null,'',path)window.dispatchEvent(newPopStateEvent('popstate'))}constres=useSyncExternalStore(subscribe,getSnapshot)return[res,push,replace]asconst}使用useHistoryHook让我们在组件中使用这个useHistoryHook,实现基本的前进、后退操作以及程序化导航。import{useHistory}from"./hooks/useHistory"constApp=()=>{const[history,push,replace]=useHistory()return(当前url:{history}{push('/aaa')}}>跳转{replace('/bbb')}}>替换)}exportdefaultApp效果演示history:这是useHistory返回的当前路径值。每次URL变化时,useSyncExternalStore会自动触发更新,使history始终保持最新路径。push和replace:点击“跳转”按钮调用push("/aaa"),会将/aaa推入历史记录;点击“替换”按钮调用replace("/bbb"),则会将当前路径替换为/bbb。注意事项如果getSnapshot返回值不同于上一次,React会重新渲染组件。这就是为什么,如果总是返回一个不同的值,会进入到一个无限循环,并产生这个报错。Uncaught(inpromise)Error:Maximumupdatedepthexceeded.ThiscanhappenwhenacomponentrepeatedlycallssetStateinsidecomponentWillUpdateorcomponentDidUpdate.Reactlimitsthenumberofnestedupdatestopreventinfiniteloops.functiongetSnapshot(){returnmyStore.todos;//object}这种写法每次返回了对象的引用,即使这个对象没有改变,React也会重新渲染组件。如果你的store数据是可变的,getSnapshot函数应当返回一个它的不可变快照。这意味着确实需要创建新对象,但不是每次调用都如此。而是应当保存最后一次计算得到的快照,并且在store中的数据不变的情况下,返回与上一次相同的快照。如何决定可变数据发生了改变则取决于你的可变store。functiongetSnapshot(){if(myStore.todos!==lastTodos){//只有在todos真的发生变化时,才更新快照lastSnapshot={todos:myStore.todos.slice()};lastTodos=myStore.todos;}returnlastSnapshot;}useDebugValueuseDebugValue是React18中引入的一个Hook,用于为自定义Hook提供调试信息。它可以帮助开发者在开发过程中更好地理解自定义Hook的工作原理和状态。参数说明:value:要为其提供调试信息的值。format:一个函数,用于格式化value提供的调试信息。返回说明:无返回值。应用场景:自定义Hook调试:在自定义Hook中使用useDebugValue提供更多有关Hook内部状态的调试信息。开发调试工具:可以结合开发者工具使用,帮助开发者更好地理解Hook的工作原理。用法举例:importReact,{useDebugValue,useState}from'react';constuseCounter=(initialCount:number)=>{const[count,setCount]=useState(initialCount);useDebugValue(count,(count)=>`Currentcount:${count}`);constincrement=()=>{setCount((prevCount)=>prevCount+1);};return{count,increment};};constExampleComponent:React.FC=()=>{const{count,increment}=useCounter(0);return(Count:{count}IncrementCount);};exportdefaultExampleComponent;在这个示例中,我们定义了一个自定义HookuseCounter,其中使用useDebugValue提供了关于count值的调试信息。在调试工具中,开发者可以看到当前的计数值,帮助他们更好地理解Hook的工作原理。通过使用useDebugValue,我们可以为自定义Hook提供更多的调试信息,帮助开发者更好地理解Hook的状态和工作原理。这对于开发过程中的调试和问题排查非常有帮助。希望这个例子能够帮助您理解useDebugValue的用法和应用场景。useIduseId是React18中引入的一个Hook,主要用于生成唯一的ID,通常用于支持无障碍(accessibility)特性和确保组件在多个实例中正确关联。它可以帮助你在渲染过程中生成稳定的ID,避免了手动生成ID的复杂性和潜在的冲突。参数说明:useId的基本用法非常简单。它不需要任何参数,返回一个字符串形式的唯一ID。这个ID在组件的生命周期内是稳定的,即使组件重新渲染也不会改变。返回说明:useId返回一个字符串,代表唯一的ID。使用示例:以下是一个使用useId的简单示例,展示如何在表单中使用该ID来关联标签和输入字段:importReact,{useId}from'react';functionCustomInput(){constid=useId();//使用useId生成唯一IDreturn(Enteryourname:);}functionApp(){return(FormwithUniqueIDs{/*这里会生成不同的ID*/});}exportdefaultApp;注意事项:SSR兼容性:useId在服务器端渲染(SSR)和客户端渲染(CSR)中都能保持一致性。它会确保在服务器和客户端生成相同的ID,从而避免ID不一致的问题。多实例:在多个实例的情况下,useId会为每个实例生成不同的ID,这对于组件复用非常有用。不适用于动态生成的ID:如果你需要根据某些动态数据生成ID,建议使用其他方法,因为useId的目的是生成稳定的、可重用的ID。useId是一个非常实用的工具,特别是当你需要在组件中处理无障碍特性时,可以确保组件的可访问性和用户体验。useOptimisticuseOptimistic是一个ReactHook,它允许你在进行异步操作时显示不同state。它接受state作为参数,并返回该state的副本,在异步操作(如网络请求)期间可以不同。你需要提供一个函数,该函数接受当前state和操作的输入,并返回在操作挂起期间要使用的乐观状态。这个状态被称为“乐观”状态是因为通常用于立即向用户呈现执行操作的结果,即使实际上操作需要一些时间来完成。参数说明:state:初始时和没有挂起操作时要返回的值。updateFn(currentState,optimisticValue):一个函数,接受当前state和传递给addOptimistic的乐观值,并返回结果乐观状态。它必须是一个纯函数。updateFn接受两个参数:currentState和optimisticValue。返回值将是currentState和optimisticValue的合并值。返回说明:optimisticState:结果乐观状态。除非有操作挂起,否则它等于state,在这种情况下,它等于updateFn返回的值。addOptimistic:触发乐观更新时调用的dispatch函数。它接受一个可以是任何类型的参数optimisticValue,并以state和optimisticValue作为参数来调用updateFn。应用场景:useOptimisticHook提供了一种在后台操作(如网络请求)完成之前乐观地更新用户界面的方式。在表单的上下文中,这种技术有助于使应用程序在感觉上响应地更加快速。当用户提交表单时,界面立即更新为预期的结果,而不是等待服务器的响应来反映更改。例如,当用户在表单中输入消息并点击“发送”按钮时,useOptimisticHook允许消息立即出现在列表中,并带有“发送中……”标签,即使消息实际上还没有发送到服务器。这种“乐观”方法给人一种快速和响应灵敏的印象。然后,表单在后台尝试真正发送消息。一旦服务器确认消息已收到,“发送中……”标签就会被移除。用法举例:app.jsimport{useOptimistic,useState,useRef}from"react";import{deliverMessage}from"./actions.js";functionThread({messages,sendMessage}){constformRef=useRef();asyncfunctionformAction(formData){addOptimisticMessage(formData.get("message"));formRef.current.reset();awaitsendMessage(formData);}const[optimisticMessages,addOptimisticMessage]=useOptimistic(messages,(state,newMessage)=>[...state,{text:newMessage,sending:true}]);return({optimisticMessages.map((message,index)=>({message.text}{!!message.sending&&(发送中……)}))}发送);}exportdefaultfunctionApp(){const[messages,setMessages]=useState([{text:"你好,在这儿!",sending:false,key:1}]);asyncfunctionsendMessage(formData){constsentMessage=awaitdeliverMessage(formData.get("message"));setMessages((messages)=>[...messages,{text:sentMessage}]);}return;}actions.jsexportasyncfunctiondeliverMessage(message){awaitnewPromise((res)=>setTimeout(res,1000));returnmessage;}useActionStateuseActionState是一个可以根据某个表单动作的结果更新state的Hook。useFormStatususeFormStatus是一个提供上次表单提交状态信息的Hook。
-
Debian12 安装后首先应该做的事情-更换软件源Linux一、备份当前软件源cp/etc/apt/sources.list/etc/apt/sources.list.bak二、修改当前软件源#需要root权限vim/etc/apt/sources.list三、替换软件源内容#中科大镜像站debhttps://mirrors.ustc.edu.cn/debian/bookwormmaincontribnon-freenon-free-firmwaredeb-srchttps://mirrors.ustc.edu.cn/debian/bookwormmaincontribnon-freenon-free-firmwaredebhttps://mirrors.ustc.edu.cn/debian/bookworm-updatesmaincontribnon-freenon-free-firmwaredeb-srchttps://mirrors.ustc.edu.cn/debian/bookworm-updatesmaincontribnon-freenon-free-firmwaredebhttps://mirrors.ustc.edu.cn/debian/bookworm-backportsmaincontribnon-freenon-free-firmwaredeb-srchttps://mirrors.ustc.edu.cn/debian/bookworm-backportsmaincontribnon-freenon-free-firmwaredebhttps://mirrors.ustc.edu.cn/debian-security/bookworm-securitymaincontribnon-freenon-free-firmwaredeb-srchttps://mirrors.ustc.edu.cn/debian-security/bookworm-securitymaincontribnon-freenon-free-firmware#阿里云镜像站debhttps://mirrors.aliyun.com/debian/bookwormmainnon-freenon-free-firmwarecontribdeb-srchttps://mirrors.aliyun.com/debian/bookwormmainnon-freenon-free-firmwarecontribdebhttps://mirrors.aliyun.com/debian-security/bookworm-securitymaindeb-srchttps://mirrors.aliyun.com/debian-security/bookworm-securitymaindebhttps://mirrors.aliyun.com/debian/bookworm-updatesmainnon-freenon-free-firmwarecontribdeb-srchttps://mirrors.aliyun.com/debian/bookworm-updatesmainnon-freenon-free-firmwarecontribdebhttps://mirrors.aliyun.com/debian/bookworm-backportsmainnon-freenon-free-firmwarecontribdeb-srchttps://mirrors.aliyun.com/debian/bookworm-backportsmainnon-freenon-free-firmwarecontrib#腾讯云镜像站debhttps://mirrors.tencent.com/debian/bookwormmainnon-freenon-free-firmwarecontribdeb-srchttps://mirrors.tencent.com/debian/bookwormmainnon-freenon-free-firmwarecontribdebhttps://mirrors.tencent.com/debian-security/bookworm-securitymaindeb-srchttps://mirrors.tencent.com/debian-security/bookworm-securitymaindebhttps://mirrors.tencent.com/debian/bookworm-updatesmainnon-freenon-free-firmwarecontribdeb-srchttps://mirrors.tencent.com/debian/bookworm-updatesmainnon-freenon-free-firmwarecontribdebhttps://mirrors.tencent.com/debian/bookworm-backportsmainnon-freenon-free-firmwarecontribdeb-srchttps://mirrors.tencent.com/debian/bookworm-backportsmainnon-freenon-free-firmwarecontrib#华为镜像站debhttps://mirrors.huaweicloud.com/debian/bookwormmainnon-freenon-free-firmwarecontribdeb-srchttps://mirrors.huaweicloud.com/debian/bookwormmainnon-freenon-free-firmwarecontribdebhttps://mirrors.huaweicloud.com/debian-security/bookworm-securitymaindeb-srchttps://mirrors.huaweicloud.com/debian-security/bookworm-securitymaindebhttps://mirrors.huaweicloud.com/debian/bookworm-updatesmainnon-freenon-free-firmwarecontribdeb-srchttps://mirrors.huaweicloud.com/debian/bookworm-updatesmainnon-freenon-free-firmwarecontribdebhttps://mirrors.huaweicloud.com/debian/bookworm-backportsmainnon-freenon-free-firmwarecontribdeb-srchttps://mirrors.huaweicloud.com/debian/bookworm-backportsmainnon-freenon-free-firmwarecontrib#清华大学镜像站debhttps://mirrors.tuna.tsinghua.edu.cn/debian/bookwormmaincontribnon-freenon-free-firmwaredeb-srchttps://mirrors.tuna.tsinghua.edu.cn/debian/bookwormmaincontribnon-freenon-free-firmwaredebhttps://mirrors.tuna.tsinghua.edu.cn/debian/bookworm-updatesmaincontribnon-freenon-free-firmwaredeb-srchttps://mirrors.tuna.tsinghua.edu.cn/debian/bookworm-updatesmaincontribnon-freenon-free-firmwaredebhttps://mirrors.tuna.tsinghua.edu.cn/debian/bookworm-backportsmaincontribnon-freenon-free-firmwaredeb-srchttps://mirrors.tuna.tsinghua.edu.cn/debian/bookworm-backportsmaincontribnon-freenon-free-firmwaredebhttps://mirrors.tuna.tsinghua.edu.cn/debian-securitybookworm-securitymaincontribnon-freenon-free-firmwaredeb-srchttps://mirrors.tuna.tsinghua.edu.cn/debian-securitybookworm-securitymaincontribnon-freenon-free-firmware#Debian官方维护镜像站debhttp://security.debian.org/debian-securitybookworm-securitymainnon-free-firmwaredeb-srchttp://security.debian.org/debian-securitybookworm-securitymainnon-free-firmware四、更新源sudoaptupdate五、扩展-关于软件源参数说明1.deb表示二进制软件包,指示系统下载已经编译好的软件包进行安装。这些二进制软件包包含了可以直接在系统上运行的可执行文件、库文件等。deb软件包适用于普通用户安装和使用。2.deb-src表示源代码软件包,指示系统下载软件的源代码。用户可以使用这些源代码进行编译、修改或分析,然后再生成可执行文件。deb-src软件包适用于开发人员、编译器和需要对软件进行定制或调试的用户。3.bookworm是Debian的一个版本代号,通常用来指代Debian的下一个发行版。每个Debian版本都有一个代号,例如Debian11的代号是Bullseye。在这个软件源中,bookworm指示这个源适用于Debian的bookworm版本,也就是Debian的下一个发行版。4.主要软件库(main):包含了由Debian项目维护的自由软件,这些软件遵守Debian自由软件指导方针,用户可以自由使用、修改和分发。5.贡献软件库(contrib):包含了由社区维护的自由软件,这些软件也遵守Debian自由软件指导方针,但是不属于Debian项目正式维护的软件。6.非自由软件库(non-free):包含了一些不完全符合Debian自由软件指导方针的软件,这些软件可能包含一些限制或专有内容,用户需要自行权衡是否使用这些软件。7.非自由固件库(non-free-firmware):包含了一些硬件设备的固件,这些固件可能包含专有内容或限制,这些固件通常用于支持硬件设备的正常运行。用户需要自行权衡是否使用这些固件。六、扩展-把本地光盘当做源来使用vim/etc/apt/sources.list#在第一行插入下行内容deb[trusted=yes]file:///mnt/usb1bookwormmainnon-free-firmware#挂载光盘mkdir/mnt/usb1mount/dev/sr0/mnt/usb1
最新文章
-
Vite6+React18+Ts项目-17.API请求时的防抖与节流
axios防抖与节流方案在Axios中实现防抖(debounce)和节流(throttle)可以有效控制API请求频率,避免不必要的网络请求。以下是几种实现方案...
-
Vite6+React18+Ts项目-16.国际化进阶之 i18next 多语言
ant支持的语言地址:https://ant.design/docs/react/i18n-cn语言文件名阿拉伯语ar_EG阿塞拜疆语az_AZ保加利亚语bg_...
-
浏览器语言编码
语言代码通常遵循ISO639标准,包含:两个字母的代码(如en,zh)+可选的地区代码(如en-US,zh-CN)当网站涉及国际化时,需要处理针对浏览器语言进行...
-
今天写代码时,突然看到 “!!”运算符,一时竟然没反应过来,再来复习一下?
!!this.isVisit是一种常见的JavaScript编程技巧,用于将this.isVisit的值强制转换为布尔类型(true或false)。它的作用是通...
-
Vite6+React18+Ts项目-15.几种状态传递方法
1.父子组件之间状态传递最基本的父子组件间状态传递方式是通过Props传递ParentComponent.tsximportReactfrom'react';i...
-
Vite6+React18+Ts项目-14.浏览器hover事件React原生实现及使用ahooks钩子实现
在现代Web开发中,hover交互效果是提升用户体验的重要元素。本文将探讨在React18TypeScript项目中实现hover效果的两种主流方式:React...
-
Vite6+React18+Ts项目-13.使用iconfont图标库,封装Icon组件
在前端开发中,图标是不可或缺的UI元素。本文介绍了如何利用iconfont图标库,并封装一个灵活易用的ReactIcon组件。1.为什么选择iconfont?i...
-
Vite6+React18+Ts项目-12.简单封装后台GuardAuth组件,控制页面内容和按钮是否显示
权限控制是一个常见的需求。我们需要根据用户的权限级别来决定是否显示某些页面内容或功能按钮。本文将介绍如何封装一个简单的GuardAuth组件来实现这一功能。1....
-
Vite6+React18+Ts项目-11.封装加载中及加载异常组件
在现代前端开发中,优雅地处理异步数据加载状态是提升用户体验的关键因素之一。本文将介绍如何在Vite6+React18+TypeScript项目模板中封装一个通用...
-
Vite6+React18+Ts项目-10.封装阿里ahooks里的useRequest方法,统一增加loadingDelay防抖动
在前端开发中,数据请求是常见的操作,而阿里开源的ahooks库中的useRequest是一个非常实用的ReactHook,它简化了数据请求的管理。但在实际使用中...