处于对hooks
原理的求知心理, 所以大力研究函数
组件的更新机制.
更新
[2019-7-17]
前言
上一篇记录了ReactDOM.render
的具体流程, 到了beginWork
, 也就是react根据当前fiber节点的各种属性, 来做不同的更新处理.
这篇issue主要记录下react对于函数组件(FunctionComponent
)的处理机制.
流程
beginWork beginWork
内部有一个值得注意的地方, 那就是fiber.elementType
和fiber.type
, 这两者有什么区别?
fiber.elementType
对应createElement
的第一个参数type
. 而fiber.type
则是为了区分Suspense
组件. 正常情况下两者是相等的, 当存在按需
组件, 也就是Suspense
时, fiber.type
的值为null.
展开源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function beginWork ( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ): Fiber | null { switch (workInProgress.tag) { case FunctionComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateFunctionComponent( current, workInProgress, Component, resolvedProps, renderExpirationTime, ); } } }
updateFunctionComponent 顾名思义, 函数组件更新入口
展开源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function updateFunctionComponent ( current, workInProgress, Component, nextProps: any, renderExpirationTime, ) { let nextChildren; nextChildren = renderWithHooks( current, workInProgress, Component, nextProps, context, renderExpirationTime, ); reconcileChildren( current, workInProgress, nextChildren, renderExpirationTime, ); return workInProgress.child; }
renderWithHooks 在整个react应用渲染到某个函数
组件的时候, 在reconcileChildren
, 也就是解析其子节点之前, 会使用hooks
进行渲染, 此时的渲染过程是这样的:
renderHooks
由于挂载和更新阶段执行的生命周期可能有所不同, 所以判断当前处于mount
或update
阶段,
mount
ReactCurrentDispatcher.current = HooksDispatcherOnMount
update
ReactCurrentDispatcher.current = HooksDispatcherOnUpdate
组件内部主动
或被动
触发hooks
API, 进而通过ReactCurrentDispatcher
来处理当前组件的更新
而对于ReactCurrentDispatcher
, 则是hooks
和组件的桥梁.
展开源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 export function renderWithHooks ( current: Fiber | null, workInProgress: Fiber, Component: any, props: any, refOrContext: any, nextRenderExpirationTime: ExpirationTime, ): any { renderExpirationTime = nextRenderExpirationTime; currentlyRenderingFiber = workInProgress; nextCurrentHook = current !== null ? current.memoizedState : null ; ReactCurrentDispatcher.current = nextCurrentHook === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate; let children = Component(props, refOrContext); if (didScheduleRenderPhaseUpdate) { do { didScheduleRenderPhaseUpdate = false ; numberOfReRenders += 1 ; nextCurrentHook = current !== null ? current.memoizedState : null ; nextWorkInProgressHook = firstWorkInProgressHook; currentHook = null ; workInProgressHook = null ; componentUpdateQueue = null ; ReactCurrentDispatcher.current = __DEV__ ? HooksDispatcherOnUpdateInDEV : HooksDispatcherOnUpdate; children = Component(props, refOrContext); } while (didScheduleRenderPhaseUpdate); renderPhaseUpdates = null ; numberOfReRenders = 0 ; } return children; }
hooks react@18
提供了hooks
API, 可以方便的在函数组件中定义state
、ref
等, 那么react为什么耗尽心思推出这个东西? 它解决了什么痛点? 说下我自己的看法吧:
1. 代码冗余
一个简单的例子. 在传统的class
类组件中, 可能会在componentDidMount
时开启定时器
、事件监听器
、Observable
, 在componentWillUnMount
时移除. 这时问题就来了, 设置
和监听
分别需要调用两个生命周期函数, 显得繁杂.
而hooks
函数组件则很好的解决了这个问题, useEffect
很好的模拟了componentDidMount
、componentDidUpdate
、componentWillUnmount
等生命周期, 可以用最少的代码完成相同的工作.
2. 可维护性
这可谓是突破性
的变化.
我自己的毕设全面使用hooks
来完成, 可能是由于项目小的缘故吧, 我永远体会不到class
组件充满了state
的情形, 可以想象一下一个文件塞满了成百上千
行代码, 会有多心累.
hooks
同样解决了这个问题, 无关紧要、不需要提升的state全部交由function
函数组件内部维护, 自然而然地减少了class
组件内部的代码量, 可维护性大大提高.
3. 编译性能
由于暂时没有深入v8
引擎, 对于class
和function
的编译性能无法做出完美解释. 而正常情况下, class
组件的编译性能要远远低于function
组件.
而具体的对于hooks
的结构、原理、机制, 为了避免篇幅过长, 还是记录到其它issue
里面吧:
总结