You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
currentlyRenderingFiber: 其实就是workInProgressTree, 即更新时链表当前正在遍历的fiber节点(源码注释:The work-in-progress fiber. I've named it differently to distinguish it from the work-in-progress hook);
/*Hooks are stored as a linked list on the fiber's memoizedState field. hooks 以链表的形式存储在fiber节点的memoizedState属性上The current hook list is the list that belongs to the current fiber.当前的hook链表就是当前正在遍历的fiber节点上的The work-in-progress hook list is a new list that will be added to the work-in-progress fiber.work-in-progress hook 就是即将被添加到正在遍历fiber节点的hooks新链表*/letcurrentHook: Hook|null=null;letnextCurrentHook: Hook|null=null;
functiondispatchAction(fiber,queue,action){// 只贴了相关代码constupdate={
expirationTime,
suspenseConfig,
action,eagerReducer: null,eagerState: null,next: null,};// Append the update to the end of the list.constlast=queue.last;if(last===null){// This is the first update. Create a circular list.update.next=update;}else{constfirst=last.next;if(first!==null){// Still circular.update.next=first;}last.next=update;}queue.last=update;// 触发更新scheduleWork(fiber,expirationTime);}
开篇有奖
如果你最近一年出去面过试,很可能面临这些问题:
第一个问题如果你提到了Fiber reconciler,fiber,链表,新的什么周期,可能在面试官眼里这仅仅是一个及格的回答。以下是我整理的,自我感觉还良好的回答:
分三步:
命令式
编程转变为声明式
编程,即所谓的数据驱动视图,但如果简单粗暴的操作,比如讲生成的html直接采用innerHtml替换,会带来重绘重排
之类的性能问题。为了尽量提高性能,React团队引入了虚拟dom,即采用js对象来描述dom树,通过对比前后两次的虚拟对象,来找到最小的dom操作(vdom diff),以此提高性能。stack reconciler
,它是一个递归的过程,在树很深的时候,单次diff时间过长会造成JS线程持续被占用,用户交互响应迟滞,页面渲染会出现明显的卡顿,这在现代前端是一个致命的问题。所以为了解决这种问题,react 团队对整个架构进行了调整,引入了fiber架构,将以前的stack reconciler替换为fiber reconciler
。采用增量式渲染
。引入了任务优先级(expiration)
和requestIdleCallback
的循环调度算法,简单来说就是将以前的一根筋diff更新,首先拆分成两个阶段:reconciliation
与commit
;第一个reconciliation
阶段是可打断的,被拆分成一个个的小任务(fiber),在每一侦的渲染空闲期做小任务diff。然后是commit阶段,这个阶段是不拆分且不能打断的,将diff节点的effectTag一口气更新到页面上。workInprogressTree
(future vdom) 与oldTree
(current vdom)两个链表,两个链表相互引用。这无形中又解决了另一个问题,当workInprogressTree生成报错时,这时也不会导致页面渲染崩溃,而只是更新失败,页面仍然还在。以上就是我上半年面试自己不断总结迭代出的答案,希望能对你有所启发。
接着来回答第二个问题,hooks本质是什么?
hooks 为什么出现
当我们在谈论React这个UI库时,最先想到的是,数据驱动视图,简单来讲就是下面这个公式:
我们开发的整个应用,都是很多组件组合而成,这些组件是纯粹,不具备扩展的。因为React不能像普通类一样直接继承,从而达到功能扩展的目的。
出现前的逻辑复用
在用react实现业务时,我们复用一些组件逻辑去扩展另一个组件,最常见比如Connect,Form.create, Modal。这类组件通常是一个容器,容器内部封装了一些通用的功能(非视觉的占多数),容器里面的内容由被包装的组件自己定制,从而达到一定程度的逻辑复用。
在hooks 出现之前,解决这类需求最常用的就两种模式:
HOC高阶组件
和Render Props
。高阶组件类似于JS中的高阶函数,即输入一个函数,返回一个
新的
函数, 比如React-Redux中的Connect:高阶组件由于每次都会返回一个新的组件,对于react来说,这是不利于diff和状态复用的,所以高阶组件的包装不能在render 方法中进行,而只能像上面那样在组件声明时包裹,这样也就不利于动态传参。而Render Props模式的出现就完美解决了这个问题,其原理就是将要包裹的组件作为props属性传入,然后容器组件调用这个属性,并向其传参, 最常见的用
props.children
来做这个属性。举个🌰:更多关于render 与 Hoc,可以参见以前写的一片弱文:React进阶,写中后台也能写出花
已存方案的问题
嵌套地狱
上面提到的高阶组件和RenderProps, 看似解决了逻辑复用的问题,但面对复杂需求时,即一个组件需要使用多个复用逻辑包裹时,两种方案都会让我们的代码陷入常见的
嵌套地狱
, 比如:除了嵌套地狱的写法让人困惑,但更致命的深度会直接影响react组件更新时的diff性能。
函数式编程的普及
Hooks 出现前的函数式组件只是以模板函数存在,而前面两种方案,某种程度都是依赖类组件来完成。而提到了类,就不得不想到下面这些痛点:
所以React团队回归
view = fn(state)
的初心,希望函数式组件也能拥有状态管理的能力,让逻辑复用变得更简单,更纯粹。架构的更新
为什么在React 16前,函数式组件不能拥有状态管理?其本质是因为16以前只有类组件在更新时存在实例,而16以后Fiber 架构的出现,让每一个节点都拥有对应的实例,也就拥有了保存状态的能力,下面会详讲。
hooks 的本质
有可能,你听到过Hooks的本质就是
闭包
。但是,如果满分100的话,这个说法最多只能得60分。哪满分答案是什么呢?闭包 + 两级
链表
。下面就来一一分解, 下面都以useState来举例剖析。
闭包
JS 中闭包是难点,也是必考点,概括的讲就是:
以上面的示例来讲,闭包就是setAge这个函数,何以见得呢,看组件挂载阶段hook执行的源码:
所以这个函数就是mountReducer,而产生的闭包就是dispatch函数(对应上面的setAge),被闭包引用的变量就是
currentlyRenderingFiber
与queue
。The work-in-progress fiber. I've named it differently to distinguish it from the work-in-progress hook
);这个闭包将 fiber节点与action, action 与 state很好的串联起来了,举上面的例子就是:
ok,到这,闭包就讲完了。
第一个链表:hooks
在ReactFiberHooks文件开头声明currentHook变量的源码有这样一段注释。
从上面的源码注释可以看出hooks链表与fiber链表是极其相似的;也得知hooks 链表是保存在fiber节点的memoizedState属性的, 而赋值是在renderWithHooks函数具体实现的;
有可能代码贴了这么多,你还没反应过来这个hooks 链表具体指什么?
其实就是指一个组件包含的hooks, 比如上面示例中的:
形成的链表就是下面这样的:
所以在下一次更新时,再次执行hook,就会去获取当前运行节点的hooks链表;
到这 hooks 链表是什么,应该就明白了;这时你可能会更明白,为什么hooks不能在循环,判断语句中调用,而只能在函数最外层使用,因为挂载或则更新时,这个队列需要是一致的,才能保证hooks的结果正确。
第二个链表:state
其实state 链表不是hooks独有的,类操作的setState也存在,正是由于这个链表存在,所以有一个经(sa)典(bi)React 面试题:
结合实例来看,当点击增加会执行三次setAge
第一次执行完dispatch后,会形成一个状态待执行任务链表:
如果仔细观察,会发现这个链表还是一个
环
(会在updateReducer后断开), 这一块设计相当有意思,我现在也还没搞明白为什么需要环,值得细品,而建立这个链表的逻辑就在dispatchAction函数中。上面已经说了,执行setAge 只是形成了状态待执行任务链表,真正得到最终状态,其实是在下一次更新(获取状态)时,即:
而获取最新状态的相关代码逻辑存在于updateReducer中:
最后来看,状态更新的逻辑似乎是最绕的。但如果看过setState,这一块可能就比较容易。至此,第二个链表state就理清楚了。
结
读到这里,你就应该明白hooks 到底是怎么实现的:
虽然我这里只站在useState这个hooks做了剖析,但其他hooks的实现基本类似。
另外分享一下在我眼中的hooks,与类组件到底到底是什么联系:
子组件的Effect先执行
), Update需要deps依赖来唤起;闭包
的坑Rerender
第一次写源码解析,出发点主要两点:
文章中若有不详或不对之处,欢迎斧正;
推荐阅读: 源码解析React Hook构建过程:没有设计就是最好的设计
The text was updated successfully, but these errors were encountered: