处于对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
组件内部主动或被动触发hooksAPI, 进而通过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提供了hooksAPI, 可以方便的在函数组件中定义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里面吧:
总结