第一次加载页面

image.png

image.png
image.png

发生事件点击后

image.png
image.png

image.png
commitRoot(root)
root.current
// 包含有 effect fiber 环形链表
root.current.nextEffect
root.current.firstEffect
root.current.lastEffect

卡颂老师课件

https://www.bilibili.com/video/BV1Ki4y1u7Vr?from=search&seid=8355098943507069182
image.png

React16架构可以分为三层:

  • Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler (requestIdleCallback放弃使用)
  • Reconciler(协调器)—— 负责找出变化的组件
  • Renderer(渲染器)—— 负责将变化的组件渲染到页面上
  • Reconciler工作的阶段被称为render阶段。因为在该阶段会调用组件的render方法。
  • Renderer工作的阶段被称为commit阶段。就像你完成一个需求的编码后执行git commit提交代码。commit阶段会把render阶段提交的信息渲染在页面上。
  • render与commit阶段统称为work,即React在工作中。相对应的,如果任务正在Scheduler内调度,就不属于work。

image.png

相关执行流程博文

https://segmentfault.com/a/1190000037447202

代码主线跟踪

  1. // 以 useState 为例 代码如下:
  2. import { useState } from "react";
  3. import "./styles.css";
  4. export default function App() {
  5. const [state, setState] = useState(0);
  6. return (
  7. <div className="App">
  8. <h1 onClick={() => setState(state + 1)}>
  9. Hello CodeSandbox state:{state}
  10. </h1>
  11. <h2>Start editing to see some magic happen!</h2>
  12. </div>
  13. );
  14. }
  15. // setState -> dispatchAction->创建 update ->scheduleUpdateOnFiber-> diff 算法(与其他scheduleUpdateOnFiber抢优先级) ->得出需要更新的 dom(新增,修改,删除)
  16. // ->操作完了以后,render 阶段结束
  17. // -> 然后进入 commit 阶段 执行 commitRoot(root)同步方法
  18. /*
  19. * 相关代码引用 HooksDispatcherOnMount 另外还有 HooksDispatcherOnUpdate
  20. */
  21. const HooksDispatcherOnMount: Dispatcher = {
  22. readContext,
  23. useCallback: mountCallback,
  24. useContext: readContext,
  25. useEffect: mountEffect,
  26. useImperativeHandle: mountImperativeHandle,
  27. useLayoutEffect: mountLayoutEffect,
  28. useMemo: mountMemo,
  29. useReducer: mountReducer,
  30. useRef: mountRef,
  31. useState: mountState,
  32. useDebugValue: mountDebugValue,
  33. useDeferredValue: mountDeferredValue,
  34. useTransition: mountTransition,
  35. useMutableSource: mountMutableSource,
  36. useOpaqueIdentifier: mountOpaqueIdentifier,
  37. unstable_isNewReconciler: enableNewReconciler,
  38. };
  39. function mountState<S>(
  40. initialState: (() => S) | S,
  41. ): [S, Dispatch<BasicStateAction<S>>] {
  42. const hook = mountWorkInProgressHook();
  43. if (typeof initialState === 'function') {
  44. // $FlowFixMe: Flow doesn't like mixed types
  45. initialState = initialState();
  46. }
  47. hook.memoizedState = hook.baseState = initialState;
  48. const queue = (hook.queue = {
  49. pending: null,
  50. interleaved: null,
  51. lanes: NoLanes,
  52. dispatch: null,
  53. lastRenderedReducer: basicStateReducer,
  54. lastRenderedState: (initialState: any),
  55. });
  56. const dispatch: Dispatch<
  57. BasicStateAction<S>,
  58. > = (queue.dispatch = (dispatchAction.bind(
  59. null,
  60. currentlyRenderingFiber,
  61. queue,
  62. ): any));
  63. return [hook.memoizedState, dispatch];
  64. }
  65. function dispatchAction(fiber, queue, action){
  66. // ....构建相关 fiber 参数
  67. scheduleUpdateOnFiber(fiber, lane, eventTime)
  68. }
  69. function FiberNode (tag, key) {
  70. // 节点 key,主要用于了优化列表 diff
  71. this.key = key
  72. // 节点类型;FunctionComponent: 0, ClassComponent: 1, HostRoot: 3 ...
  73. this.tag = tag
  74. // 子节点
  75. this.child = null
  76. // 父节点
  77. this.return = null
  78. // 兄弟节点
  79. this.sibling = null
  80. // 更新队列,用于暂存 setState 的值
  81. this.updateQueue = null
  82. // 新传入的 props
  83. this.pendingProps = pendingProps;
  84. // 之前的 props
  85. this.memoizedProps = null;
  86. // 之前的 state
  87. this.memoizedState = null;
  88. // 节点更新过期时间,用于时间分片
  89. // react 17 改为:lanes、childLanes
  90. this.expirationTime = NoLanes
  91. this.childExpirationTime = NoLanes
  92. // 对应到页面的真实 DOM 节点
  93. this.stateNode = null
  94. // Fiber 节点的副本,可以理解为备胎,主要用于提升更新的性能
  95. this.alternate = null
  96. // 副作用相关,用于标记节点是否需要更新
  97. // 以及更新的类型:替换成新节点、更新属性、更新文本、删除……
  98. this.effectTag = NoEffect
  99. // 指向下一个需要更新的节点
  100. this.nextEffect = null
  101. this.firstEffect = null
  102. this.lastEffect = null
  103. }
  104. function commitRootImpl(root, renderPriorityLevel) {
  105. do {
  106. // `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
  107. // means `flushPassiveEffects` will sometimes result in additional
  108. // passive effects. So we need to keep flushing in a loop until there are
  109. // no more pending effects.
  110. // TODO: Might be better if `flushPassiveEffects` did not automatically
  111. // flush synchronous work at the end, to avoid factoring hazards like this.
  112. flushPassiveEffects();
  113. } while (rootWithPendingPassiveEffects !== null);
  114. flushRenderPhaseStrictModeWarningsInDEV();
  115. invariant(
  116. (executionContext & (RenderContext | CommitContext)) === NoContext,
  117. 'Should not already be working.',
  118. );
  119. const finishedWork = root.finishedWork;
  120. const lanes = root.finishedLanes;
  121. if (__DEV__) {
  122. if (enableDebugTracing) {
  123. logCommitStarted(lanes);
  124. }
  125. }
  126. if (enableSchedulingProfiler) {
  127. markCommitStarted(lanes);
  128. }
  129. if (finishedWork === null) {
  130. if (__DEV__) {
  131. if (enableDebugTracing) {
  132. logCommitStopped();
  133. }
  134. }
  135. if (enableSchedulingProfiler) {
  136. markCommitStopped();
  137. }
  138. return null;
  139. } else {
  140. if (__DEV__) {
  141. if (lanes === NoLanes) {
  142. console.error(
  143. 'root.finishedLanes should not be empty during a commit. This is a ' +
  144. 'bug in React.',
  145. );
  146. }
  147. }
  148. }
  149. root.finishedWork = null;
  150. root.finishedLanes = NoLanes;
  151. invariant(
  152. finishedWork !== root.current,
  153. 'Cannot commit the same tree as before. This error is likely caused by ' +
  154. 'a bug in React. Please file an issue.',
  155. );
  156. // commitRoot never returns a continuation; it always finishes synchronously.
  157. // So we can clear these now to allow a new callback to be scheduled.
  158. root.callbackNode = null;
  159. root.callbackPriority = NoLane;
  160. // Update the first and last pending times on this root. The new first
  161. // pending time is whatever is left on the root fiber.
  162. let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
  163. markRootFinished(root, remainingLanes);
  164. if (root === workInProgressRoot) {
  165. // We can reset these now that they are finished.
  166. workInProgressRoot = null;
  167. workInProgress = null;
  168. workInProgressRootRenderLanes = NoLanes;
  169. } else {
  170. // This indicates that the last root we worked on is not the same one that
  171. // we're committing now. This most commonly happens when a suspended root
  172. // times out.
  173. }
  174. // If there are pending passive effects, schedule a callback to process them.
  175. // Do this as early as possible, so it is queued before anything else that
  176. // might get scheduled in the commit phase. (See #16714.)
  177. // TODO: Delete all other places that schedule the passive effect callback
  178. // They're redundant.
  179. if (
  180. (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
  181. (finishedWork.flags & PassiveMask) !== NoFlags
  182. ) {
  183. if (!rootDoesHavePassiveEffects) {
  184. rootDoesHavePassiveEffects = true;
  185. scheduleCallback(NormalSchedulerPriority, () => {
  186. flushPassiveEffects();
  187. return null;
  188. });
  189. }
  190. }
  191. // Check if there are any effects in the whole tree.
  192. // TODO: This is left over from the effect list implementation, where we had
  193. // to check for the existence of `firstEffect` to satisfy Flow. I think the
  194. // only other reason this optimization exists is because it affects profiling.
  195. // Reconsider whether this is necessary.
  196. const subtreeHasEffects =
  197. (finishedWork.subtreeFlags &
  198. (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
  199. NoFlags;
  200. const rootHasEffect =
  201. (finishedWork.flags &
  202. (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
  203. NoFlags;
  204. if (subtreeHasEffects || rootHasEffect) {
  205. const prevTransition = ReactCurrentBatchConfig.transition;
  206. ReactCurrentBatchConfig.transition = 0;
  207. const previousPriority = getCurrentUpdatePriority();
  208. setCurrentUpdatePriority(DiscreteEventPriority);
  209. const prevExecutionContext = executionContext;
  210. executionContext |= CommitContext;
  211. // Reset this to null before calling lifecycles
  212. ReactCurrentOwner.current = null;
  213. // The commit phase is broken into several sub-phases. We do a separate pass
  214. // of the effect list for each phase: all mutation effects come before all
  215. // layout effects, and so on.
  216. // The first phase a "before mutation" phase. We use this phase to read the
  217. // state of the host tree right before we mutate it. This is where
  218. // getSnapshotBeforeUpdate is called.
  219. const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
  220. root,
  221. finishedWork,
  222. );
  223. if (enableProfilerTimer) {
  224. // Mark the current commit time to be shared by all Profilers in this
  225. // batch. This enables them to be grouped later.
  226. recordCommitTime();
  227. }
  228. if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
  229. // Track the root here, rather than in commitLayoutEffects(), because of ref setters.
  230. // Updates scheduled during ref detachment should also be flagged.
  231. rootCommittingMutationOrLayoutEffects = root;
  232. }
  233. // The next phase is the mutation phase, where we mutate the host tree.
  234. commitMutationEffects(root, finishedWork, lanes);
  235. if (enableCreateEventHandleAPI) {
  236. if (shouldFireAfterActiveInstanceBlur) {
  237. afterActiveInstanceBlur();
  238. }
  239. }
  240. resetAfterCommit(root.containerInfo);
  241. // The work-in-progress tree is now the current tree. This must come after
  242. // the mutation phase, so that the previous tree is still current during
  243. // componentWillUnmount, but before the layout phase, so that the finished
  244. // work is current during componentDidMount/Update.
  245. root.current = finishedWork;
  246. // The next phase is the layout phase, where we call effects that read
  247. // the host tree after it's been mutated. The idiomatic use case for this is
  248. // layout, but class component lifecycles also fire here for legacy reasons.
  249. if (__DEV__) {
  250. if (enableDebugTracing) {
  251. logLayoutEffectsStarted(lanes);
  252. }
  253. }
  254. if (enableSchedulingProfiler) {
  255. markLayoutEffectsStarted(lanes);
  256. }
  257. commitLayoutEffects(finishedWork, root, lanes);
  258. if (__DEV__) {
  259. if (enableDebugTracing) {
  260. logLayoutEffectsStopped();
  261. }
  262. }
  263. if (enableSchedulingProfiler) {
  264. markLayoutEffectsStopped();
  265. }
  266. if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
  267. rootCommittingMutationOrLayoutEffects = null;
  268. }
  269. // Tell Scheduler to yield at the end of the frame, so the browser has an
  270. // opportunity to paint.
  271. requestPaint();
  272. executionContext = prevExecutionContext;
  273. // Reset the priority to the previous non-sync value.
  274. setCurrentUpdatePriority(previousPriority);
  275. ReactCurrentBatchConfig.transition = prevTransition;
  276. } else {
  277. // No effects.
  278. root.current = finishedWork;
  279. // Measure these anyway so the flamegraph explicitly shows that there were
  280. // no effects.
  281. // TODO: Maybe there's a better way to report this.
  282. if (enableProfilerTimer) {
  283. recordCommitTime();
  284. }
  285. }
  286. const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
  287. if (rootDoesHavePassiveEffects) {
  288. // This commit has passive effects. Stash a reference to them. But don't
  289. // schedule a callback until after flushing layout work.
  290. rootDoesHavePassiveEffects = false;
  291. rootWithPendingPassiveEffects = root;
  292. pendingPassiveEffectsLanes = lanes;
  293. }
  294. // Read this again, since an effect might have updated it
  295. remainingLanes = root.pendingLanes;
  296. // Check if there's remaining work on this root
  297. if (remainingLanes === NoLanes) {
  298. // If there's no remaining work, we can clear the set of already failed
  299. // error boundaries.
  300. legacyErrorBoundariesThatAlreadyFailed = null;
  301. }
  302. if (__DEV__ && enableStrictEffects) {
  303. if (!rootDidHavePassiveEffects) {
  304. commitDoubleInvokeEffectsInDEV(root.current, false);
  305. }
  306. }
  307. if (includesSomeLane(remainingLanes, (SyncLane: Lane))) {
  308. if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
  309. markNestedUpdateScheduled();
  310. }
  311. // Count the number of times the root synchronously re-renders without
  312. // finishing. If there are too many, it indicates an infinite update loop.
  313. if (root === rootWithNestedUpdates) {
  314. nestedUpdateCount++;
  315. } else {
  316. nestedUpdateCount = 0;
  317. rootWithNestedUpdates = root;
  318. }
  319. } else {
  320. nestedUpdateCount = 0;
  321. }
  322. onCommitRootDevTools(finishedWork.stateNode, renderPriorityLevel);
  323. if (enableUpdaterTracking) {
  324. if (isDevToolsPresent) {
  325. root.memoizedUpdaters.clear();
  326. }
  327. }
  328. if (__DEV__) {
  329. onCommitRootTestSelector();
  330. }
  331. // Always call this before exiting `commitRoot`, to ensure that any
  332. // additional work on this root is scheduled.
  333. ensureRootIsScheduled(root, now());
  334. if (hasUncaughtError) {
  335. hasUncaughtError = false;
  336. const error = firstUncaughtError;
  337. firstUncaughtError = null;
  338. throw error;
  339. }
  340. // If the passive effects are the result of a discrete render, flush them
  341. // synchronously at the end of the current task so that the result is
  342. // immediately observable. Otherwise, we assume that they are not
  343. // order-dependent and do not need to be observed by external systems, so we
  344. // can wait until after paint.
  345. // TODO: We can optimize this by not scheduling the callback earlier. Since we
  346. // currently schedule the callback in multiple places, will wait until those
  347. // are consolidated.
  348. if (
  349. includesSomeLane(pendingPassiveEffectsLanes, SyncLane) &&
  350. root.tag !== LegacyRoot
  351. ) {
  352. flushPassiveEffects();
  353. }
  354. // If layout work was scheduled, flush it now.
  355. flushSyncCallbacks();
  356. if (__DEV__) {
  357. if (enableDebugTracing) {
  358. logCommitStopped();
  359. }
  360. }
  361. if (enableSchedulingProfiler) {
  362. markCommitStopped();
  363. }
  364. return null;
  365. }