├── .vscode └── settings.json ├── src ├── scheduler │ ├── index.js │ └── src │ │ └── forks │ │ ├── SchedulerPriorities.js │ │ ├── SchedulerMinHeap.js │ │ └── Scheduler.js ├── react-dom │ ├── client.js │ └── src │ │ └── client │ │ └── ReactDOMRoot.js ├── react │ ├── jsx-dev-runtime.js │ ├── index.js │ └── src │ │ ├── React.js │ │ ├── ReactSharedInternals.js │ │ ├── ReactCurrentDispatcher.js │ │ ├── jsx │ │ └── ReactJSXElement.js │ │ └── ReactHooks.js ├── shared │ ├── hasOwnProperty.js │ ├── ReactSharedInternals.js │ ├── logger.js │ ├── isArray.js │ ├── assign.js │ ├── ReactSymbols.js │ └── ReactFeatureFlags.js ├── react-dom-bindings │ └── src │ │ ├── events │ │ ├── EventSystemFlags.js │ │ ├── getEventTarget.js │ │ ├── EventListener.js │ │ ├── EventRegistry.js │ │ ├── getListener.js │ │ ├── DOMEventProperties.js │ │ ├── plugins │ │ │ └── SimpleEventPlugin.js │ │ ├── ReactDOMEventListener.js │ │ ├── SyntheticEvent.js │ │ └── DOMPluginEventSystem.js │ │ └── client │ │ ├── setTextContent.js │ │ ├── DOMPropertyOperations.js │ │ ├── CSSPropertyOperations.js │ │ ├── ReactDOMComponentTree.js │ │ ├── ReactDOMHostConfig.js │ │ └── ReactDOMComponent.js ├── react-reconciler │ └── src │ │ ├── Scheduler.js │ │ ├── ReactHookEffectTags.js │ │ ├── ReactFiberReconciler.js │ │ ├── ReactWorkTags.js │ │ ├── ReactFiberSyncTaskQueue.js │ │ ├── ReactFiberFlags.js │ │ ├── ReactEventPriorities.js │ │ ├── ReactFiberConcurrentUpdates.js │ │ ├── ReactFiberRoot.js │ │ ├── ReactFiberBeginWork.js │ │ ├── ReactFiberCompleteWork.js │ │ ├── ReactFiberClassUpdateQueue.js │ │ ├── ReactFiber.js │ │ ├── ReactFiberLane.js │ │ ├── ReactFiberCommitWork.js │ │ ├── ReactChildFiber.js │ │ ├── ReactFiberWorkLoop.js │ │ └── ReactFiberHooks.js └── main.jsx ├── img ├── buma.png ├── fanma.gif ├── fiber.jpg ├── react.png ├── yuanma.gif ├── beginWork.png ├── domDiff.jpeg ├── fiber-hook.png ├── fiberFlow.png ├── hookUpdate.jpg ├── lifeCycle.png ├── min_heap.jpg ├── setState.png ├── taskQueue.jpeg ├── timeSlice.jpeg ├── completeWork.png ├── createRoot.jpeg ├── domDiff_move.jpg ├── eventBubble.png ├── fiberAnverse.png ├── fiberSimple.png ├── fiberUpdate.jpg ├── firstRender.jpg ├── lifeoffram.jpeg ├── lifeofframe.jpeg ├── moreDomDiff.png ├── updatequeue.png ├── dispatch-event.png ├── renderFiber1.jpeg ├── renderRootFiber.jpg ├── renderWithHook.jpg ├── requestIdleback.png ├── singleDomDiff.jpg ├── useLayoutEffect.png ├── useStateMount.png ├── collectEffectList.jpg ├── createUpdateQueue.png ├── fiberConstructor.png ├── reactelement-tree.png ├── registerEventName.png ├── scheduleCallback.jpg ├── completely_twoTree.jpeg ├── memoizedStateQueue.png ├── reactfiberworkloop.png ├── eventuse_1665894712077.jpeg ├── di_gui_gou_jian_fiber_shu.jpeg ├── mountReducer_1678679227351.png ├── queuepending_1644750048819.png ├── extractEvents2_1678678999496.png ├── liu_lan_qi_zhen_1643277067067.jpeg ├── useLayoutEffect_1666851036689.jpeg ├── zui_xiao_dui_1_1643275468911.jpeg ├── dan_jie_dian_diff_1678677255463.png ├── flushPassiveEffects_1666783551920.jpeg ├── initializeUpdateQueue_1664039386818.png ├── processDispatchQueue1_1678679016915.png └── shi_jian_qie_pian_diao_du_1643278352662.jpeg ├── .gitignore ├── index.html ├── vite.config.js ├── LICENSE ├── markdown ├── dfs.md ├── messageChannel.md ├── &|.md └── minHeap.md ├── package.json └── tsconfig.json /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /src/scheduler/index.js: -------------------------------------------------------------------------------- 1 | export * from './src/forks/Scheduler'; -------------------------------------------------------------------------------- /src/react-dom/client.js: -------------------------------------------------------------------------------- 1 | export { createRoot } from './src/client/ReactDOMRoot'; -------------------------------------------------------------------------------- /src/react/jsx-dev-runtime.js: -------------------------------------------------------------------------------- 1 | export { jsxDEV } from './src/jsx/ReactJSXElement'; -------------------------------------------------------------------------------- /img/buma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/buma.png -------------------------------------------------------------------------------- /img/fanma.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/fanma.gif -------------------------------------------------------------------------------- /img/fiber.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/fiber.jpg -------------------------------------------------------------------------------- /img/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/react.png -------------------------------------------------------------------------------- /img/yuanma.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/yuanma.gif -------------------------------------------------------------------------------- /src/shared/hasOwnProperty.js: -------------------------------------------------------------------------------- 1 | const { hasOwnProperty } = Object.prototype; 2 | export default hasOwnProperty; -------------------------------------------------------------------------------- /img/beginWork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/beginWork.png -------------------------------------------------------------------------------- /img/domDiff.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/domDiff.jpeg -------------------------------------------------------------------------------- /img/fiber-hook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/fiber-hook.png -------------------------------------------------------------------------------- /img/fiberFlow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/fiberFlow.png -------------------------------------------------------------------------------- /img/hookUpdate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/hookUpdate.jpg -------------------------------------------------------------------------------- /img/lifeCycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/lifeCycle.png -------------------------------------------------------------------------------- /img/min_heap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/min_heap.jpg -------------------------------------------------------------------------------- /img/setState.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/setState.png -------------------------------------------------------------------------------- /img/taskQueue.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/taskQueue.jpeg -------------------------------------------------------------------------------- /img/timeSlice.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/timeSlice.jpeg -------------------------------------------------------------------------------- /img/completeWork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/completeWork.png -------------------------------------------------------------------------------- /img/createRoot.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/createRoot.jpeg -------------------------------------------------------------------------------- /img/domDiff_move.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/domDiff_move.jpg -------------------------------------------------------------------------------- /img/eventBubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/eventBubble.png -------------------------------------------------------------------------------- /img/fiberAnverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/fiberAnverse.png -------------------------------------------------------------------------------- /img/fiberSimple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/fiberSimple.png -------------------------------------------------------------------------------- /img/fiberUpdate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/fiberUpdate.jpg -------------------------------------------------------------------------------- /img/firstRender.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/firstRender.jpg -------------------------------------------------------------------------------- /img/lifeoffram.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/lifeoffram.jpeg -------------------------------------------------------------------------------- /img/lifeofframe.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/lifeofframe.jpeg -------------------------------------------------------------------------------- /img/moreDomDiff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/moreDomDiff.png -------------------------------------------------------------------------------- /img/updatequeue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/updatequeue.png -------------------------------------------------------------------------------- /img/dispatch-event.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/dispatch-event.png -------------------------------------------------------------------------------- /img/renderFiber1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/renderFiber1.jpeg -------------------------------------------------------------------------------- /img/renderRootFiber.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/renderRootFiber.jpg -------------------------------------------------------------------------------- /img/renderWithHook.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/renderWithHook.jpg -------------------------------------------------------------------------------- /img/requestIdleback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/requestIdleback.png -------------------------------------------------------------------------------- /img/singleDomDiff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/singleDomDiff.jpg -------------------------------------------------------------------------------- /img/useLayoutEffect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/useLayoutEffect.png -------------------------------------------------------------------------------- /img/useStateMount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/useStateMount.png -------------------------------------------------------------------------------- /src/react-dom-bindings/src/events/EventSystemFlags.js: -------------------------------------------------------------------------------- 1 | export const IS_CAPTURE_PHASE = 1 << 2; 2 | // 0b0001 3 | // 0b0100 -------------------------------------------------------------------------------- /img/collectEffectList.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/collectEffectList.jpg -------------------------------------------------------------------------------- /img/createUpdateQueue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/createUpdateQueue.png -------------------------------------------------------------------------------- /img/fiberConstructor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/fiberConstructor.png -------------------------------------------------------------------------------- /img/reactelement-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/reactelement-tree.png -------------------------------------------------------------------------------- /img/registerEventName.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/registerEventName.png -------------------------------------------------------------------------------- /img/scheduleCallback.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/scheduleCallback.jpg -------------------------------------------------------------------------------- /img/completely_twoTree.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/completely_twoTree.jpeg -------------------------------------------------------------------------------- /img/memoizedStateQueue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/memoizedStateQueue.png -------------------------------------------------------------------------------- /img/reactfiberworkloop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/reactfiberworkloop.png -------------------------------------------------------------------------------- /img/eventuse_1665894712077.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/eventuse_1665894712077.jpeg -------------------------------------------------------------------------------- /img/di_gui_gou_jian_fiber_shu.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/di_gui_gou_jian_fiber_shu.jpeg -------------------------------------------------------------------------------- /img/mountReducer_1678679227351.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/mountReducer_1678679227351.png -------------------------------------------------------------------------------- /img/queuepending_1644750048819.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/queuepending_1644750048819.png -------------------------------------------------------------------------------- /img/extractEvents2_1678678999496.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/extractEvents2_1678678999496.png -------------------------------------------------------------------------------- /img/liu_lan_qi_zhen_1643277067067.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/liu_lan_qi_zhen_1643277067067.jpeg -------------------------------------------------------------------------------- /img/useLayoutEffect_1666851036689.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/useLayoutEffect_1666851036689.jpeg -------------------------------------------------------------------------------- /img/zui_xiao_dui_1_1643275468911.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/zui_xiao_dui_1_1643275468911.jpeg -------------------------------------------------------------------------------- /img/dan_jie_dian_diff_1678677255463.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/dan_jie_dian_diff_1678677255463.png -------------------------------------------------------------------------------- /img/flushPassiveEffects_1666783551920.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/flushPassiveEffects_1666783551920.jpeg -------------------------------------------------------------------------------- /img/initializeUpdateQueue_1664039386818.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/initializeUpdateQueue_1664039386818.png -------------------------------------------------------------------------------- /img/processDispatchQueue1_1678679016915.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/processDispatchQueue1_1678679016915.png -------------------------------------------------------------------------------- /src/react-dom-bindings/src/client/setTextContent.js: -------------------------------------------------------------------------------- 1 | 2 | function setTextContent(node, text) { 3 | node.textContent = text; 4 | } 5 | export default setTextContent; -------------------------------------------------------------------------------- /img/shi_jian_qie_pian_diao_du_1643278352662.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/changcheng1/react18-source-implementation/HEAD/img/shi_jian_qie_pian_diao_du_1643278352662.jpeg -------------------------------------------------------------------------------- /src/shared/ReactSharedInternals.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | const ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED 3 | export default ReactSharedInternals 4 | 5 | -------------------------------------------------------------------------------- /src/react-dom-bindings/src/events/getEventTarget.js: -------------------------------------------------------------------------------- 1 | function getEventTarget(nativeEvent) { 2 | const target = nativeEvent.target || nativeEvent.srcElement || window; 3 | return target; 4 | } 5 | export default getEventTarget; -------------------------------------------------------------------------------- /src/react-dom-bindings/src/client/DOMPropertyOperations.js: -------------------------------------------------------------------------------- 1 | 2 | export function setValueForProperty(node, name, value) { 3 | if (value === null) { 4 | node.removeAttribute(name); 5 | } else { 6 | node.setAttribute(name, value); 7 | } 8 | } -------------------------------------------------------------------------------- /src/react-dom-bindings/src/client/CSSPropertyOperations.js: -------------------------------------------------------------------------------- 1 | 2 | export function setValueForStyles(node, styles) { 3 | const { style } = node; 4 | //styles={ color: "red" } 5 | for (const styleName in styles) { 6 | if (styles.hasOwnProperty(styleName)) { 7 | const styleValue = styles[styleName]; 8 | style[styleName] = styleValue; 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/react-dom-bindings/src/events/EventListener.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export function addEventCaptureListener(target, eventType, listener) { 4 | target.addEventListener(eventType, listener, true); 5 | return listener; 6 | } 7 | export function addEventBubbleListener(target, eventType, listener) { 8 | target.addEventListener(eventType, listener, false); 9 | return listener; 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |
12 |
13 | ```javaScript
14 | var channel = new MessageChannel();
15 | var port1 = channel.port1;
16 | var port2 = channel.port2
17 | port1.onmessage = function(event) {
18 | console.log("port1收到来自port2的数据:" + event.data);
19 | }
20 | port2.onmessage = function(event) {
21 | console.log("port2收到来自port1的数据:" + event.data);
22 | }
23 | port1.postMessage("发送给port2");
24 | port2.postMessage("发送给port1");
25 | ```
26 |
27 | ### requestIdleCallback
28 |
29 | `requestIdleCallback`用此方法,判断当前帧是否有空余时间,执行某个任务,在React中,可以模拟任务调度,根据任务优先级不同,根据浏览器剩余时间调度不同的任务,缺点就是浏览器兼容问题了。
30 |
31 | ```javaScript
32 | // 挂起任务
33 | function sleep(duration) {
34 | let time = Date.now();
35 | while (duration + time > Date.now()) {}
36 | }
37 | // 工作任务
38 | let works = [
39 | () => {
40 | console.log("A1开始");
41 | sleep(30); // 挂起30ms
42 | console.log("A1结束");
43 | },
44 | () => {
45 | console.log("B1开始");
46 | console.log("B1结束");
47 | },
48 | () => {
49 | console.log("B2开始");
50 | console.log("B2结束");
51 | },
52 | ];
53 |
54 | // 取出当前的第一个任务执行
55 | function performUnitWord() {
56 | let work = works.shift();
57 | work();
58 | }
59 |
60 | function workLoop(deadLine) {
61 | // deadLine.timeRemaining:函数的返回值表示当前空闲时间还剩下多少时间
62 | console.log("本帧剩余时间ms", parseInt(deadLine.timeRemaining()));
63 | while (
64 | // 如果有剩余时间或者过期了,过期的话 deadLine.timeout属性就会为true
65 | (deadLine.timeRemaining() > 0 || deadLine.timeout) &&
66 | works.length > 0
67 | ) {
68 | // 执行任务
69 | performUnitWord();
70 | }
71 | if (works.length > 0) {
72 | // 浏览器有空继续调用
73 | console.log(
74 | `只剩下${deadLine.timeRemaining()},时间片已经到期了,等待下次调度`
75 | );
76 | requestIdleCallback(workLoop);
77 | }
78 | }
79 |
80 | // 告诉浏览器有空闲时间执行任务,但是如果已经过期,不管有没有空,都帮我执行,执行方法就是requestIdleCallback(callBack,timeOut)
81 | requestIdleCallback(workLoop, { timeout: 1000 });
82 | ```
83 |
--------------------------------------------------------------------------------
/src/react-dom-bindings/src/client/ReactDOMHostConfig.js:
--------------------------------------------------------------------------------
1 | import { setInitialProperties, diffProperties, updateProperties } from './ReactDOMComponent';
2 | import { precacheFiberNode, updateFiberProps } from './ReactDOMComponentTree';
3 | import { DefaultEventPriority } from 'react-reconciler/src/ReactEventPriorities';
4 | import { getEventPriority } from '../events/ReactDOMEventListener';
5 |
6 | export function shouldSetTextContent(type, props) {
7 | return typeof props.children === "string" || typeof props.children === "number";
8 | }
9 |
10 | export function createTextInstance(content) {
11 | return document.createTextNode(content);
12 | }
13 | /**
14 | * 在原生组件初次挂载的时候,会通过此方法创建真实DOM
15 | * @param {*} type 类型 span
16 | * @param {*} props 属性
17 | * @param {*} internalInstanceHandle 它对应的fiber
18 | * @returns
19 | */
20 | export function createInstance(type, props, internalInstanceHandle) {
21 | const domElement = document.createElement(type);
22 | //预先缓存fiber节点到DOM元素上
23 | precacheFiberNode(internalInstanceHandle, domElement);
24 | //把属性直接保存在domElement的属性上
25 | updateFiberProps(domElement, props);
26 | return domElement;
27 | }
28 |
29 | export function appendInitialChild(parent, child) {
30 | parent.appendChild(child);
31 | }
32 | export function finalizeInitialChildren(domElement, type, props) {
33 | setInitialProperties(domElement, type, props);
34 | }
35 | export function appendChild(parentInstance, child) {
36 | parentInstance.appendChild(child);
37 | }
38 | /**
39 | *
40 | * @param {*} parentInstance 父DOM节点
41 | * @param {*} child 子DOM节点
42 | * @param {*} beforeChild 插入到谁的前面,它也是一个DOM节点
43 | */
44 | export function insertBefore(parentInstance, child, beforeChild) {
45 | parentInstance.insertBefore(child, beforeChild);
46 | }
47 | export function prepareUpdate(domElement, type, oldProps, newProps) {
48 | return diffProperties(domElement, type, oldProps, newProps);
49 | }
50 | export function commitUpdate(domElement, updatePayload, type, oldProps, newProps) {
51 | updateProperties(domElement, updatePayload, type, oldProps, newProps);
52 | updateFiberProps(domElement, newProps);
53 | }
54 | export function removeChild(parentInstance, child) {
55 | parentInstance.removeChild(child);
56 | }
57 |
58 | export function getCurrentEventPriority() {
59 | const currentEvent = window.event;
60 | if (currentEvent === undefined) {
61 | return DefaultEventPriority;
62 | }
63 | return getEventPriority(currentEvent.type);
64 | }
--------------------------------------------------------------------------------
/src/react-reconciler/src/ReactEventPriorities.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React Event Priorities - Event-Based Priority Management
3 | *
4 | * This module manages the mapping between different types of events and their
5 | * corresponding priority levels in React's scheduling system. It bridges the
6 | * gap between user interactions and React's internal lane-based priority system.
7 | *
8 | * Priority levels (from highest to lowest):
9 | * - Discrete: User interactions that need immediate response (click, input)
10 | * - Continuous: Events that fire frequently (mousemove, scroll)
11 | * - Default: Normal updates and state changes
12 | * - Idle: Background work that can be deferred
13 | *
14 | * @module ReactEventPriorities
15 | */
16 |
17 | import {
18 | NoLane,
19 | SyncLane,
20 | InputContinuousLane,
21 | DefaultLane,
22 | IdleLane,
23 | getHighestPriorityLane,
24 | includesNonIdleWork,
25 | } from "./ReactFiberLane";
26 |
27 | // Event priority constants mapped to corresponding lanes
28 | export const DiscreteEventPriority = SyncLane; // 1 - Discrete events (click, change)
29 | export const ContinuousEventPriority = InputContinuousLane; // 4 - Continuous events (mousemove)
30 | export const DefaultEventPriority = DefaultLane; // 16 - Default priority events
31 | export const IdleEventPriority = IdleLane; // Idle priority events
32 |
33 | // Global state for current update priority
34 | let currentUpdatePriority = NoLane;
35 |
36 | export function getCurrentUpdatePriority() {
37 | return currentUpdatePriority;
38 | }
39 | export function setCurrentUpdatePriority(newPriority) {
40 | currentUpdatePriority = newPriority;
41 | }
42 |
43 | /**
44 | * 判断eventPriority是不是比lane要小,更小意味着优先级更高
45 | * @param {*} a
46 | * @param {*} b
47 | * @returns
48 | */
49 | export function isHigherEventPriority(eventPriority, lane) {
50 | return eventPriority !== 0 && eventPriority < lane;
51 | }
52 | /**
53 | * 把lane转成事件优先级
54 | * lane 31
55 | * 事件优先级是4
56 | * 调度优先级5
57 | * @param {*} lanes
58 | * @returns
59 | */
60 | export function lanesToEventPriority(lanes) {
61 | //获取最高优先级的lane
62 | let lane = getHighestPriorityLane(lanes);
63 | if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
64 | return DiscreteEventPriority; //1
65 | }
66 | if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
67 | return ContinuousEventPriority; //4
68 | }
69 | if (includesNonIdleWork(lane)) {
70 | return DefaultEventPriority; //16
71 | }
72 | return IdleEventPriority;
73 | }
74 |
--------------------------------------------------------------------------------
/src/react-reconciler/src/ReactFiberConcurrentUpdates.js:
--------------------------------------------------------------------------------
1 | import { HostRoot } from "./ReactWorkTags";
2 | import { mergeLanes } from "./ReactFiberLane";
3 |
4 | const concurrentQueues = [];
5 | let concurrentQueuesIndex = 0;
6 |
7 | /**
8 | * Cache updates to the concurrentQueue array first
9 | * @param {*} fiber
10 | * @param {*} queue
11 | * @param {*} update
12 | */
13 | function enqueueUpdate(fiber, queue, update, lane) {
14 | //012 setNumber1 345 setNumber2 678 setNumber3
15 | concurrentQueues[concurrentQueuesIndex++] = fiber; // The fiber corresponding to the function component
16 | concurrentQueues[concurrentQueuesIndex++] = queue; // The update queue corresponding to the hook to be updated
17 | concurrentQueues[concurrentQueuesIndex++] = update; // Update object
18 | concurrentQueues[concurrentQueuesIndex++] = lane; // Lane corresponding to the update
19 | // When we add an update to a fiber, we need to merge the lane of this update into the lane of this fiber
20 | fiber.lanes = mergeLanes(fiber.lanes, lane);
21 | }
22 |
23 | export function finishQueueingConcurrentUpdates() {
24 | const endIndex = concurrentQueuesIndex; //9 Just a boundary condition
25 | concurrentQueuesIndex = 0;
26 | let i = 0;
27 | while (i < endIndex) {
28 | const fiber = concurrentQueues[i++];
29 | const queue = concurrentQueues[i++];
30 | const update = concurrentQueues[i++];
31 | const lane = concurrentQueues[i++];
32 | if (queue !== null && update !== null) {
33 | const pending = queue.pending;
34 | if (pending === null) {
35 | update.next = update;
36 | } else {
37 | update.next = pending.next;
38 | pending.next = update;
39 | }
40 | queue.pending = update;
41 | }
42 | }
43 | }
44 | /**
45 | * Add the update queue to the update queue
46 | * @param {*} fiber The fiber corresponding to the function component
47 | * @param {*} queue The update queue corresponding to the hook to be updated
48 | * @param {*} update Update object
49 | */
50 | export function enqueueConcurrentHookUpdate(fiber, queue, update, lane) {
51 | enqueueUpdate(fiber, queue, update, lane);
52 | return getRootForUpdatedFiber(fiber);
53 | }
54 | /**
55 | * Enqueue the update
56 | * @param {*} fiber The fiber to be enqueued, root fiber
57 | * @param {*} queue shareQueue The queue to be effective
58 | * @param {*} update Update
59 | * @param {*} lane The lane of this update
60 | */
61 | export function enqueueConcurrentClassUpdate(fiber, queue, update, lane) {
62 | enqueueUpdate(fiber, queue, update, lane);
63 | return getRootForUpdatedFiber(fiber);
64 | }
65 | function getRootForUpdatedFiber(sourceFiber) {
66 | let node = sourceFiber;
67 | let parent = node.return;
68 | while (parent !== null) {
69 | node = parent;
70 | parent = node.return;
71 | }
72 | return node.tag === HostRoot ? node.stateNode : null; //FiberRootNode div#root
73 | }
74 |
--------------------------------------------------------------------------------
/src/react-reconciler/src/ReactFiberRoot.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React Fiber Root - Root Container Management
3 | *
4 | * This module manages the root container of a React application. The FiberRootNode
5 | * represents the top-level container that holds the entire React fiber tree.
6 | * It manages scheduling, priority lanes, and the connection between React and the DOM.
7 | *
8 | * Key responsibilities:
9 | * - Root container lifecycle management
10 | * - Priority lane tracking and scheduling
11 | * - Connection between React fiber tree and DOM container
12 | * - Update queue initialization
13 | *
14 | * @module ReactFiberRoot
15 | */
16 |
17 | import { createHostRootFiber } from "./ReactFiber";
18 | import { initialUpdateQueue } from "./ReactFiberClassUpdateQueue";
19 | import { NoLanes, NoLane, createLaneMap, NoTimestamp } from "./ReactFiberLane";
20 |
21 | /**
22 | * Fiber Root Node Constructor
23 | *
24 | * Creates the root node that represents the entire React application container.
25 | * This node sits above the React fiber tree and manages scheduling and updates.
26 | *
27 | * @param {Element} containerInfo - DOM container element (e.g., div#root)
28 | */
29 | function FiberRootNode(containerInfo) {
30 | this.containerInfo = containerInfo; // DOM container element (div#root)
31 |
32 | // Scheduling and priority management
33 | this.pendingLanes = NoLanes; // Lanes with pending work
34 | this.callbackNode = null; // Current scheduler callback
35 | this.callbackPriority = NoLane; // Priority of current callback
36 |
37 | // Expiration tracking
38 | this.expirationTimes = createLaneMap(NoTimestamp); // Expiration time for each lane
39 | this.expiredLanes = NoLanes; // Lanes that have expired
40 | }
41 |
42 | /**
43 | * Create Fiber Root
44 | *
45 | * Creates and initializes a complete fiber root structure. This includes:
46 | * 1. Creating the FiberRootNode (container management)
47 | * 2. Creating the HostRootFiber (root of the fiber tree)
48 | * 3. Establishing bidirectional connection between them
49 | * 4. Initializing the update queue
50 | *
51 | * This function sets up the foundation for React's rendering system.
52 | *
53 | * @param {Element} containerInfo - DOM container element
54 | * @returns {FiberRootNode} Complete fiber root structure
55 | */
56 | export function createFiberRoot(containerInfo) {
57 | // Create the root container node
58 | const root = new FiberRootNode(containerInfo);
59 |
60 | // Create the root fiber (HostRoot represents the container element)
61 | const uninitializedFiber = createHostRootFiber();
62 |
63 | // Establish bidirectional connection for double buffering
64 | root.current = uninitializedFiber; // Root points to current fiber tree
65 | uninitializedFiber.stateNode = root; // Root fiber points back to container
66 |
67 | // Initialize update queue for the root fiber
68 | initialUpdateQueue(uninitializedFiber);
69 |
70 | return root;
71 | }
72 |
--------------------------------------------------------------------------------
/src/react-dom-bindings/src/events/ReactDOMEventListener.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ReactDOMEventListener - Event Listener for ReactDOM
3 | *
4 | * This module provides the event listener functionality for ReactDOM.
5 | * It handles the creation and dispatch of synthetic events.
6 | *
7 | * @module ReactDOMEventListener
8 | */
9 | import getEventTarget from "./getEventTarget";
10 | import { getClosestInstanceFromNode } from "../client/ReactDOMComponentTree";
11 | import { dispatchEventForPluginEventSystem } from "./DOMPluginEventSystem";
12 | import {
13 | ContinuousEventPriority,
14 | DefaultEventPriority,
15 | DiscreteEventPriority,
16 | getCurrentUpdatePriority,
17 | setCurrentUpdatePriority,
18 | } from "react-reconciler/src/ReactEventPriorities";
19 | export function createEventListenerWrapperWithPriority(
20 | targetContainer,
21 | domEventName,
22 | eventSystemFlags
23 | ) {
24 | const listenerWrapper = dispatchDiscreteEvent;
25 | return listenerWrapper.bind(
26 | null,
27 | domEventName,
28 | eventSystemFlags,
29 | targetContainer
30 | );
31 | }
32 | /**
33 | * dispatch the discrete event of the listener function
34 | * @param {*} domEventName click
35 | * @param {*} eventSystemFlags phase 0 bubble 4 capture
36 | * @param {*} container container div#root
37 | * @param {*} nativeEvent native event
38 | */
39 | function dispatchDiscreteEvent(
40 | domEventName,
41 | eventSystemFlags,
42 | container,
43 | nativeEvent
44 | ) {
45 | //when you click the button, you need to set the update priority
46 | //get the current old update priority
47 | const previousPriority = getCurrentUpdatePriority();
48 | try {
49 | //set the current update priority to the discrete event priority 1
50 | setCurrentUpdatePriority(DiscreteEventPriority);
51 | dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
52 | } finally {
53 | setCurrentUpdatePriority(previousPriority);
54 | }
55 | }
56 | /**
57 | * this method is to delegate to the container's callback, when the container#root handles the event in the capture or bubble phase, this function will be executed
58 | * @param {*} domEventName
59 | * @param {*} eventSystemFlags phase 0 bubble 4 capture
60 | * @param {*} container container div#root
61 | * @param {*} nativeEvent native event
62 | */
63 | export function dispatchEvent(
64 | domEventName,
65 | eventSystemFlags,
66 | targetContainer,
67 | nativeEvent
68 | ) {
69 | // get the event target, it is a real DOM
70 | const nativeEventTarget = getEventTarget(nativeEvent);
71 | const targetInst = getClosestInstanceFromNode(nativeEventTarget);
72 | dispatchEventForPluginEventSystem(
73 | domEventName, //click
74 | eventSystemFlags, //0 4
75 | nativeEvent, //native event
76 | targetInst, //the fiber of the real DOM
77 | targetContainer //target container
78 | );
79 | }
80 | /**
81 | * get the event priority
82 | * @param {*} domEventName click
83 | */
84 | export function getEventPriority(domEventName) {
85 | switch (domEventName) {
86 | case "click":
87 | return DiscreteEventPriority;
88 | case "drag":
89 | return ContinuousEventPriority;
90 | default:
91 | return DefaultEventPriority;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/markdown/&|.md:
--------------------------------------------------------------------------------
1 |
5 |
6 | #### Bitwise AND (&)
7 |
8 | When every bit is 1, the result is 1, otherwise it is 0
9 |
10 | ```javaScript
11 | const a = 5; // 00000000000000000000000000000101
12 | const b = 3; // 00000000000000000000000000000011
13 |
14 | console.log(a & b); // 00000000000000000000000000000001
15 | // Expected output: 1
16 | ```
17 |
18 | #### Bitwise OR (|)
19 |
20 | When every bit is 0, the result is 0, otherwise it is 1
21 |
22 | ```javaScript
23 | const a = 5; // 00000000000000000000000000000101
24 | const b = 3; // 00000000000000000000000000000011
25 |
26 | console.log(a | b); // 00000000000000000000000000000111
27 | // Expected output: 7
28 | ```
29 |
30 | #### Bitwise AND assignment (&=)
31 |
32 | The bitwise AND assignment operator (&=) uses the binary representation of two operands, performs a bitwise AND operation on them, and assigns the result to the variable.
33 |
34 | ```javaScript
35 | let a = 5; // 00000000000000000000000000000101
36 | a &= 3; // 00000000000000000000000000000011
37 |
38 | console.log(a); // 00000000000000000000000000000001
39 | // Expected output: 1
40 | ```
41 |
42 | #### Bitwise OR assignment (|=)
43 |
44 | The bitwise OR assignment (|=) operator uses the binary representation of two operands, performs a bitwise OR operation on them, and assigns the result to the variable.
45 |
46 | ```javaScript
47 | let a = 5; // 00000000000000000000000000000101
48 | a |= 3; // 00000000000000000000000000000011
49 |
50 | console.log(a); // 00000000000000000000000000000111
51 | // Expected output: 7
52 |
53 | ```
54 |
55 | #### Bitwise NOT (~)
56 |
57 | Bitwise negation
58 |
59 | ```javaScript
60 | const a = 5; // 00000000000000000000000000000101
61 | const b = -3; // 11111111111111111111111111111101
62 |
63 | console.log(~a); // 11111111111111111111111111111010
64 | // Expected output: -6
65 |
66 | console.log(~b); // 00000000000000000000000000000010
67 | // Expected output: 2
68 |
69 | ```
70 |
71 | Used in React to record side effect flags
72 |
73 | Side effect flags can optimize performance, because in React, side effects (**add, delete, modify**) bubble up to the parent level. If the parent has no side effects, there's no need to traverse child nodes in depth-first order, saving performance
74 |
75 | ```javaScript
76 |
77 | //Define constants
78 | const Placement = 0b001; // 1
79 | const Update = 0b010; // 2
80 | //Define operations
81 | let flags = 0b000; // 0
82 | //Add operation
83 | flags |= Placement;
84 | flags |= Update;
85 | console.log(flags.toString(2)); //0b011
86 |
87 | //Delete operation 0b011 & 0b110 => 0b010
88 | flags = flags & ~Placement;
89 | console.log(flags.toString(2)); //0b010
90 | console.log(flags); //2
91 |
92 | //Check if contains
93 | console.log((flags & Placement) === Placement); // false
94 | console.log((flags & Update) === Update); // true
95 |
96 | //Check if not contains
97 | console.log((flags & Placement) === 0); // true
98 | console.log((flags & Update) === 0); // fasle
99 |
100 | ```
101 |
--------------------------------------------------------------------------------
/src/react/src/ReactHooks.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React Hooks - Public API Layer
3 | *
4 | * This module provides the public hooks API that developers use in their components.
5 | * It uses the dispatcher pattern to delegate to different implementations based on
6 | * the current rendering phase (mount vs update).
7 | *
8 | * @module ReactHooks
9 | */
10 |
11 | import ReactCurrentDispatcher from "./ReactCurrentDispatcher";
12 |
13 | /**
14 | * Resolve Current Dispatcher
15 | *
16 | * Gets the current dispatcher implementation. The dispatcher changes based on
17 | * the rendering phase - different implementations for mount and update phases.
18 | *
19 | * @returns {Object} Current dispatcher with hook implementations
20 | */
21 | function resolveDispatcher() {
22 | return ReactCurrentDispatcher.current;
23 | }
24 |
25 | /**
26 | * useReducer Hook
27 | *
28 | * Hook for managing complex state logic. Similar to useState but accepts a reducer
29 | * function that specifies how the state gets updated in response to actions.
30 | *
31 | * @param {Function} reducer - Reducer function that takes (state, action) and returns new state
32 | * @param {any} initialArg - Initial state value
33 | * @returns {Array} Array containing [state, dispatch] where dispatch triggers state updates
34 | */
35 | export function useReducer(reducer, initialArg) {
36 | const dispatcher = resolveDispatcher();
37 | return dispatcher.useReducer(reducer, initialArg);
38 | }
39 |
40 | /**
41 | * useState Hook
42 | *
43 | * Hook for managing component state. Returns current state value and a function
44 | * to update it. State updates are asynchronous and may be batched.
45 | *
46 | * @param {any} initialState - Initial state value or function that returns initial state
47 | * @returns {Array} Array containing [state, setState] where setState updates the state
48 | */
49 | export function useState(initialState) {
50 | const dispatcher = resolveDispatcher();
51 | return dispatcher.useState(initialState);
52 | }
53 |
54 | /**
55 | * useEffect Hook
56 | *
57 | * Hook for performing side effects in function components. Runs after render
58 | * and can optionally clean up before the next effect or component unmount.
59 | *
60 | * @param {Function} create - Effect function, optionally returns cleanup function
61 | * @param {Array} deps - Dependency array, effect runs when dependencies change
62 | */
63 | export function useEffect(create, deps) {
64 | const dispatcher = resolveDispatcher();
65 | return dispatcher.useEffect(create, deps);
66 | }
67 |
68 | /**
69 | * useLayoutEffect Hook
70 | *
71 | * Similar to useEffect but runs synchronously after all DOM mutations.
72 | * Use this for DOM measurements or synchronous DOM updates.
73 | *
74 | * @param {Function} create - Effect function, optionally returns cleanup function
75 | * @param {Array} deps - Dependency array, effect runs when dependencies change
76 | */
77 | export function useLayoutEffect(create, deps) {
78 | const dispatcher = resolveDispatcher();
79 | return dispatcher.useLayoutEffect(create, deps);
80 | }
81 |
82 | /**
83 | * useRef Hook
84 | *
85 | * Hook for creating a mutable ref object. The ref object persists across renders
86 | * and doesn't cause re-renders when its value changes.
87 | *
88 | * @param {any} initialValue - Initial value for the ref.current property
89 | * @returns {Object} Ref object with current property
90 | */
91 | export function useRef(initialValue) {
92 | const dispatcher = resolveDispatcher();
93 | return dispatcher.useRef(initialValue);
94 | }
95 |
--------------------------------------------------------------------------------
/src/scheduler/src/forks/SchedulerMinHeap.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Scheduler Min Heap - Priority Queue Implementation
3 | *
4 | * This module implements a binary min heap data structure used by React's scheduler
5 | * to manage tasks by priority. The heap ensures that the highest priority task
6 | * (lowest sortIndex value) is always at the root and can be accessed in O(1) time.
7 | *
8 | * Key properties:
9 | * - Parent node value ≤ child node values (min heap property)
10 | * - Complete binary tree structure
11 | * - Efficient insertion and extraction: O(log n)
12 | * - Peek operation: O(1)
13 | *
14 | * Used for:
15 | * - Task queue management in scheduler
16 | * - Priority-based task execution
17 | * - Efficient task scheduling and dequeuing
18 | *
19 | * @module SchedulerMinHeap
20 | */
21 |
22 | /**
23 | * Push - Insert Element into Heap
24 | *
25 | * Adds a new node to the heap while maintaining the min heap property.
26 | * The element is added at the end and then "bubbled up" to its correct position.
27 | *
28 | * Time complexity: O(log n)
29 | *
30 | * @param {Array} heap - The heap array
31 | * @param {Object} node - Node to insert (must have sortIndex property)
32 | */
33 | export function push(heap, node) {
34 | const index = heap.length;
35 | heap.push(node);
36 | siftUp(heap, node, index);
37 | }
38 |
39 | /**
40 | * Peek - Get Minimum Element
41 | *
42 | * Returns the minimum element (root) without removing it from the heap.
43 | * In a min heap, this is always the first element.
44 | *
45 | * Time complexity: O(1)
46 | *
47 | * @param {Array} heap - The heap array
48 | * @returns {Object|null} Minimum element or null if heap is empty
49 | */
50 | export function peek(heap) {
51 | return heap.length === 0 ? null : heap[0];
52 | }
53 |
54 | /**
55 | * Pop - Remove and Return Minimum Element
56 | *
57 | * Removes and returns the minimum element (root) from the heap.
58 | * The last element replaces the root and is "bubbled down" to maintain heap property.
59 | *
60 | * Time complexity: O(log n)
61 | *
62 | * @param {Array} heap - The heap array
63 | * @returns {Object|null} Minimum element or null if heap is empty
64 | */
65 | export function pop(heap) {
66 | if (heap.length === 0) {
67 | return null;
68 | }
69 |
70 | const first = heap[0];
71 | const last = heap.pop();
72 |
73 | if (last !== first) {
74 | heap[0] = last;
75 | siftDown(heap, last, 0);
76 | }
77 |
78 | return first;
79 | }
80 | function siftUp(heap, node, i) {
81 | let index = i;
82 | while (index > 0) {
83 | const parentIndex = (index - 1) >>> 1;
84 | const parent = heap[parentIndex];
85 | if (compare(parent, node) > 0) {
86 | heap[parentIndex] = node;
87 | heap[index] = parent;
88 | index = parentIndex;
89 | } else {
90 | return;
91 | }
92 | }
93 | }
94 | function siftDown(heap, node, i) {
95 | let index = i;
96 | const length = heap.length;
97 | const halfLength = length >>> 1;
98 | while (index < halfLength) {
99 | const leftIndex = (index + 1) * 2 - 1;
100 | const left = heap[leftIndex];
101 | const rightIndex = leftIndex + 1;
102 | const right = heap[rightIndex];
103 | if (compare(left, node) < 0) {
104 | if (rightIndex < length && compare(right, left) < 0) {
105 | heap[index] = right;
106 | heap[rightIndex] = node;
107 | index = rightIndex;
108 | } else {
109 | heap[index] = left;
110 | heap[leftIndex] = node;
111 | index = leftIndex;
112 | }
113 | } else if (rightIndex < length && compare(right, node) < 0) {
114 | heap[index] = right;
115 | heap[rightIndex] = node;
116 | index = rightIndex;
117 | } else {
118 | return;
119 | }
120 | }
121 | }
122 | function compare(a, b) {
123 | const diff = a.sortIndex - b.sortIndex;
124 | return diff !== 0 ? diff : a.id - b.id;
125 | }
126 |
--------------------------------------------------------------------------------
/src/react-dom-bindings/src/client/ReactDOMComponent.js:
--------------------------------------------------------------------------------
1 | import { setValueForStyles } from './CSSPropertyOperations';
2 | import setTextContent from './setTextContent';
3 | import { setValueForProperty } from './DOMPropertyOperations';
4 | const STYLE = 'style';
5 | const CHILDREN = 'children';
6 | function setInitialDOMProperties(tag, domElement, nextProps) {
7 | for (const propKey in nextProps) {
8 | if (nextProps.hasOwnProperty(propKey)) {
9 | const nextProp = nextProps[propKey];
10 | if (propKey === STYLE) {
11 | setValueForStyles(domElement, nextProp);
12 | } else if (propKey == CHILDREN) {
13 | if (typeof nextProp === 'string') {
14 | setTextContent(domElement, nextProp);
15 | } else if (typeof nextProp === 'number') {
16 | setTextContent(domElement, `${nextProp}`);
17 | }
18 | } else if (nextProp !== null) {
19 | setValueForProperty(domElement, propKey, nextProp)
20 | }
21 | }
22 | }
23 | }
24 | export function setInitialProperties(domElement, tag, props) {
25 | setInitialDOMProperties(tag, domElement, props);
26 | }
27 |
28 | export function diffProperties(domElement, tag, lastProps, nextProps) {
29 | let updatePayload = null;
30 | let propKey;
31 | let styleName;
32 | let styleUpdates = null;
33 | //处理属性的删除 如果说一个属性在老对象里有,新对象没有的话,那就意味着删除
34 | for (propKey in lastProps) {
35 | //如果新属性对象里有此属性,或者老的没有此属性,或者老的是个null
36 | if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] === null) {
37 | continue;
38 | }
39 | if (propKey === STYLE) {
40 | const lastStyle = lastProps[propKey];
41 | for (styleName in lastStyle) {
42 | if (lastStyle.hasOwnProperty(styleName)) {
43 | if (!styleUpdates) {
44 | styleUpdates = {};
45 | }
46 | styleUpdates[styleName] = '';
47 | }
48 | }
49 | } else {
50 | (updatePayload = updatePayload || []).push(propKey, null);
51 | }
52 | }
53 | //遍历新的属性对象
54 | for (propKey in nextProps) {
55 | const nextProp = nextProps[propKey];//新属性中的值
56 | const lastProp = lastProps !== null ? lastProps[propKey] : undefined;//老属性中的值
57 | if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || (nextProp === null && lastProp === null)) {
58 | continue;
59 | }
60 | if (propKey === STYLE) {
61 | if (lastProp) {
62 | //计算要删除的行内样式
63 | for (styleName in lastProp) {
64 | //如果此样式对象里在的某个属性老的style里有,新的style里没有
65 | if (lastProp.hasOwnProperty(styleName) && (!nextProp || !nextProp.hasOwnProperty(styleName))) {
66 | if (!styleUpdates)
67 | styleUpdates = {};
68 | styleUpdates[styleName] = '';
69 | }
70 | }
71 | //遍历新的样式对象
72 | for (styleName in nextProp) {
73 | //如果说新的属性有,并且新属性的值和老属性不一样
74 | if (nextProp.hasOwnProperty(styleName) && lastProp[styleName] !== nextProp[styleName]) {
75 | if (!styleUpdates)
76 | styleUpdates = {};
77 | styleUpdates[styleName] = nextProp[styleName];
78 | }
79 | }
80 | } else {
81 | styleUpdates = nextProp;
82 | }
83 | } else if (propKey === CHILDREN) {
84 | if (typeof nextProp === 'string' || typeof nextProp === 'number') {
85 | (updatePayload = updatePayload || []).push(propKey, nextProp);
86 | }
87 | } else {
88 | (updatePayload = updatePayload || []).push(propKey, nextProp);
89 | }
90 | }
91 | if (styleUpdates) {
92 | (updatePayload = updatePayload || []).push(STYLE, styleUpdates);
93 | }
94 | return updatePayload;//[key1,value1,key2,value2]
95 | }
96 |
97 | export function updateProperties(domElement, updatePayload) {
98 | updateDOMProperties(domElement, updatePayload);
99 | }
100 | function updateDOMProperties(domElement, updatePayload) {
101 | for (let i = 0; i < updatePayload.length; i += 2) {
102 | const propKey = updatePayload[i];
103 | const propValue = updatePayload[i + 1];
104 | if (propKey === STYLE) {
105 | setValueForStyles(domElement, propValue);
106 | } else if (propKey === CHILDREN) {
107 | setTextContent(domElement, propValue);
108 | } else {
109 | setValueForProperty(domElement, propKey, propValue);
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/src/react-dom-bindings/src/events/SyntheticEvent.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Synthetic Event - Cross-Browser Event Wrapper
3 | *
4 | * This module implements React's synthetic event system, which provides a
5 | * consistent interface for handling events across different browsers.
6 | * Synthetic events wrap native browser events and normalize their behavior.
7 | *
8 | * Key features:
9 | * - Cross-browser compatibility
10 | * - Consistent API across different event types
11 | * - Event pooling for performance (in older React versions)
12 | * - preventDefault and stopPropagation support
13 | * - Integration with React's event delegation system
14 | *
15 | * @module SyntheticEvent
16 | */
17 |
18 | import assign from "shared/assign";
19 |
20 | /**
21 | * Helper functions for event state tracking
22 | */
23 | function functionThatReturnsTrue() {
24 | return true;
25 | }
26 |
27 | function functionThatReturnsFalse() {
28 | return false;
29 | }
30 |
31 | /**
32 | * Mouse Event Interface
33 | *
34 | * Defines the properties that should be copied from native mouse events
35 | * to synthetic mouse events. This ensures consistent access to mouse
36 | * position and button information.
37 | */
38 | const MouseEventInterface = {
39 | clientX: 0, // X coordinate relative to viewport
40 | clientY: 0, // Y coordinate relative to viewport
41 | };
42 |
43 | /**
44 | * Create Synthetic Event
45 | *
46 | * Factory function that creates synthetic event constructors with specific interfaces.
47 | * This allows different event types (mouse, keyboard, etc.) to have their own
48 | * property sets while sharing common synthetic event behavior.
49 | *
50 | * @param {Object} inter - Interface object defining which properties to copy from native events
51 | * @returns {Function} Synthetic event constructor
52 | */
53 | function createSyntheticEvent(inter) {
54 | /**
55 | * Synthetic Base Event Constructor
56 | *
57 | * Creates a synthetic event that wraps a native browser event with consistent
58 | * cross-browser behavior and React-specific functionality.
59 | *
60 | * @param {string} reactName - React event prop name (e.g., 'onClick')
61 | * @param {string} reactEventType - Event type (e.g., 'click')
62 | * @param {Fiber} targetInst - Fiber instance of the event target
63 | * @param {Event} nativeEvent - Original native browser event
64 | * @param {Element} nativeEventTarget - DOM element that triggered the event
65 | */
66 | function SyntheticBaseEvent(
67 | reactName,
68 | reactEventType,
69 | targetInst,
70 | nativeEvent,
71 | nativeEventTarget
72 | ) {
73 | // React-specific properties
74 | this._reactName = reactName; // React prop name (onClick, onChange, etc.)
75 | this.type = reactEventType; // Event type (click, change, etc.)
76 | this._targetInst = targetInst; // Target fiber instance
77 | this.nativeEvent = nativeEvent; // Original native event
78 | this.target = nativeEventTarget; // DOM element that triggered event
79 |
80 | // Copy interface-specific properties from native event
81 | for (const propName in inter) {
82 | if (!inter.hasOwnProperty(propName)) {
83 | continue;
84 | }
85 | this[propName] = nativeEvent[propName];
86 | }
87 |
88 | // Initialize event state tracking
89 | this.isDefaultPrevented = functionThatReturnsFalse; // Default behavior not prevented
90 | this.isPropagationStopped = functionThatReturnsFalse; // Propagation not stopped
91 |
92 | return this;
93 | }
94 | // Add synthetic event methods to prototype
95 | assign(SyntheticBaseEvent.prototype, {
96 | /**
97 | * Prevent Default
98 | *
99 | * Prevents the default browser behavior for this event.
100 | * Uses cross-browser compatible approach for older browsers.
101 | */
102 | preventDefault() {
103 | const event = this.nativeEvent;
104 | if (event.preventDefault) {
105 | event.preventDefault();
106 | } else {
107 | // Fallback for older browsers (IE8 and below)
108 | event.returnValue = false;
109 | }
110 | this.isDefaultPrevented = functionThatReturnsTrue;
111 | },
112 |
113 | /**
114 | * Stop Propagation
115 | *
116 | * Stops the event from bubbling up or capturing down the DOM tree.
117 | * Uses cross-browser compatible approach for older browsers.
118 | */
119 | stopPropagation() {
120 | const event = this.nativeEvent;
121 | if (event.stopPropagation) {
122 | event.stopPropagation();
123 | } else {
124 | // Fallback for older browsers (IE8 and below)
125 | event.cancelBubble = true;
126 | }
127 | this.isPropagationStopped = functionThatReturnsTrue;
128 | },
129 | });
130 | return SyntheticBaseEvent;
131 | }
132 | export const SyntheticMouseEvent = createSyntheticEvent(MouseEventInterface);
133 |
--------------------------------------------------------------------------------
/src/react-reconciler/src/ReactFiberBeginWork.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React Fiber Begin Work - Render Phase Implementation
3 | *
4 | * This module implements the "begin work" phase of React's reconciliation process.
5 | * During this phase, React processes each fiber node, updates its state and props,
6 | * and creates child fibers based on the new virtual DOM.
7 | *
8 | * Key responsibilities:
9 | * - Process different types of components (host, function, class)
10 | * - Reconcile children (create, update, delete child fibers)
11 | * - Handle state updates and prop changes
12 | * - Optimize rendering with bailout conditions
13 | *
14 | * @module ReactFiberBeginWork
15 | */
16 |
17 | import {
18 | HostComponent,
19 | HostRoot,
20 | HostText,
21 | IndeterminateComponent,
22 | FunctionComponent,
23 | } from "./ReactWorkTags";
24 | import {
25 | processUpdateQueue,
26 | cloneUpdateQueue,
27 | } from "./ReactFiberClassUpdateQueue";
28 | import { mountChildFibers, reconcileChildFibers } from "./ReactChildFiber";
29 | import { shouldSetTextContent } from "react-dom-bindings/src/client/ReactDOMHostConfig";
30 | import { renderWithHooks } from "react-reconciler/src/ReactFiberHooks";
31 | import { NoLane, NoLanes } from "./ReactFiberLane";
32 |
33 | /**
34 | * Reconcile Children
35 | *
36 | * Creates a new fiber child list based on the new virtual DOM. This function
37 | * implements React's reconciliation algorithm (diffing) to determine what
38 | * changes need to be made to the DOM.
39 | *
40 | * @param {Fiber|null} current - Current (old) parent fiber
41 | * @param {Fiber} workInProgress - Work-in-progress (new) parent fiber
42 | * @param {ReactElement|ReactElement[]|string|number} nextChildren - New child virtual DOM
43 | */
44 | function reconcileChildren(current, workInProgress, nextChildren) {
45 | if (current === null) {
46 | // If there's no current fiber, this is a mount (initial render)
47 | // All children are new, so we use mountChildFibers which doesn't track side effects
48 | workInProgress.child = mountChildFibers(workInProgress, null, nextChildren);
49 | } else {
50 | // If there's a current fiber, this is an update
51 | // We need to diff the old child fiber list with new virtual DOM
52 | // This enables minimal updates and proper side effect tracking
53 | workInProgress.child = reconcileChildFibers(
54 | workInProgress,
55 | current.child,
56 | nextChildren
57 | );
58 | }
59 | }
60 | function updateHostRoot(current, workInProgress, renderLanes) {
61 | const nextProps = workInProgress.pendingProps;
62 | cloneUpdateQueue(current, workInProgress);
63 | //需要知道它的子虚拟DOM,知道它的儿子的虚拟DOM信息
64 | processUpdateQueue(workInProgress, nextProps, renderLanes); //workInProgress.memoizedState={ element }
65 | const nextState = workInProgress.memoizedState;
66 | //nextChildren就是新的子虚拟DOM
67 | const nextChildren = nextState.element; //h1
68 | //根据新的虚拟DOM生成子fiber链表
69 | reconcileChildren(current, workInProgress, nextChildren);
70 | return workInProgress.child; //{tag:5,type:'h1'}
71 | }
72 | /**
73 | * 构建原生组件的子fiber链表
74 | * @param {*} current 老fiber
75 | * @param {*} workInProgress 新fiber h1
76 | */
77 | function updateHostComponent(current, workInProgress) {
78 | const { type } = workInProgress;
79 | const nextProps = workInProgress.pendingProps;
80 | let nextChildren = nextProps.children;
81 | //判断当前虚拟DOM它的儿子是不是一个文本独生子
82 | const isDirectTextChild = shouldSetTextContent(type, nextProps);
83 | if (isDirectTextChild) {
84 | nextChildren = null;
85 | }
86 | reconcileChildren(current, workInProgress, nextChildren);
87 | return workInProgress.child;
88 | }
89 | /**
90 | * 挂载函数组件
91 | * @param {*} current 老fiber
92 | * @param {*} workInProgress 新的fiber
93 | * @param {*} Component 组件类型,也就是函数组件的定义
94 | */
95 | export function mountIndeterminateComponent(
96 | current,
97 | workInProgress,
98 | Component
99 | ) {
100 | const props = workInProgress.pendingProps;
101 | //const value = Component(props);
102 | const value = renderWithHooks(current, workInProgress, Component, props);
103 | workInProgress.tag = FunctionComponent;
104 | reconcileChildren(current, workInProgress, value);
105 | return workInProgress.child;
106 | }
107 | export function updateFunctionComponent(
108 | current,
109 | workInProgress,
110 | Component,
111 | nextProps,
112 | renderLanes
113 | ) {
114 | const nextChildren = renderWithHooks(
115 | current,
116 | workInProgress,
117 | Component,
118 | nextProps,
119 | renderLanes
120 | );
121 | reconcileChildren(current, workInProgress, nextChildren);
122 | return workInProgress.child;
123 | }
124 | /**
125 | * 目标是根据新虚拟DOM构建新的fiber子链表 child .sibling
126 | * @param {*} current 老fiber
127 | * @param {*} workInProgress 新的fiber h1
128 | * @returns
129 | */
130 | export function beginWork(current, workInProgress, renderLanes) {
131 | //在构建fiber树之后清空lanes
132 | workInProgress.lanes = 0;
133 | switch (workInProgress.tag) {
134 | // 因为在React里组件其实有两种,一种是函数组件,一种是类组件,但是它们都是都是函数
135 | case IndeterminateComponent:
136 | return mountIndeterminateComponent(
137 | current,
138 | workInProgress,
139 | workInProgress.type,
140 | renderLanes
141 | );
142 | case FunctionComponent: {
143 | const Component = workInProgress.type;
144 | const nextProps = workInProgress.pendingProps;
145 | return updateFunctionComponent(
146 | current,
147 | workInProgress,
148 | Component,
149 | nextProps,
150 | renderLanes
151 | );
152 | }
153 | case HostRoot:
154 | return updateHostRoot(current, workInProgress, renderLanes);
155 | case HostComponent:
156 | return updateHostComponent(current, workInProgress, renderLanes);
157 | case HostText:
158 | return null;
159 | default:
160 | return null;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react",
4 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
5 |
6 | /* Basic Options */
7 | // "incremental": true, /* Enable incremental compilation */
8 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
9 | "module": "CommonJS" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
10 | // "lib": [], /* Specify library files to be included in the compilation. */
11 | // "allowJs": true, /* Allow javascript files to be compiled. */
12 | // "checkJs": true, /* Report errors in .js files. */
13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
14 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
15 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
16 | "sourceMap": true, /* Generates corresponding '.map' file. */
17 | // "outFile": "./", /* Concatenate and emit output to single file. */
18 | "outDir": "./build/", /* Redirect output structure to the directory. */
19 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
20 | // "composite": true, /* Enable project compilation */
21 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
22 | // "removeComments": true, /* Do not emit comments to output. */
23 | // "noEmit": true, /* Do not emit outputs. */
24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
27 |
28 | /* Strict Type-Checking Options */
29 | "strict": true /* Enable all strict type-checking options. */,
30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
31 | // "strictNullChecks": true, /* Enable strict null checks. */
32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
33 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
37 |
38 | /* Additional Checks */
39 | // "noUnusedLocals": true, /* Report errors on unused locals. */
40 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
43 |
44 | /* Module Resolution Options */
45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
49 | // "typeRoots": [], /* List of folders to include type definitions from. */
50 | // "types": [], /* Type declaration files to be included in compilation. */
51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
52 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
55 |
56 | /* Source Map Options */
57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
61 |
62 | /* Experimental Options */
63 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
65 |
66 | /* Advanced Options */
67 | "skipLibCheck": true /* Skip type checking of declaration files. */,
68 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/react-reconciler/src/ReactFiberCompleteWork.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React Fiber Complete Work - Complete Phase Implementation
3 | *
4 | * This module implements the "complete work" phase of React's reconciliation process.
5 | * During this phase, React creates DOM nodes, processes props, and builds the
6 | * complete DOM tree structure. This phase works bottom-up through the fiber tree.
7 | *
8 | * Key responsibilities:
9 | * - Create DOM nodes for host components
10 | * - Process and apply props to DOM elements
11 | * - Build parent-child DOM relationships
12 | * - Handle refs and side effects
13 | * - Optimize with bailout conditions
14 | *
15 | * @module ReactFiberCompleteWork
16 | */
17 |
18 | import {
19 | createTextInstance,
20 | createInstance,
21 | appendInitialChild,
22 | finalizeInitialChildren,
23 | prepareUpdate,
24 | } from "react-dom-bindings/src/client/ReactDOMHostConfig";
25 | import { NoFlags, Update, Ref } from "./ReactFiberFlags";
26 | import {
27 | HostComponent,
28 | HostRoot,
29 | HostText,
30 | FunctionComponent,
31 | } from "./ReactWorkTags";
32 | import { NoLanes, mergeLanes } from "./ReactFiberLane";
33 |
34 | /**
35 | * Mark Ref
36 | *
37 | * Marks a fiber as having a ref that needs to be attached during commit phase.
38 | *
39 | * @param {Fiber} workInProgress - Fiber with ref to mark
40 | */
41 | function markRef(workInProgress) {
42 | workInProgress.flags |= Ref;
43 | }
44 | /**
45 | * Append All Children
46 | *
47 | * Appends all child DOM nodes of the completed fiber to its parent DOM node.
48 | * This function traverses the fiber tree and builds the actual DOM hierarchy
49 | * by connecting child DOM elements to their parent.
50 | *
51 | * The traversal skips over component fibers (function/class components) and
52 | * only processes host components (DOM elements) and text nodes.
53 | *
54 | * @param {Element} parent - Parent DOM node to append children to
55 | * @param {Fiber} workInProgress - Completed fiber whose children should be appended
56 | */
57 | function appendAllChildren(parent, workInProgress) {
58 | let node = workInProgress.child;
59 |
60 | while (node) {
61 | // If child is a host component (DOM element) or text node, append it
62 | if (node.tag === HostComponent || node.tag === HostText) {
63 | appendInitialChild(parent, node.stateNode);
64 | } else if (node.child !== null) {
65 | // If child is a component (function/class), traverse to its children
66 | node = node.child;
67 | continue;
68 | }
69 |
70 | // If we've processed all children of workInProgress, we're done
71 | if (node === workInProgress) {
72 | return;
73 | }
74 |
75 | // If current node has no sibling, go back up the tree
76 | while (node.sibling === null) {
77 | if (node.return === null || node.return === workInProgress) {
78 | return;
79 | }
80 | // Move back to parent node
81 | node = node.return;
82 | }
83 | node = node.sibling;
84 | }
85 | }
86 | function markUpdate(workInProgress) {
87 | workInProgress.flags |= Update; //给当前的fiber添加更新的副作用
88 | }
89 | /**
90 | * 在fiber(button)的完成阶段准备更新DOM
91 | * @param {*} current button老fiber
92 | * @param {*} workInProgress button的新fiber
93 | * @param {*} type 类型
94 | * @param {*} newProps 新属性
95 | */
96 | function updateHostComponent(current, workInProgress, type, newProps) {
97 | const oldProps = current.memoizedProps; //老的属性
98 | const instance = workInProgress.stateNode; //老的DOM节点
99 | //比较新老属性,收集属性的差异
100 | const updatePayload = prepareUpdate(instance, type, oldProps, newProps);
101 | //让原生组件的新fiber更新队列等于[]
102 | workInProgress.updateQueue = updatePayload;
103 | if (updatePayload) {
104 | markUpdate(workInProgress);
105 | }
106 | }
107 | /**
108 | * 完成一个fiber节点
109 | * @param {*} current 老fiber
110 | * @param {*} workInProgress 新的构建的fiber
111 | */
112 | export function completeWork(current, workInProgress) {
113 | const newProps = workInProgress.pendingProps;
114 | switch (workInProgress.tag) {
115 | case HostRoot:
116 | bubbleProperties(workInProgress);
117 | break;
118 | //如果完成的是原生节点的话
119 | case HostComponent:
120 | ///现在只是在处理创建或者说挂载新节点的逻辑,后面此处分进行区分是初次挂载还是更新
121 | //创建真实的DOM节点
122 | const { type } = workInProgress;
123 | //如果老fiber存在,并且老fiber上真实DOM节点,要走节点更新的逻辑
124 | if (current !== null && workInProgress.stateNode !== null) {
125 | updateHostComponent(current, workInProgress, type, newProps);
126 | if ((current.ref !== workInProgress.ref) !== null) {
127 | markRef(workInProgress);
128 | }
129 | } else {
130 | const instance = createInstance(type, newProps, workInProgress);
131 | //把自己所有的儿子都添加到自己的身上
132 | appendAllChildren(instance, workInProgress);
133 | workInProgress.stateNode = instance;
134 | finalizeInitialChildren(instance, type, newProps);
135 | if (workInProgress.ref !== null) {
136 | markRef(workInProgress);
137 | }
138 | }
139 | bubbleProperties(workInProgress);
140 | break;
141 | case FunctionComponent:
142 | bubbleProperties(workInProgress);
143 | break;
144 | case HostText:
145 | //如果完成的fiber是文本节点,那就创建真实的文本节点
146 | const newText = newProps;
147 | //创建真实的DOM节点并传入stateNode
148 | workInProgress.stateNode = createTextInstance(newText);
149 | //向上冒泡属性
150 | bubbleProperties(workInProgress);
151 | break;
152 | }
153 | }
154 |
155 | function bubbleProperties(completedWork) {
156 | let newChildLanes = NoLanes;
157 | let subtreeFlags = NoFlags;
158 | //遍历当前fiber的所有子节点,把所有的子节的副作用,以及子节点的子节点的副作用全部合并
159 | let child = completedWork.child;
160 | while (child !== null) {
161 | newChildLanes = mergeLanes(
162 | newChildLanes,
163 | mergeLanes(child.lanes, child.childLanes)
164 | );
165 | subtreeFlags |= child.subtreeFlags;
166 | subtreeFlags |= child.flags;
167 | child = child.sibling;
168 | }
169 | completedWork.childLanes = newChildLanes;
170 | completedWork.subtreeFlags = subtreeFlags;
171 | }
172 |
--------------------------------------------------------------------------------
/markdown/minHeap.md:
--------------------------------------------------------------------------------
1 |
5 | ### Min Heap
6 |
7 | 1. A min heap is a sorted complete binary tree
8 |
9 | 2. The data value of any non-terminal node is not greater than the value of its left and right child nodes
10 |
11 | 3. The root node value is the smallest among all heap node values
12 |
13 | Index relationships
14 |
15 | + Left child index = (parent index * 2) + 1
16 |
17 | + Right child index = left child index + 1
18 |
19 | + Parent index = (child index - 1) / 2
20 |
21 |
22 |
23 | ### Why use min heap
24 |
25 | 1. This is because in the min heap structure, the minimum value is at the first position, and React can quickly extract the minimum value.
26 |
27 | 2. Why does React extract the minimum value instead of the maximum value? We can think of it this way: React breaks update tasks into multiple small tasks, each small task's data structure is an object with expirationTime, where expirationTime represents the expiration time of this task. The smaller the expirationTime, the closer the expiration time, and the higher the priority of the task. Extracting the minimum value is equivalent to extracting the highest priority task.
28 |
29 |
30 | ### Min heap scheduling
31 |
32 |
33 | 1. peek() View the top of the heap
34 |
35 | 2. pop() After popping the top of the heap, need to call siftDown function to adjust the heap downward
36 |
37 | 3. push() After adding a new node, need to call siftUp function to adjust the heap upward
38 |
39 | 4. siftDown() Adjust heap structure downward to ensure min heap
40 |
41 | 5. siftUp() Need to adjust heap structure upward to ensure min heap
42 |
43 | ```javaScript
44 | //scheduler/src/SchedulerMinHeap.js
45 | /**
46 | * Add a node
47 | * @param {*} heap
48 | * @param {*} node
49 | */
50 | export function push(heap, node) {
51 | const index = heap.length;
52 | heap.push(node);
53 | siftUp(heap, node, index);
54 | }
55 | /**
56 | * View the top node of the heap
57 | * @param {*} heap
58 | * @returns
59 | */
60 | export function peek(heap) {
61 | return heap.length === 0 ? null : heap[0];
62 | }
63 | /**
64 | * Pop the top node of the heap
65 | * @param {*} heap
66 | * @returns
67 | */
68 | export function pop(heap) {
69 | if (heap.length === 0) {
70 | return null;
71 | }
72 | // Take out the first element, which is the top element of the heap
73 | const first = heap[0];
74 | // Take out the last element, which is the tail element of the heap
75 | const last = heap.pop();
76 | if (last !== first) {
77 | // Swap the top and tail of the heap, then adjust downward from the top
78 | heap[0] = last;
79 | siftDown(heap, last, 0);
80 | }
81 | return first;
82 | }
83 | /**
84 | * Adjust a node upward to put it in the correct position
85 | * @param {*} heap min heap
86 | * @param {*} node node
87 | * @param {*} i index
88 | * @returns
89 | */
90 | function siftUp(heap, node, i) {
91 | let index = i;
92 | while (index > 0) {
93 | // Get parent index (child-1)/2 is equivalent to right shift, this way of writing has the advantage of direct rounding
94 | const parentIndex = index - 1 >>> 1;
95 | // Get parent node
96 | const parent = heap[parentIndex];
97 | // Parent node is larger than child node
98 | if (compare(parent, node) > 0) {
99 | // Put child's value to parent index
100 | heap[parentIndex] = node;
101 | // Put parent's value to child index
102 | heap[index] = parent;
103 | // Let index = parent index
104 | index = parentIndex;
105 | } else {
106 | // Child node is larger than parent node
107 | return;
108 | }
109 | }
110 | }
111 | /**
112 | * Adjust a node downward to put it in the correct position
113 | * @param {*} heap min heap
114 | * @param {*} node node
115 | * @param {*} i index
116 | * @returns
117 | */
118 | function siftDown(heap, node, i) {
119 | let index = i;
120 | const length = heap.length;
121 | // Similar to binary search, but this is sorting
122 | const halfLength = length >>> 1;
123 | while (index < halfLength) {
124 | // Left index
125 | const leftIndex = (index + 1) * 2 - 1;
126 | // Left child node
127 | const left = heap[leftIndex];
128 | // Right index
129 | const rightIndex = leftIndex + 1;
130 | // Right node
131 | const right = heap[rightIndex];
132 | // Compare and move index
133 | if (compare(left, node) < 0) {
134 | // Right node is smaller than left node, swap parent and right node
135 | if (rightIndex < length && compare(right, left) < 0) {
136 | // Current index is right node
137 | heap[index] = right;
138 | // Right index set as parent node
139 | heap[rightIndex] = node;
140 | // Index equals right index
141 | index = rightIndex;
142 | } else {
143 | // Current index set as left node
144 | heap[index] = left;
145 | // Left index set as parent node
146 | heap[leftIndex] = node;
147 | // Index equals left index
148 | index = leftIndex;
149 | }
150 | // Right node is smaller than parent node
151 | } else if (rightIndex < length && compare(right, node) < 0) {
152 | // Current index is right node
153 | heap[index] = right;
154 | // Right index is parent node
155 | heap[rightIndex] = node;
156 | // Index set as right index
157 | index = rightIndex;
158 | } else {
159 | return;
160 | }
161 | }
162 | }
163 | function compare(a, b) {
164 | const diff = a.sortIndex - b.sortIndex;
165 | return diff !== 0 ? diff : a.id - b.id;
166 | }
167 | ```
168 |
169 | ```javaScript
170 | const { push, pop, peek } = require('./SchedulerMinHeap');
171 | let heap = [];
172 | let id = 1;
173 | push(heap, { sortIndex: 1,id:id++ });
174 | push(heap, { sortIndex: 2,id:id++ });
175 | push(heap, { sortIndex: 3,id:id++ });
176 | console.log(peek(heap));
177 | push(heap, { sortIndex: 4 });
178 | push(heap, { sortIndex: 5 });
179 | push(heap, { sortIndex: 6 });
180 | push(heap, { sortIndex: 7 });
181 | console.log(peek(heap));
182 | pop(heap);
183 | console.log(peek(heap));
184 | ```
--------------------------------------------------------------------------------
/src/react-reconciler/src/ReactFiberClassUpdateQueue.js:
--------------------------------------------------------------------------------
1 | import { enqueueConcurrentClassUpdate } from "./ReactFiberConcurrentUpdates";
2 | import assign from "shared/assign";
3 | import { NoLanes, mergeLanes, isSubsetOfLanes } from "./ReactFiberLane";
4 |
5 | export const UpdateState = 0;
6 |
7 | export function initialUpdateQueue(fiber) {
8 | // Create a new update queue
9 | // pending is actually a circular linked list
10 | const queue = {
11 | baseState: fiber.memoizedState, // The current fiber state before this update, updates will be calculated based on it
12 | firstBaseUpdate: null, // The head of the previously skipped update linked list saved on this fiber before this update
13 | lastBaseUpdate: null, // The tail of the previously skipped update linked list saved on this fiber before this update
14 | shared: {
15 | pending: null,
16 | },
17 | };
18 | fiber.updateQueue = queue;
19 | }
20 |
21 | export function createUpdate(lane) {
22 | const update = { tag: UpdateState, lane, next: null };
23 | return update;
24 | }
25 | export function enqueueUpdate(fiber, update, lane) {
26 | // Get the update queue
27 | const updateQueue = fiber.updateQueue;
28 | // Get the shared queue
29 | const sharedQueue = updateQueue.shared;
30 | return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
31 | }
32 | /**
33 | * Calculate the latest state based on the old state and updates in the update queue
34 | * @param {*} workInProgress The fiber to be calculated
35 | */
36 | export function processUpdateQueue(workInProgress, nextProps, renderLanes) {
37 | const queue = workInProgress.updateQueue;
38 | // Old linked list head
39 | let firstBaseUpdate = queue.firstBaseUpdate;
40 | // Old linked list tail
41 | let lastBaseUpdate = queue.lastBaseUpdate;
42 | // New linked list tail
43 | const pendingQueue = queue.shared.pending;
44 | // Merge new and old linked lists into a single linked list
45 | if (pendingQueue !== null) {
46 | queue.shared.pending = null;
47 | // New linked list tail
48 | const lastPendingUpdate = pendingQueue;
49 | // New linked list tail
50 | const firstPendingUpdate = lastPendingUpdate.next;
51 | // Cut off the old linked list to make it a single linked list, circular linked list becomes single linked list
52 | lastPendingUpdate.next = null;
53 | // If there is no old linked list
54 | if (lastBaseUpdate === null) {
55 | // Point to the new linked list head
56 | firstBaseUpdate = firstPendingUpdate;
57 | } else {
58 | lastBaseUpdate.next = firstPendingUpdate;
59 | }
60 | lastBaseUpdate = lastPendingUpdate;
61 | }
62 | // If the linked list is not empty firstBaseUpdate=>lastBaseUpdate
63 | if (firstBaseUpdate !== null) {
64 | // State before the last skipped update
65 | let newState = queue.baseState;
66 | // Lanes of updates that have not been executed yet
67 | let newLanes = NoLanes;
68 | let newBaseState = null;
69 | let newFirstBaseUpdate = null;
70 | let newLastBaseUpdate = null;
71 | let update = firstBaseUpdate; //updateA
72 | do {
73 | // Get the lane of this update
74 | const updateLane = update.lane;
75 | // If updateLane is not a subset of renderLanes, it means this render does not need to process this update, so this update needs to be skipped
76 | if (!isSubsetOfLanes(renderLanes, updateLane)) {
77 | // Clone this update
78 | const clone = {
79 | id: update.id,
80 | lane: updateLane,
81 | payload: update.payload,
82 | };
83 | // If the new skipped base linked list is empty, it means the current update is the first skipped update
84 | if (newLastBaseUpdate === null) {
85 | // Let both the head and tail of the new skipped linked list point to this first skipped update
86 | newFirstBaseUpdate = newLastBaseUpdate = clone;
87 | // Calculate and save the new baseState as the state when this update is skipped
88 | newBaseState = newState; // ""
89 | } else {
90 | newLastBaseUpdate = newLastBaseUpdate.next = clone;
91 | }
92 | // If there are skipped updates, merge the lanes of the skipped updates into newLanes,
93 | // Finally, newLanes will be assigned to fiber.lanes
94 | newLanes = mergeLanes(newLanes, updateLane);
95 | } else {
96 | // It means there are already skipped updates
97 | if (newLastBaseUpdate !== null) {
98 | const clone = {
99 | id: update.id,
100 | lane: 0,
101 | payload: update.payload,
102 | };
103 | newLastBaseUpdate = newLastBaseUpdate.next = clone;
104 | }
105 | newState = getStateFromUpdate(update, newState);
106 | }
107 | update = update.next;
108 | } while (update);
109 | // If there are no skipped updates
110 | if (!newLastBaseUpdate) {
111 | newBaseState = newState;
112 | }
113 | queue.baseState = newBaseState;
114 | queue.firstBaseUpdate = newFirstBaseUpdate;
115 | queue.lastBaseUpdate = newLastBaseUpdate;
116 | workInProgress.lanes = newLanes;
117 | // After this render is complete, it will check if there are any non-zero lanes on this fiber, and if so, it will render again
118 | workInProgress.memoizedState = newState;
119 | }
120 | }
121 | /**
122 | * state=0 update=>1 update=2
123 | * Calculate new state based on old state and update
124 | * @param {*} update The update object, there are actually many types
125 | * @param {*} prevState
126 | */
127 | function getStateFromUpdate(update, prevState, nextProps) {
128 | switch (update.tag) {
129 | case UpdateState:
130 | const { payload } = update;
131 | let partialState;
132 | if (typeof payload === "function") {
133 | partialState = payload.call(null, prevState, nextProps);
134 | } else {
135 | partialState = payload;
136 | }
137 | return assign({}, prevState, partialState);
138 | }
139 | }
140 |
141 | export function cloneUpdateQueue(current, workInProgress) {
142 | const workInProgressQueue = workInProgress.updateQueue;
143 | const currentQueue = current.updateQueue;
144 | // If the new queue and the old queue are not the same object
145 | if (currentQueue === workInProgressQueue) {
146 | const clone = {
147 | baseState: currentQueue.baseState,
148 | firstBaseUpdate: currentQueue.firstBaseUpdate,
149 | firstBaseUpdate: currentQueue.firstBaseUpdate,
150 | lastBaseUpdate: currentQueue.lastBaseUpdate,
151 | shared: currentQueue.shared,
152 | };
153 | workInProgress.updateQueue = clone;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/scheduler/src/forks/Scheduler.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React Scheduler - Task Scheduling and Time Slicing
3 | *
4 | * This module implements React's scheduler which manages task execution with
5 | * priority-based scheduling and time slicing. It ensures that high-priority
6 | * tasks can interrupt low-priority ones and that the main thread remains
7 | * responsive by yielding control back to the browser.
8 | *
9 | * Key features:
10 | * - Priority-based task scheduling
11 | * - Time slicing with configurable frame intervals
12 | * - Task queue management using min heap
13 | * - Cooperative scheduling with shouldYield
14 | *
15 | * @module Scheduler
16 | */
17 |
18 | import { push, peek, pop } from "./SchedulerMinHeap";
19 | import {
20 | ImmediatePriority,
21 | UserBlockingPriority,
22 | NormalPriority,
23 | LowPriority,
24 | IdlePriority,
25 | } from "./SchedulerPriorities";
26 |
27 | /**
28 | * Get Current Time
29 | *
30 | * Returns the current time in milliseconds using high-resolution timer.
31 | *
32 | * @returns {number} Current time in milliseconds
33 | */
34 | function getCurrentTime() {
35 | return performance.now();
36 | }
37 |
38 | // Maximum 31-bit signed integer value
39 | var maxSigned31BitInt = 1073741823;
40 |
41 | // Priority timeout constants (in milliseconds)
42 | var IMMEDIATE_PRIORITY_TIMEOUT = -1; // Expires immediately
43 | var USER_BLOCKING_PRIORITY_TIMEOUT = 250; // Expires in 250ms
44 | var NORMAL_PRIORITY_TIMEOUT = 5000; // Expires in 5 seconds
45 | var LOW_PRIORITY_TIMEOUT = 10000; // Expires in 10 seconds
46 | var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; // Never expires
47 |
48 | // Scheduler state
49 | let taskIdCounter = 1; // Task ID counter for unique identification
50 | const taskQueue = []; // Min heap of scheduled tasks
51 | let scheduleHostCallback = null; // Host callback scheduler function
52 | let startTime = -1; // Work start time for yielding calculations
53 | let currentTask = null; // Currently executing task
54 |
55 | // Time slicing configuration
56 | // React requests 5ms per frame for task execution
57 | // If tasks don't complete within 5ms, React yields control back to browser
58 | const frameInterval = 5;
59 |
60 | const channel = new MessageChannel();
61 | var port2 = channel.port2;
62 | var port1 = channel.port1;
63 | port1.onmessage = performWorkUntilDeadline;
64 |
65 | /**
66 | * 按优先级执行任务
67 | * @param {*} priorityLevel
68 | * @param {*} callback
69 | */
70 | function scheduleCallback(priorityLevel, callback) {
71 | // 获取当前的时候
72 | const currentTime = getCurrentTime();
73 | // 此任务的开时间
74 | const startTime = currentTime;
75 | //超时时间
76 | let timeout;
77 | //根据优先级计算过期的时间
78 | switch (priorityLevel) {
79 | case ImmediatePriority:
80 | timeout = IMMEDIATE_PRIORITY_TIMEOUT; // -1
81 | break;
82 | case UserBlockingPriority:
83 | timeout = USER_BLOCKING_PRIORITY_TIMEOUT; // 250ms
84 | break;
85 | case IdlePriority:
86 | timeout = IDLE_PRIORITY_TIMEOUT; //1073741823
87 | break;
88 | case LowPriority:
89 | timeout = LOW_PRIORITY_TIMEOUT; //10000
90 | break;
91 | case NormalPriority:
92 | default:
93 | timeout = NORMAL_PRIORITY_TIMEOUT; //5000
94 | break;
95 | }
96 | //计算此任务的过期时间
97 | const expirationTime = startTime + timeout;
98 | const newTask = {
99 | id: taskIdCounter++,
100 | callback, //回调函数或者说任务函数
101 | priorityLevel, //优先级别
102 | startTime, //任务的开始时间
103 | expirationTime, //任务的过期时间
104 | sortIndex: expirationTime, //排序依赖
105 | };
106 | //向任务最小堆里添加任务,排序的依据是过期时间
107 | push(taskQueue, newTask);
108 | //flushWork执行工作,刷新工作,执行任务,司机接人
109 | requestHostCallback(workLoop);
110 | return newTask;
111 | }
112 | function shouldYieldToHost() {
113 | //用当前时间减去开始的时间就是过去的时间
114 | const timeElapsed = getCurrentTime() - startTime;
115 | //如果流逝或者说经过的时间小于5毫秒,那就不需要放弃执行
116 | if (timeElapsed < frameInterval) {
117 | return false;
118 | } //否则就是表示5毫秒用完了,需要放弃执行
119 | return true;
120 | }
121 | function workLoop(startTime) {
122 | let currentTime = startTime;
123 | //取出优先级最高的任务 局长
124 | currentTask = peek(taskQueue);
125 | while (currentTask !== null) {
126 | //如果此任务的过期时间小于当前时间,也就是说没有过期,并且需要放弃执行 时间片到期
127 | if (currentTask.expirationTime > currentTime && shouldYieldToHost()) {
128 | //跳出工作循环
129 | break;
130 | }
131 | //取出当前的任务中的回调函数 performConcurrentWorkOnRoot
132 | const callback = currentTask.callback;
133 | if (typeof callback === "function") {
134 | currentTask.callback = null;
135 | //执行工作,如果返回新的函数,则表示当前的工作没有完成
136 | const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
137 | const continuationCallback = callback(didUserCallbackTimeout);
138 | if (typeof continuationCallback === "function") {
139 | currentTask.callback = continuationCallback;
140 | return true; //还有任务要执行
141 | }
142 | //如果此任务已经完成,则不需要再继续执行了,可以把此任务弹出
143 | if (currentTask === peek(taskQueue)) {
144 | pop(taskQueue);
145 | }
146 | } else {
147 | pop(taskQueue);
148 | }
149 | //如果当前的任务执行完了,或者当前任务不合法,取出下一个任务执行
150 | currentTask = peek(taskQueue);
151 | }
152 | //如果循环结束还有未完成的任务,那就表示hasMoreWork=true
153 | if (currentTask !== null) {
154 | return true;
155 | }
156 | //没有任何要完成的任务了
157 | return false;
158 | }
159 | function requestHostCallback(workLoop) {
160 | //先缓存回调函数
161 | scheduleHostCallback = workLoop;
162 | //执行工作直到截止时间
163 | schedulePerformWorkUntilDeadline();
164 | }
165 | function schedulePerformWorkUntilDeadline() {
166 | port2.postMessage(null);
167 | }
168 | function performWorkUntilDeadline() {
169 | if (scheduleHostCallback) {
170 | // 先获取开始执行任务的时间
171 | //表示时间片的开始
172 | startTime = getCurrentTime();
173 | // 是否有更多的工作要做
174 | let hasMoreWork = true;
175 | try {
176 | //执行 flushWork ,并判断有没有返回值
177 | hasMoreWork = scheduleHostCallback(startTime);
178 | } finally {
179 | //执行完以后如果为true,说明还有更多工作要做
180 | if (hasMoreWork) {
181 | //继续执行
182 | schedulePerformWorkUntilDeadline();
183 | } else {
184 | scheduleHostCallback = null;
185 | }
186 | }
187 | }
188 | }
189 | function unstable_cancelCallback(task) {
190 | task.callback = null;
191 | }
192 | export {
193 | scheduleCallback as unstable_scheduleCallback,
194 | shouldYieldToHost as unstable_shouldYield,
195 | ImmediatePriority as unstable_ImmediatePriority,
196 | UserBlockingPriority as unstable_UserBlockingPriority,
197 | NormalPriority as unstable_NormalPriority,
198 | LowPriority as unstable_LowPriority,
199 | IdlePriority as unstable_IdlePriority,
200 | unstable_cancelCallback,
201 | getCurrentTime as now,
202 | };
203 |
204 | /*
205 | 开始执行任务队列中的任务
206 |
207 | function flushWork(startTime) {
208 | return workLoop(startTime);
209 | }
210 | */
211 |
--------------------------------------------------------------------------------
/src/react-reconciler/src/ReactFiber.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React Fiber - Core Fiber Node Implementation
3 | *
4 | * This module contains the Fiber node structure and related utilities.
5 | * Fiber is React's reconciliation algorithm that enables interruptible rendering,
6 | * priority-based scheduling, and efficient updates.
7 | *
8 | * @module ReactFiber
9 | */
10 |
11 | import {
12 | HostComponent,
13 | HostRoot,
14 | IndeterminateComponent,
15 | HostText,
16 | } from "./ReactWorkTags";
17 | import { NoFlags } from "./ReactFiberFlags";
18 | import { NoLanes } from "./ReactFiberLane";
19 |
20 | /**
21 | * Fiber Node Constructor
22 | *
23 | * Creates a new Fiber node which represents a unit of work in React's reconciliation.
24 | * Each React element corresponds to a Fiber node in the Fiber tree.
25 | *
26 | * @param {number} tag - Fiber type (FunctionComponent: 0, ClassComponent: 1, HostComponent: 5, HostRoot: 3)
27 | * @param {any} pendingProps - New props waiting to be processed
28 | * @param {string|number|null} key - Unique identifier for reconciliation
29 | */
30 | export function FiberNode(tag, pendingProps, key) {
31 | // Instance properties
32 | this.tag = tag; // Fiber type identifier
33 | this.key = key; // Unique key for reconciliation
34 | this.type = null; // Component type (function, class, or DOM tag name)
35 | this.stateNode = null; // Reference to DOM node or component instance
36 |
37 | // Fiber tree structure - forms a linked list tree
38 | this.return = null; // Parent fiber (return pointer)
39 | this.child = null; // First child fiber
40 | this.sibling = null; // Next sibling fiber
41 |
42 | // Props and state management
43 | this.pendingProps = pendingProps; // New props waiting to be applied
44 | this.memoizedProps = null; // Props from the last completed render
45 |
46 | // State varies by fiber type:
47 | // - Class components: component instance state
48 | // - Host root: elements to render
49 | // - Function components: hooks state
50 | this.memoizedState = null;
51 |
52 | // Update queue for managing state updates
53 | this.updateQueue = null;
54 |
55 | // Side effects - indicate what operations need to be performed
56 | this.flags = NoFlags; // Side effects for this fiber
57 | this.subtreeFlags = NoFlags; // Side effects for child fibers
58 |
59 | // Double buffering - enables efficient updates
60 | this.alternate = null; // Alternate fiber for double buffering
61 |
62 | // Additional properties
63 | this.index = 0; // Index of this fiber among siblings
64 | this.deletions = null; // Child fibers to be deleted
65 | this.lanes = NoLanes; // Priority lanes for this fiber
66 | this.childLanes = NoLanes; // Priority lanes for child fibers
67 | this.ref = null; // Ref object or callback
68 | }
69 | // We use a double buffering pooling technique because we know that we'll only ever need at most two versions of a tree.
70 | // We pool the "other" unused node that we're free to reuse.
71 |
72 | // This is lazily created to avoid allocating
73 | // extra objects for things that are never updated. It also allow us to
74 | // reclaim the extra memory if needed.
75 | export function createFiber(tag, pendingProps, key) {
76 | return new FiberNode(tag, pendingProps, key);
77 | }
78 | export function createHostRootFiber() {
79 | return createFiber(HostRoot, null, null);
80 | }
81 | /**
82 | * Create Work-in-Progress Fiber
83 | *
84 | * Creates a work-in-progress fiber based on the current fiber and new props.
85 | * This implements React's double buffering technique where we maintain two fiber trees:
86 | * - Current tree: represents the current state
87 | * - Work-in-progress tree: represents the next state being built
88 | *
89 | * Reuse has two meanings:
90 | * 1. Reuse the old fiber object structure
91 | * 2. Reuse the actual DOM nodes when possible
92 | *
93 | * @param {Fiber} current - The current fiber (old fiber)
94 | * @param {any} pendingProps - New props to be applied
95 | * @returns {Fiber} Work-in-progress fiber
96 | */
97 | export function createWorkInProgress(current, pendingProps) {
98 | let workInProgress = current.alternate;
99 |
100 | if (workInProgress === null) {
101 | // No alternate exists, create a new fiber
102 | workInProgress = createFiber(current.tag, pendingProps, current.key);
103 | workInProgress.type = current.type;
104 | workInProgress.stateNode = current.stateNode;
105 |
106 | // Establish bidirectional connection for double buffering
107 | workInProgress.alternate = current;
108 | current.alternate = workInProgress;
109 | } else {
110 | // Reuse existing alternate fiber
111 | workInProgress.pendingProps = pendingProps;
112 | workInProgress.type = current.type;
113 |
114 | // Reset side effects
115 | workInProgress.flags = NoFlags;
116 | workInProgress.subtreeFlags = NoFlags;
117 | workInProgress.deletions = null;
118 | }
119 |
120 | // Copy properties from current fiber
121 | workInProgress.child = current.child;
122 | workInProgress.memoizedProps = current.memoizedProps;
123 | workInProgress.memoizedState = current.memoizedState;
124 | workInProgress.updateQueue = current.updateQueue;
125 | workInProgress.sibling = current.sibling;
126 | workInProgress.index = current.index;
127 | workInProgress.ref = current.ref;
128 | workInProgress.flags = current.flags;
129 | workInProgress.lanes = current.lanes;
130 | workInProgress.childLanes = current.childLanes;
131 |
132 | return workInProgress;
133 | }
134 | /**
135 | * Create Fiber from React Element
136 | *
137 | * Converts a React element (virtual DOM node) into a Fiber node.
138 | * This is used during the reconciliation process to build the Fiber tree.
139 | *
140 | * @param {ReactElement} element - React element to convert
141 | * @returns {Fiber} New fiber node
142 | */
143 | export function createFiberFromElement(element) {
144 | const { type, key, props: pendingProps } = element;
145 | return createFiberFromTypeAndProps(type, key, pendingProps);
146 | }
147 |
148 | /**
149 | * Create Fiber from Type and Props
150 | *
151 | * Creates a fiber node based on the component type and props.
152 | * Determines the appropriate fiber tag based on the component type.
153 | *
154 | * @param {string|function|class} type - Component type
155 | * @param {string|number|null} key - Unique key
156 | * @param {object} pendingProps - Component props
157 | * @returns {Fiber} New fiber node
158 | */
159 | function createFiberFromTypeAndProps(type, key, pendingProps) {
160 | let tag = IndeterminateComponent; // Default for function components
161 |
162 | // If type is a string (span, div, etc.), this is a host component
163 | if (typeof type === "string") {
164 | tag = HostComponent;
165 | }
166 |
167 | const fiber = createFiber(tag, pendingProps, key);
168 | fiber.type = type;
169 | return fiber;
170 | }
171 |
172 | /**
173 | * Create Fiber from Text Content
174 | *
175 | * Creates a fiber node for text content. Text nodes are leaf nodes
176 | * in the Fiber tree and represent actual text content in the DOM.
177 | *
178 | * @param {string} content - Text content
179 | * @returns {Fiber} Text fiber node
180 | */
181 | export function createFiberFromText(content) {
182 | return createFiber(HostText, content, null);
183 | }
184 |
--------------------------------------------------------------------------------
/src/react-reconciler/src/ReactFiberLane.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React Fiber Lane - Priority-Based Scheduling System
3 | *
4 | * This module implements React's lane-based priority system for concurrent rendering.
5 | * Lanes are represented as binary flags, allowing efficient bitwise operations for
6 | * priority management, merging, and comparison.
7 | *
8 | * Key concepts:
9 | * - Lower numeric values = higher priority
10 | * - Lanes can be combined using bitwise OR
11 | * - Each lane represents a different type of work or priority level
12 | * - Enables fine-grained control over update scheduling
13 | *
14 | * Lane types (from highest to lowest priority):
15 | * - SyncLane: Synchronous updates (highest priority)
16 | * - InputContinuous: User input events (high priority)
17 | * - Default: Normal updates (medium priority)
18 | * - Idle: Background updates (lowest priority)
19 | *
20 | * @module ReactFiberLane
21 | */
22 |
23 | import { allowConcurrentByDefault } from "shared/ReactFeatureFlags";
24 |
25 | // Total number of available lanes (31-bit system)
26 | export const TotalLanes = 31;
27 |
28 | // Lane constants - binary representation for efficient bitwise operations
29 | export const NoLanes = 0b0000000000000000000000000000000; // No work scheduled
30 | export const NoLane = 0b0000000000000000000000000000000; // Alias for NoLanes
31 |
32 | // Synchronous lane - highest priority, cannot be interrupted
33 | export const SyncLane = 0b0000000000000000000000000000001;
34 |
35 | // Input continuous lanes - for user interactions
36 | export const InputContinuousHydrationLane = 0b0000000000000000000000000000010;
37 | export const InputContinuousLane = 0b0000000000000000000000000000100;
38 |
39 | // Default lanes - for normal updates
40 | export const DefaultHydrationLane = 0b0000000000000000000000000001000;
41 | export const DefaultLane = 0b0000000000000000000000000010000;
42 |
43 | // Special purpose lanes
44 | export const SelectiveHydrationLane = 0b0001000000000000000000000000000;
45 | export const IdleHydrationLane = 0b0010000000000000000000000000000;
46 | export const IdleLane = 0b0100000000000000000000000000000;
47 | export const OffscreenLane = 0b1000000000000000000000000000000;
48 |
49 | // Mask for non-idle lanes (excludes idle and offscreen work)
50 | const NonIdleLanes = 0b0001111111111111111111111111111;
51 |
52 | // Timestamp constants
53 | export const NoTimestamp = -1; // Indicates no timestamp set
54 |
55 | /**
56 | * Mark Root Updated
57 | *
58 | * Marks a root as having pending updates in the specified lane.
59 | * This adds the update lane to the root's pending lanes using bitwise OR.
60 | *
61 | * @param {FiberRoot} root - Fiber root to mark as updated
62 | * @param {Lane} updateLane - Lane containing the update
63 | */
64 | export function markRootUpdated(root, updateLane) {
65 | // Add the update lane to pending lanes (bitwise OR combines lanes)
66 | root.pendingLanes |= updateLane;
67 | }
68 |
69 | /**
70 | * Get Next Lanes
71 | *
72 | * Determines which lanes should be processed next based on priority.
73 | * This function implements React's scheduling logic, considering:
74 | * - Pending work lanes
75 | * - Currently rendering work
76 | * - Priority levels
77 | *
78 | * @param {FiberRoot} root - Fiber root to get lanes for
79 | * @param {Lanes} wipLanes - Currently work-in-progress lanes
80 | * @returns {Lanes} Next lanes to process
81 | */
82 | export function getNextLanes(root, wipLanes) {
83 | // Get all lanes with pending updates
84 | const pendingLanes = root.pendingLanes;
85 | if (pendingLanes === NoLanes) {
86 | return NoLanes;
87 | }
88 |
89 | // Get the highest priority lanes from pending work
90 | const nextLanes = getHighestPriorityLanes(pendingLanes);
91 |
92 | if (wipLanes !== NoLane && wipLanes !== nextLanes) {
93 | // If new lanes have lower priority than current work, continue current work
94 | // (Higher numeric value = lower priority)
95 | if (nextLanes > wipLanes) {
96 | return wipLanes;
97 | }
98 | }
99 |
100 | return nextLanes;
101 | }
102 | export function getHighestPriorityLanes(lanes) {
103 | return getHighestPriorityLane(lanes);
104 | }
105 | //找到最右边的1 只能返回一个车道
106 | export function getHighestPriorityLane(lanes) {
107 | return lanes & -lanes;
108 | }
109 | export function includesNonIdleWork(lanes) {
110 | return (lanes & NonIdleLanes) !== NoLanes;
111 | }
112 | /**
113 | * 源码此处的逻辑有大的改变动
114 | * 以前
115 | * pendingLanes= 001100
116 | * 找到最右边的1 000100
117 | * nextLanes 000111
118 | *
119 | * 现在的源码已经改了
120 | * pendingLanes= 001100
121 | * 找到最右边的1 000100
122 | * update 000010
123 | * 那是不是意味着以前是不检测车道上有没有任务的,就是先拿优先级再检测?
124 | */
125 |
126 | export function isSubsetOfLanes(set, subset) {
127 | return (set & subset) === subset;
128 | }
129 | export function mergeLanes(a, b) {
130 | return a | b;
131 | }
132 |
133 | export function includesBlockingLane(root, lanes) {
134 | //如果允许默认并行渲染
135 | if (allowConcurrentByDefault) {
136 | return false;
137 | }
138 | const SyncDefaultLanes = InputContinuousLane | DefaultLane;
139 | return (lanes & SyncDefaultLanes) !== NoLane;
140 | }
141 | /**
142 | * 取是左侧的1的索引
143 | * 00011000
144 | * 7-3=4
145 | */
146 | function pickArbitraryLaneIndex(lanes) {
147 | //clz32返回最左侧的1的左边0的个数
148 | // 000100010
149 | return 31 - Math.clz32(lanes);
150 | }
151 |
152 | export function markStarvedLanesAsExpired(root, currentTime) {
153 | //获取当前有更新赛 道
154 | const pendingLanes = root.pendingLanes;
155 | //记录每个赛道上的过期时间
156 | const expirationTimes = root.expirationTimes;
157 | let lanes = pendingLanes;
158 | while (lanes > 0) {
159 | //获取最左侧的1的索引
160 | const index = pickArbitraryLaneIndex(lanes);
161 | const lane = 1 << index;
162 | const expirationTime = expirationTimes[index];
163 | //如果此赛道上没有过期时间,说明没有为此车道设置过期时间
164 | if (expirationTime === NoTimestamp) {
165 | expirationTimes[index] = computeExpirationTime(lane, currentTime);
166 | //如果此车道的过期时间已经小于等于当前时间了
167 | } else if (expirationTime <= currentTime) {
168 | //把此车道添加到过期车道里
169 | root.expiredLanes |= lane;
170 | console.log(
171 | "expirationTime",
172 | expirationTime,
173 | "currentTime",
174 | currentTime,
175 | root.expiredLanes
176 | );
177 | }
178 | lanes &= ~lane;
179 | }
180 | }
181 | function computeExpirationTime(lane, currentTime) {
182 | switch (lane) {
183 | case SyncLane:
184 | case InputContinuousLane:
185 | return currentTime + 250;
186 | case DefaultLane:
187 | return currentTime + 5000;
188 | case IdleLane:
189 | return NoTimestamp;
190 | default:
191 | return NoTimestamp;
192 | }
193 | }
194 |
195 | export function createLaneMap(initial) {
196 | const laneMap = [];
197 | for (let i = 0; i < TotalLanes; i++) {
198 | laneMap.push(initial);
199 | }
200 | return laneMap;
201 | }
202 | export function includesExpiredLane(root, lanes) {
203 | return (lanes & root.expiredLanes) !== NoLanes;
204 | }
205 | export function markRootFinished(root, remainingLanes) {
206 | //pendingLanes根上所有的将要被渲染的车道 1和2
207 | //remainingLanes 2
208 | //noLongerPendingLanes指的是已经更新过的lane
209 | const noLongerPendingLanes = root.pendingLanes & ~remainingLanes;
210 | root.pendingLanes = remainingLanes;
211 | const expirationTimes = root.expirationTimes;
212 | let lanes = noLongerPendingLanes;
213 | while (lanes > 0) {
214 | //获取最左侧的1的索引
215 | const index = pickArbitraryLaneIndex(lanes);
216 | const lane = 1 << index;
217 | //清除已经计算过的车道的过期时间
218 | expirationTimes[index] = NoTimestamp;
219 | lanes &= ~lane;
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/src/react-dom-bindings/src/events/DOMPluginEventSystem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * DOM Plugin Event System - React's Event Delegation Implementation
3 | *
4 | * This module implements React's synthetic event system with event delegation.
5 | * Instead of attaching event listeners to individual DOM elements, React uses
6 | * a single event delegation system at the root container level.
7 | *
8 | * Key features:
9 | * - Event delegation at root container level
10 | * - Cross-browser event normalization
11 | * - Synthetic event creation and dispatch
12 | * - Support for both capture and bubble phases
13 | * - Priority-based event handling
14 | *
15 | * Benefits:
16 | * - Better performance (fewer event listeners)
17 | * - Consistent behavior across browsers
18 | * - Dynamic event handling for added/removed elements
19 | * - Memory efficiency
20 | *
21 | * @module DOMPluginEventSystem
22 | */
23 |
24 | import { allNativeEvents } from "./EventRegistry";
25 | import * as SimpleEventPlugin from "./plugins/SimpleEventPlugin";
26 | import { IS_CAPTURE_PHASE } from "./EventSystemFlags";
27 | import { createEventListenerWrapperWithPriority } from "./ReactDOMEventListener";
28 | import {
29 | addEventCaptureListener,
30 | addEventBubbleListener,
31 | } from "./EventListener";
32 | import getEventTarget from "./getEventTarget";
33 | import { HostComponent } from "react-reconciler/src/ReactWorkTags";
34 | import getListener from "./getListener";
35 |
36 | // Register all simple events (click, change, etc.)
37 | SimpleEventPlugin.registerEvents();
38 |
39 | // Unique marker to track if container already has listeners
40 | const listeningMarker = `_reactListening` + Math.random().toString(36).slice(2);
41 | /**
42 | * Listen to All Supported Events
43 | *
44 | * Sets up event delegation for all supported events on the root container.
45 | * This function is called once per root container and registers listeners
46 | * for both capture and bubble phases of all native events.
47 | *
48 | * The event delegation approach means that instead of attaching listeners
49 | * to individual elements, we attach them to the root and handle all events
50 | * from there, determining the actual target through event bubbling/capturing.
51 | *
52 | * @param {Element} rootContainerElement - Root DOM element (e.g., div#root)
53 | */
54 | export function listenToAllSupportedEvents(rootContainerElement) {
55 | // Only set up listeners once per container
56 | if (!rootContainerElement[listeningMarker]) {
57 | rootContainerElement[listeningMarker] = true;
58 |
59 | // Register listeners for all native events (click, change, focus, etc.)
60 | allNativeEvents.forEach((domEventName) => {
61 | // Register for capture phase (events flow down from root to target)
62 | listenToNativeEvent(domEventName, true, rootContainerElement);
63 | // Register for bubble phase (events flow up from target to root)
64 | listenToNativeEvent(domEventName, false, rootContainerElement);
65 | });
66 | }
67 | }
68 | /**
69 | * Listen to Native Event
70 | *
71 | * Registers a native event listener on the target DOM element for a specific
72 | * event type and phase (capture or bubble). This creates the actual DOM event
73 | * listener that will handle all events of this type.
74 | *
75 | * @param {string} domEventName - Native event name (e.g., 'click', 'change')
76 | * @param {boolean} isCapturePhaseListener - Whether this is for capture phase
77 | * @param {Element} target - Target DOM element (root container)
78 | */
79 | export function listenToNativeEvent(
80 | domEventName,
81 | isCapturePhaseListener,
82 | target
83 | ) {
84 | // Set event system flags: 0 for bubble phase, IS_CAPTURE_PHASE for capture
85 | let eventSystemFlags = 0;
86 | if (isCapturePhaseListener) {
87 | eventSystemFlags |= IS_CAPTURE_PHASE;
88 | }
89 | addTrappedEventListener(
90 | target,
91 | domEventName,
92 | eventSystemFlags,
93 | isCapturePhaseListener
94 | );
95 | }
96 |
97 | function addTrappedEventListener(
98 | targetContainer,
99 | domEventName,
100 | eventSystemFlags,
101 | isCapturePhaseListener
102 | ) {
103 | const listener = createEventListenerWrapperWithPriority(
104 | targetContainer,
105 | domEventName,
106 | eventSystemFlags
107 | );
108 | if (isCapturePhaseListener) {
109 | addEventCaptureListener(targetContainer, domEventName, listener);
110 | } else {
111 | addEventBubbleListener(targetContainer, domEventName, listener);
112 | }
113 | }
114 |
115 | export function dispatchEventForPluginEventSystem(
116 | domEventName,
117 | eventSystemFlags,
118 | nativeEvent,
119 | targetInst,
120 | targetContainer
121 | ) {
122 | dispatchEventForPlugins(
123 | domEventName,
124 | eventSystemFlags,
125 | nativeEvent,
126 | targetInst,
127 | targetContainer
128 | );
129 | }
130 |
131 | function dispatchEventForPlugins(
132 | domEventName,
133 | eventSystemFlags,
134 | nativeEvent,
135 | targetInst,
136 | targetContainer
137 | ) {
138 | const nativeEventTarget = getEventTarget(nativeEvent);
139 | //派发事件的数组
140 | const dispatchQueue = [];
141 | extractEvents(
142 | dispatchQueue,
143 | domEventName,
144 | targetInst,
145 | nativeEvent,
146 | nativeEventTarget,
147 | eventSystemFlags,
148 | targetContainer
149 | );
150 | processDispatchQueue(dispatchQueue, eventSystemFlags);
151 | }
152 | function processDispatchQueue(dispatchQueue, eventSystemFlags) {
153 | //判断是否在捕获阶段
154 | const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
155 | for (let i = 0; i < dispatchQueue.length; i++) {
156 | const { event, listeners } = dispatchQueue[i];
157 | processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
158 | }
159 | }
160 | function executeDispatch(event, listener, currentTarget) {
161 | //合成事件实例currentTarget是在不断的变化的
162 | // event nativeEventTarget 它的是原始的事件源,是永远不变的
163 | // event currentTarget 当前的事件源,它是会随着事件回调的执行不断变化的
164 | event.currentTarget = currentTarget;
165 | listener(event);
166 | }
167 | function processDispatchQueueItemsInOrder(
168 | event,
169 | dispatchListeners,
170 | inCapturePhase
171 | ) {
172 | if (inCapturePhase) {
173 | //dispatchListeners[子,父]
174 | for (let i = dispatchListeners.length - 1; i >= 0; i--) {
175 | const { listener, currentTarget } = dispatchListeners[i];
176 | if (event.isPropagationStopped()) {
177 | return;
178 | }
179 | executeDispatch(event, listener, currentTarget);
180 | }
181 | } else {
182 | for (let i = 0; i < dispatchListeners.length; i++) {
183 | const { listener, currentTarget } = dispatchListeners[i];
184 | if (event.isPropagationStopped()) {
185 | return;
186 | }
187 | executeDispatch(event, listener, currentTarget);
188 | }
189 | }
190 | }
191 | function extractEvents(
192 | dispatchQueue,
193 | domEventName,
194 | targetInst,
195 | nativeEvent,
196 | nativeEventTarget,
197 | eventSystemFlags,
198 | targetContainer
199 | ) {
200 | SimpleEventPlugin.extractEvents(
201 | dispatchQueue,
202 | domEventName,
203 | targetInst,
204 | nativeEvent,
205 | nativeEventTarget,
206 | eventSystemFlags,
207 | targetContainer
208 | );
209 | }
210 |
211 | export function accumulateSinglePhaseListeners(
212 | targetFiber,
213 | reactName,
214 | nativeEventType,
215 | isCapturePhase
216 | ) {
217 | const captureName = reactName + "Capture";
218 | const reactEventName = isCapturePhase ? captureName : reactName;
219 | const listeners = [];
220 | let instance = targetFiber;
221 | while (instance !== null) {
222 | const { stateNode, tag } = instance; //stateNode 当前的执行回调的DOM节点
223 | if (tag === HostComponent && stateNode !== null) {
224 | const listener = getListener(instance, reactEventName);
225 | if (listener) {
226 | listeners.push(createDispatchListener(instance, listener, stateNode));
227 | }
228 | }
229 | instance = instance.return;
230 | }
231 | return listeners;
232 | }
233 | function createDispatchListener(instance, listener, currentTarget) {
234 | return { instance, listener, currentTarget };
235 | }
236 |
--------------------------------------------------------------------------------
/src/react-reconciler/src/ReactFiberCommitWork.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React Fiber Commit Work - Commit Phase Implementation
3 | *
4 | * This module implements the commit phase of React's reconciliation process.
5 | * The commit phase is synchronous and applies all the side effects that were
6 | * collected during the render phase to the actual DOM.
7 | *
8 | * The commit phase has three sub-phases:
9 | * 1. Before Mutation: getSnapshotBeforeUpdate, async scheduling
10 | * 2. Mutation: DOM insertions, updates, deletions
11 | * 3. Layout: componentDidMount, componentDidUpdate, useLayoutEffect
12 | *
13 | * Key responsibilities:
14 | * - Apply DOM mutations (insert, update, delete nodes)
15 | * - Execute lifecycle methods and effects
16 | * - Handle refs attachment/detachment
17 | * - Manage focus and selection
18 | *
19 | * @module ReactFiberCommitWork
20 | */
21 |
22 | import {
23 | appendChild,
24 | insertBefore,
25 | commitUpdate,
26 | removeChild,
27 | } from "react-dom-bindings/src/client/ReactDOMHostConfig";
28 | import {
29 | Placement,
30 | MutationMask,
31 | Update,
32 | Passive,
33 | LayoutMask,
34 | Ref,
35 | } from "./ReactFiberFlags";
36 | import {
37 | FunctionComponent,
38 | HostComponent,
39 | HostRoot,
40 | HostText,
41 | } from "./ReactWorkTags";
42 | import {
43 | HasEffect as HookHasEffect,
44 | Passive as HookPassive,
45 | Layout as HookLayout,
46 | } from "./ReactHookEffectTags";
47 |
48 | // Global state for tracking the current host parent during commit
49 | let hostParent = null;
50 | /**
51 | * 提交删除副作用
52 | * @param {*} root 根节点
53 | * @param {*} returnFiber 父fiber
54 | * @param {*} deletedFiber 删除的fiber
55 | */
56 | function commitDeletionEffects(root, returnFiber, deletedFiber) {
57 | let parent = returnFiber;
58 | //一直向上找,找到真实的DOM节点为此
59 | findParent: while (parent !== null) {
60 | switch (parent.tag) {
61 | case HostComponent: {
62 | hostParent = parent.stateNode;
63 | break findParent;
64 | }
65 | case HostRoot: {
66 | hostParent = parent.stateNode.containerInfo;
67 | break findParent;
68 | }
69 | }
70 | parent = parent.return;
71 | }
72 | commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
73 | hostParent = null;
74 | }
75 | function commitDeletionEffectsOnFiber(
76 | finishedRoot,
77 | nearestMountedAncestor,
78 | deletedFiber
79 | ) {
80 | switch (deletedFiber.tag) {
81 | case HostComponent:
82 | case HostText: {
83 | //当要删除一个节点的时候,要先删除它的子节点
84 | recursivelyTraverseDeletionEffects(
85 | finishedRoot,
86 | nearestMountedAncestor,
87 | deletedFiber
88 | );
89 | //再把自己删除
90 | if (hostParent !== null) {
91 | removeChild(hostParent, deletedFiber.stateNode);
92 | }
93 | break;
94 | }
95 | default:
96 | break;
97 | }
98 | }
99 | function recursivelyTraverseDeletionEffects(
100 | finishedRoot,
101 | nearestMountedAncestor,
102 | parent
103 | ) {
104 | let child = parent.child;
105 | while (child !== null) {
106 | commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
107 | child = child.sibling;
108 | }
109 | }
110 | /**
111 | * 递归遍历处理变更的作用
112 | * @param {*} root 根节点
113 | * @param {*} parentFiber 父fiber
114 | */
115 | function recursivelyTraverseMutationEffects(root, parentFiber) {
116 | //先把父fiber上该删除的节点都删除
117 | const deletions = parentFiber.deletions;
118 | if (deletions !== null) {
119 | for (let i = 0; i < deletions.length; i++) {
120 | const childToDelete = deletions[i];
121 | commitDeletionEffects(root, parentFiber, childToDelete);
122 | }
123 | }
124 | //再去处理剩下的子节点
125 | if (parentFiber.subtreeFlags & MutationMask) {
126 | let { child } = parentFiber;
127 | while (child !== null) {
128 | commitMutationEffectsOnFiber(child, root);
129 | child = child.sibling;
130 | }
131 | }
132 | }
133 | function commitReconciliationEffects(finishedWork) {
134 | const { flags } = finishedWork;
135 | //如果此fiber要执行插入操作的话
136 | if (flags & Placement) {
137 | //进行插入操作,也就是把此fiber对应的真实DOM节点添加到父真实DOM节点上
138 | commitPlacement(finishedWork);
139 | //把flags里的Placement删除
140 | finishedWork.flags & ~Placement;
141 | }
142 | }
143 | function isHostParent(fiber) {
144 | return fiber.tag === HostComponent || fiber.tag == HostRoot; //div#root
145 | }
146 | function getHostParentFiber(fiber) {
147 | let parent = fiber.return;
148 | while (parent !== null) {
149 | if (isHostParent(parent)) {
150 | return parent;
151 | }
152 | parent = parent.return;
153 | }
154 | parent;
155 | }
156 |
157 | /**
158 | * 把子节点对应的真实DOM插入到父节点DOM中
159 | * @param {*} node 将要插入的fiber节点
160 | * @param {*} parent 父真实DOM节点
161 | */
162 | function insertOrAppendPlacementNode(node, before, parent) {
163 | const { tag } = node;
164 | //判断此fiber对应的节点是不是真实DOM节点
165 | const isHost = tag === HostComponent || tag === HostText;
166 | //如果是的话直接插入
167 | if (isHost) {
168 | const { stateNode } = node;
169 | if (before) {
170 | insertBefore(parent, stateNode, before);
171 | } else {
172 | appendChild(parent, stateNode);
173 | }
174 | } else {
175 | //如果node不是真实的DOM节点,获取它的大儿子
176 | const { child } = node;
177 | if (child !== null) {
178 | //把大儿子添加到父亲DOM节点里面去
179 | insertOrAppendPlacementNode(child, before, parent);
180 | let { sibling } = child;
181 | while (sibling !== null) {
182 | insertOrAppendPlacementNode(sibling, before, parent);
183 | sibling = sibling.sibling;
184 | }
185 | }
186 | }
187 | }
188 | /**
189 | * 找到要插入的锚点
190 | * 找到可以插在它的前面的那个fiber对应的真实DOM
191 | * @param {*} fiber
192 | */
193 | function getHostSibling(fiber) {
194 | let node = fiber;
195 | siblings: while (true) {
196 | while (node.sibling === null) {
197 | if (node.return === null || isHostParent(node.return)) {
198 | return null;
199 | }
200 | node = node.return;
201 | }
202 | node = node.sibling;
203 | //如果弟弟不是原生节点也不是文本节点
204 | while (node.tag !== HostComponent && node.tag !== HostText) {
205 | //如果此节点是一个将要插入的新的节点,找它的弟弟
206 | if (node.flags & Placement) {
207 | continue siblings;
208 | } else {
209 | node = node.child;
210 | }
211 | }
212 | if (!(node.flags & Placement)) {
213 | return node.stateNode;
214 | }
215 | }
216 | }
217 | /**
218 | * 把此fiber的真实DOM插入到父DOM里
219 | * @param {*} finishedWork
220 | */
221 | function commitPlacement(finishedWork) {
222 | const parentFiber = getHostParentFiber(finishedWork);
223 | switch (parentFiber.tag) {
224 | case HostRoot: {
225 | const parent = parentFiber.stateNode.containerInfo;
226 | const before = getHostSibling(finishedWork); //获取最近的弟弟真实DOM节点
227 | insertOrAppendPlacementNode(finishedWork, before, parent);
228 | break;
229 | }
230 | case HostComponent: {
231 | const parent = parentFiber.stateNode;
232 | const before = getHostSibling(finishedWork);
233 | insertOrAppendPlacementNode(finishedWork, before, parent);
234 | break;
235 | }
236 | default:
237 | break;
238 | }
239 | }
240 | /**
241 | * 遍历fiber树,执行fiber上的副作用
242 | * @param {*} finishedWork fiber节点
243 | * @param {*} root 根节点
244 | */
245 | export function commitMutationEffectsOnFiber(finishedWork, root) {
246 | const current = finishedWork.alternate;
247 | const flags = finishedWork.flags;
248 | switch (finishedWork.tag) {
249 | case FunctionComponent: {
250 | //先遍历它们的子节点,处理它们的子节点上的副作用
251 | recursivelyTraverseMutationEffects(root, finishedWork);
252 | //再处理自己身上的副作用
253 | commitReconciliationEffects(finishedWork);
254 | if (flags & Update) {
255 | commitHookEffectListUnmount(HookHasEffect | HookLayout, finishedWork);
256 | }
257 | break;
258 | }
259 | case HostRoot:
260 | case HostText: {
261 | //先遍历它们的子节点,处理它们的子节点上的副作用
262 | recursivelyTraverseMutationEffects(root, finishedWork);
263 | //再处理自己身上的副作用
264 | commitReconciliationEffects(finishedWork);
265 | break;
266 | }
267 | case HostComponent: {
268 | //先遍历它们的子节点,处理它们的子节点上的副作用
269 | recursivelyTraverseMutationEffects(root, finishedWork);
270 | //再处理自己身上的副作用
271 | commitReconciliationEffects(finishedWork);
272 | if (flags & Ref) {
273 | commitAttachRef(finishedWork);
274 | }
275 | //处理DOM更新
276 | if (flags & Update) {
277 | //获取真实DOM
278 | const instance = finishedWork.stateNode;
279 | //更新真实DOM
280 | if (instance !== null) {
281 | const newProps = finishedWork.memoizedProps;
282 | const oldProps = current !== null ? current.memoizedProps : newProps;
283 | const type = finishedWork.type;
284 | const updatePayload = finishedWork.updateQueue;
285 | finishedWork.updateQueue = null;
286 | if (updatePayload) {
287 | commitUpdate(
288 | instance,
289 | updatePayload,
290 | type,
291 | oldProps,
292 | newProps,
293 | finishedWork
294 | );
295 | }
296 | }
297 | }
298 | break;
299 | }
300 | default:
301 | break;
302 | }
303 | }
304 | function commitAttachRef(finishedWork) {
305 | const ref = finishedWork.ref;
306 | if (ref !== null) {
307 | const instance = finishedWork.stateNode;
308 | if (typeof ref === "function") {
309 | ref(instance);
310 | } else {
311 | ref.current = instance;
312 | }
313 | }
314 | }
315 |
316 | export function commitPassiveUnmountEffects(finishedWork) {
317 | commitPassiveUnmountOnFiber(finishedWork);
318 | }
319 | function commitPassiveUnmountOnFiber(finishedWork) {
320 | const flags = finishedWork.flags;
321 | switch (finishedWork.tag) {
322 | case HostRoot: {
323 | recursivelyTraversePassiveUnmountEffects(finishedWork);
324 | break;
325 | }
326 | case FunctionComponent: {
327 | recursivelyTraversePassiveUnmountEffects(finishedWork);
328 | if (flags & Passive) {
329 | //1024
330 | commitHookPassiveUnmountEffects(
331 | finishedWork,
332 | HookHasEffect | HookPassive
333 | );
334 | }
335 | break;
336 | }
337 | }
338 | }
339 | function recursivelyTraversePassiveUnmountEffects(parentFiber) {
340 | if (parentFiber.subtreeFlags & Passive) {
341 | let child = parentFiber.child;
342 | while (child !== null) {
343 | commitPassiveUnmountOnFiber(child);
344 | child = child.sibling;
345 | }
346 | }
347 | }
348 | function commitHookPassiveUnmountEffects(finishedWork, hookFlags) {
349 | commitHookEffectListUnmount(hookFlags, finishedWork);
350 | }
351 | function commitHookEffectListUnmount(flags, finishedWork) {
352 | const updateQueue = finishedWork.updateQueue;
353 | const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
354 | if (lastEffect !== null) {
355 | //获取 第一个effect
356 | const firstEffect = lastEffect.next;
357 | let effect = firstEffect;
358 | do {
359 | //如果此 effect类型和传入的相同,都是 9 HookHasEffect | PassiveEffect
360 | if ((effect.tag & flags) === flags) {
361 | const destroy = effect.destroy;
362 | if (destroy !== undefined) {
363 | destroy();
364 | }
365 | }
366 | effect = effect.next;
367 | } while (effect !== firstEffect);
368 | }
369 | }
370 | export function commitPassiveMountEffects(root, finishedWork) {
371 | commitPassiveMountOnFiber(root, finishedWork);
372 | }
373 | function commitPassiveMountOnFiber(finishedRoot, finishedWork) {
374 | const flags = finishedWork.flags;
375 | switch (finishedWork.tag) {
376 | case HostRoot: {
377 | recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork);
378 | break;
379 | }
380 | case FunctionComponent: {
381 | recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork);
382 | if (flags & Passive) {
383 | //1024
384 | commitHookPassiveMountEffects(
385 | finishedWork,
386 | HookHasEffect | HookPassive
387 | );
388 | }
389 | break;
390 | }
391 | }
392 | }
393 | function recursivelyTraversePassiveMountEffects(root, parentFiber) {
394 | if (parentFiber.subtreeFlags & Passive) {
395 | let child = parentFiber.child;
396 | while (child !== null) {
397 | commitPassiveMountOnFiber(root, child);
398 | child = child.sibling;
399 | }
400 | }
401 | }
402 | function commitHookPassiveMountEffects(finishedWork, hookFlags) {
403 | commitHookEffectListMount(hookFlags, finishedWork);
404 | }
405 | function commitHookEffectListMount(flags, finishedWork) {
406 | const updateQueue = finishedWork.updateQueue;
407 | const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
408 | if (lastEffect !== null) {
409 | //获取 第一个effect
410 | const firstEffect = lastEffect.next;
411 | let effect = firstEffect;
412 | do {
413 | //如果此 effect类型和传入的相同,都是 9 HookHasEffect | PassiveEffect
414 | if ((effect.tag & flags) === flags) {
415 | const create = effect.create;
416 | effect.destroy = create();
417 | }
418 | effect = effect.next;
419 | } while (effect !== firstEffect);
420 | }
421 | }
422 |
423 | export function commitLayoutEffects(finishedWork, root) {
424 | //老的根fiber
425 | const current = finishedWork.alternate;
426 | commitLayoutEffectOnFiber(root, current, finishedWork);
427 | }
428 | function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork) {
429 | const flags = finishedWork.flags;
430 | switch (finishedWork.tag) {
431 | case HostRoot: {
432 | recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
433 | break;
434 | }
435 | case FunctionComponent: {
436 | recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
437 | if (flags & LayoutMask) {
438 | // LayoutMask=Update=4
439 | commitHookLayoutEffects(finishedWork, HookHasEffect | HookLayout);
440 | }
441 | break;
442 | }
443 | }
444 | }
445 | function commitHookLayoutEffects(finishedWork, hookFlags) {
446 | commitHookEffectListMount(hookFlags, finishedWork);
447 | }
448 | function recursivelyTraverseLayoutEffects(root, parentFiber) {
449 | if (parentFiber.subtreeFlags & LayoutMask) {
450 | let child = parentFiber.child;
451 | while (child !== null) {
452 | const current = child.alternate;
453 | commitLayoutEffectOnFiber(root, current, child);
454 | child = child.sibling;
455 | }
456 | }
457 | }
458 |
--------------------------------------------------------------------------------
/src/react-reconciler/src/ReactChildFiber.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React Child Fiber - Reconciliation Algorithm Implementation
3 | *
4 | * This module implements React's reconciliation algorithm (also known as the "diffing" algorithm).
5 | * It's responsible for comparing the current fiber tree with the new virtual DOM tree and
6 | * determining what changes need to be made to efficiently update the DOM.
7 | *
8 | * Key concepts:
9 | * - Reconciliation: Process of comparing old and new trees
10 | * - Side effects: Tracking what DOM operations need to be performed
11 | * - Keys: Used for efficient list reconciliation
12 | * - Placement/Deletion flags: Mark fibers for DOM operations
13 | *
14 | * The reconciler handles:
15 | * - Single element reconciliation
16 | * - Array/list reconciliation with keys
17 | * - Text node reconciliation
18 | * - Deletion of removed elements
19 | *
20 | * @module ReactChildFiber
21 | */
22 |
23 | import { REACT_ELEMENT_TYPE } from "shared/ReactSymbols";
24 | import {
25 | createFiberFromElement,
26 | createFiberFromText,
27 | createWorkInProgress,
28 | } from "./ReactFiber";
29 | import { Placement, ChildDeletion } from "./ReactFiberFlags";
30 | import isArray from "shared/isArray";
31 | import { HostText } from "./ReactWorkTags";
32 |
33 | /**
34 | * Create Child Reconciler
35 | *
36 | * Factory function that creates a child reconciler with configurable side effect tracking.
37 | * During mount phase, side effects aren't tracked since everything is new.
38 | * During update phase, side effects are tracked to determine what DOM operations are needed.
39 | *
40 | * @param {boolean} shouldTrackSideEffects - Whether to track side effects (placement, deletion)
41 | * @returns {Function} Reconciler function for processing children
42 | */
43 | function createChildReconciler(shouldTrackSideEffects) {
44 | /**
45 | * Use Fiber
46 | *
47 | * Reuses an existing fiber by creating a work-in-progress copy with new props.
48 | * This is an optimization to avoid creating new fiber objects when possible.
49 | *
50 | * @param {Fiber} fiber - Existing fiber to reuse
51 | * @param {any} pendingProps - New props for the fiber
52 | * @returns {Fiber} Work-in-progress fiber
53 | */
54 | function useFiber(fiber, pendingProps) {
55 | const clone = createWorkInProgress(fiber, pendingProps);
56 | clone.index = 0;
57 | clone.sibling = null;
58 | return clone;
59 | }
60 |
61 | /**
62 | * Delete Child
63 | *
64 | * Marks a child fiber for deletion during the commit phase.
65 | * Adds the fiber to the parent's deletions array and sets the ChildDeletion flag.
66 | *
67 | * @param {Fiber} returnFiber - Parent fiber
68 | * @param {Fiber} childToDelete - Child fiber to mark for deletion
69 | */
70 | function deleteChild(returnFiber, childToDelete) {
71 | if (!shouldTrackSideEffects) return;
72 |
73 | const deletions = returnFiber.deletions;
74 | if (deletions === null) {
75 | returnFiber.deletions = [childToDelete];
76 | returnFiber.flags |= ChildDeletion;
77 | } else {
78 | returnFiber.deletions.push(childToDelete);
79 | }
80 | }
81 |
82 | /**
83 | * Delete Remaining Children
84 | *
85 | * Marks all remaining children starting from currentFirstChild for deletion.
86 | * This is used when the new children list is shorter than the old one.
87 | *
88 | * @param {Fiber} returnFiber - Parent fiber
89 | * @param {Fiber} currentFirstChild - First child to start deleting from
90 | * @returns {null} Always returns null
91 | */
92 | function deleteRemainingChildren(returnFiber, currentFirstChild) {
93 | if (!shouldTrackSideEffects) return null;
94 |
95 | let childToDelete = currentFirstChild;
96 | while (childToDelete !== null) {
97 | deleteChild(returnFiber, childToDelete);
98 | childToDelete = childToDelete.sibling;
99 | }
100 | return null;
101 | }
102 | /**
103 | * Reconcile Single Element
104 | *
105 | * Reconciles a single React element with the existing fiber tree. This function
106 | * implements the core diffing logic for single elements, comparing keys and types
107 | * to determine if a fiber can be reused or if a new one needs to be created.
108 | *
109 | * Algorithm:
110 | * 1. Compare keys - if different, delete old fiber and create new one
111 | * 2. Compare types - if same key but different type, delete old and create new
112 | * 3. If key and type match, reuse existing fiber with new props
113 | *
114 | * @param {Fiber} returnFiber - Parent fiber (e.g., div#root fiber)
115 | * @param {Fiber} currentFirstChild - Existing first child fiber
116 | * @param {ReactElement} element - New virtual DOM element to reconcile
117 | * @returns {Fiber} New or reused first child fiber
118 | */
119 | function reconcileSingleElement(returnFiber, currentFirstChild, element) {
120 | // Get the key from new virtual DOM element (used for reconciliation)
121 | const key = element.key;
122 | let child = currentFirstChild;
123 | while (child !== null) {
124 | //判断此老fiber对应的key和新的虚拟DOM对象的key是否一样 null===null
125 | if (child.key === key) {
126 | //判断老fiber对应的类型和新虚拟DOM元素对应的类型是否相同
127 | if (child.type === element.type) {
128 | // p div
129 | deleteRemainingChildren(returnFiber, child.sibling);
130 | //如果key一样,类型也一样,则认为此节点可以复用
131 | const existing = useFiber(child, element.props);
132 | existing.ref = element.ref;
133 | existing.return = returnFiber;
134 | return existing;
135 | } else {
136 | //如果找到一key一样老fiber,但是类型不一样,不能此老fiber,把剩下的全部删除
137 | deleteRemainingChildren(returnFiber, child);
138 | }
139 | } else {
140 | deleteChild(returnFiber, child);
141 | }
142 | child = child.sibling;
143 | }
144 |
145 | //因为我们现实的初次挂载,老节点currentFirstChild肯定是没有的,所以可以直接根据虚拟DOM创建新的Fiber节点
146 | const created = createFiberFromElement(element);
147 | created.ref = element.ref;
148 | created.return = returnFiber;
149 | return created;
150 | }
151 | /**
152 | * 设置副作用
153 | * @param {*} newFiber
154 | * @returns
155 | */
156 | function placeSingleChild(newFiber) {
157 | //说明要添加副作用
158 | if (shouldTrackSideEffects && newFiber.alternate === null) {
159 | //要在最后的提交阶段插入此节点 React渲染分成渲染(创建Fiber树)和提交(更新真实DOM)二个阶段
160 | newFiber.flags |= Placement;
161 | }
162 | return newFiber;
163 | }
164 | function createChild(returnFiber, newChild) {
165 | if (
166 | (typeof newChild === "string" && newChild !== "") ||
167 | typeof newChild === "number"
168 | ) {
169 | const created = createFiberFromText(`${newChild}`);
170 | created.return = returnFiber;
171 | return created;
172 | }
173 | if (typeof newChild === "object" && newChild !== null) {
174 | switch (newChild.$$typeof) {
175 | case REACT_ELEMENT_TYPE: {
176 | const created = createFiberFromElement(newChild);
177 | created.ref = newChild.ref;
178 | created.return = returnFiber;
179 | return created;
180 | }
181 | default:
182 | break;
183 | }
184 | }
185 | return null;
186 | }
187 | function placeChild(newFiber, lastPlacedIndex, newIdx) {
188 | //指定新的fiber在新的挂载索引
189 | newFiber.index = newIdx;
190 | //如果不需要跟踪副作用
191 | if (!shouldTrackSideEffects) {
192 | return lastPlacedIndex;
193 | }
194 | //获取它的老fiber
195 | const current = newFiber.alternate;
196 | //如果有,说明这是一个更新的节点,有老的真实DOM。
197 | if (current !== null) {
198 | const oldIndex = current.index;
199 | //如果找到的老fiber的索引比lastPlacedIndex要小,则老fiber对应的DOM节点需要移动
200 | if (oldIndex < lastPlacedIndex) {
201 | newFiber.flags |= Placement;
202 | return lastPlacedIndex;
203 | } else {
204 | return oldIndex;
205 | }
206 | } else {
207 | //如果没有,说明这是一个新的节点,需要插入
208 | newFiber.flags |= Placement;
209 | return lastPlacedIndex;
210 | }
211 | }
212 | function updateElement(returnFiber, current, element) {
213 | const elementType = element.type;
214 | if (current !== null) {
215 | //判断是否类型一样,则表示key和type都一样,可以复用老的fiber和真实DOM
216 | if (current.type === elementType) {
217 | const existing = useFiber(current, element.props);
218 | existing.ref = element.ref;
219 | existing.return = returnFiber;
220 | return existing;
221 | }
222 | }
223 | const created = createFiberFromElement(element);
224 | created.ref = element.ref;
225 | created.return = returnFiber;
226 | return created;
227 | }
228 | function updateSlot(returnFiber, oldFiber, newChild) {
229 | const key = oldFiber !== null ? oldFiber.key : null;
230 | if (newChild !== null && typeof newChild === "object") {
231 | switch (newChild.$$typeof) {
232 | case REACT_ELEMENT_TYPE: {
233 | //如果key一样,进入更新元素的逻辑
234 | if (newChild.key === key) {
235 | return updateElement(returnFiber, oldFiber, newChild);
236 | }
237 | }
238 | default:
239 | return null;
240 | }
241 | }
242 | return null;
243 | }
244 | function mapRemainingChildren(returnFiber, currentFirstChild) {
245 | const existingChildren = new Map();
246 | let existingChild = currentFirstChild;
247 | while (existingChild != null) {
248 | //如果有key用key,如果没有key使用索引
249 | if (existingChild.key !== null) {
250 | existingChildren.set(existingChild.key, existingChild);
251 | } else {
252 | existingChildren.set(existingChild.index, existingChild);
253 | }
254 | existingChild = existingChild.sibling;
255 | }
256 | return existingChildren;
257 | }
258 | function updateTextNode(returnFiber, current, textContent) {
259 | if (current === null || current.tag !== HostText) {
260 | const created = createFiberFromText(textContent);
261 | created.return = returnFiber;
262 | return created;
263 | } else {
264 | const existing = useFiber(current, textContent);
265 | existing.return = returnFiber;
266 | return existing;
267 | }
268 | }
269 | function updateFromMap(existingChildren, returnFiber, newIdx, newChild) {
270 | if (
271 | (typeof newChild === "string" && newChild !== "") ||
272 | typeof newChild === "number"
273 | ) {
274 | const matchedFiber = existingChildren.get(newIdx) || null;
275 | return updateTextNode(returnFiber, matchedFiber, "" + newChild);
276 | }
277 | if (typeof newChild === "object" && newChild !== null) {
278 | switch (newChild.$$typeof) {
279 | case REACT_ELEMENT_TYPE: {
280 | const matchedFiber =
281 | existingChildren.get(
282 | newChild.key === null ? newIdx : newChild.key
283 | ) || null;
284 | return updateElement(returnFiber, matchedFiber, newChild);
285 | }
286 | }
287 | }
288 | }
289 | function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {
290 | let resultingFirstChild = null; //返回的第一个新儿子
291 | let previousNewFiber = null; //上一个的一个新的儿fiber
292 | let newIdx = 0; //用来遍历新的虚拟DOM的索引
293 | let oldFiber = currentFirstChild; //第一个老fiber
294 | let nextOldFiber = null; //下一个第fiber
295 | let lastPlacedIndex = 0; //上一个不需要移动的老节点的索引
296 | // 开始第一轮循环 如果老fiber有值,新的虚拟DOM也有值
297 | for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
298 | //先暂下一个老fiber
299 | nextOldFiber = oldFiber.sibling;
300 | //试图更新或者试图复用老的fiber
301 | const newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx]);
302 | if (newFiber === null) {
303 | break;
304 | }
305 | if (shouldTrackSideEffects) {
306 | //如果有老fiber,但是新的fiber并没有成功复用老fiber和老的真实DOM,那就删除老fiber,在提交阶段会删除真实DOM
307 | if (oldFiber && newFiber.alternate === null) {
308 | deleteChild(returnFiber, oldFiber);
309 | }
310 | }
311 | //指定新fiber的位置
312 | lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
313 | if (previousNewFiber === null) {
314 | resultingFirstChild = newFiber; //li(A).sibling=p(B).sibling=>li(C)
315 | } else {
316 | previousNewFiber.sibling = newFiber;
317 | }
318 | previousNewFiber = newFiber;
319 | oldFiber = nextOldFiber;
320 | }
321 | //新的虚拟DOM已经循环完毕,3=>2
322 | if (newIdx === newChildren.length) {
323 | //删除剩下的老fiber
324 | deleteRemainingChildren(returnFiber, oldFiber);
325 | return resultingFirstChild;
326 | }
327 | if (oldFiber === null) {
328 | //如果老的 fiber已经没有了, 新的虚拟DOM还有,进入插入新节点的逻辑
329 | for (; newIdx < newChildren.length; newIdx++) {
330 | const newFiber = createChild(returnFiber, newChildren[newIdx]);
331 | if (newFiber === null) continue;
332 | lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
333 | //如果previousNewFiber为null,说明这是第一个fiber
334 | if (previousNewFiber === null) {
335 | resultingFirstChild = newFiber; //这个newFiber就是大儿子
336 | } else {
337 | //否则说明不是大儿子,就把这个newFiber添加上一个子节点后面
338 | previousNewFiber.sibling = newFiber;
339 | }
340 | //让newFiber成为最后一个或者说上一个子fiber
341 | previousNewFiber = newFiber;
342 | }
343 | }
344 | // 开始处理移动的情况
345 | const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
346 | //开始遍历剩下的虚拟DOM子节点
347 | for (; newIdx < newChildren.length; newIdx++) {
348 | const newFiber = updateFromMap(
349 | existingChildren,
350 | returnFiber,
351 | newIdx,
352 | newChildren[newIdx]
353 | );
354 | if (newFiber !== null) {
355 | if (shouldTrackSideEffects) {
356 | //如果要跟踪副作用,并且有老fiber
357 | if (newFiber.alternate !== null) {
358 | existingChildren.delete(
359 | newFiber.key === null ? newIdx : newFiber.key
360 | );
361 | }
362 | }
363 | //指定新的fiber存放位置 ,并且给lastPlacedIndex赋值
364 | lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
365 | if (previousNewFiber === null) {
366 | resultingFirstChild = newFiber; //这个newFiber就是大儿子
367 | } else {
368 | //否则说明不是大儿子,就把这个newFiber添加上一个子节点后面
369 | previousNewFiber.sibling = newFiber;
370 | }
371 | //让newFiber成为最后一个或者说上一个子fiber
372 | previousNewFiber = newFiber;
373 | }
374 | }
375 | if (shouldTrackSideEffects) {
376 | //等全部处理完后,删除map中所有剩下的老fiber
377 | existingChildren.forEach((child) => deleteChild(returnFiber, child));
378 | }
379 | return resultingFirstChild;
380 | }
381 | /**
382 | * 比较子Fibers DOM-DIFF 就是用老的子fiber链表和新的虚拟DOM进行比较的过程
383 | * @param {*} returnFiber 新的父Fiber
384 | * @param {*} currentFirstChild 老fiber第一个子fiber current一般来说指的是老
385 | * @param {*} newChild 新的子虚拟DOM h1虚拟DOM
386 | */
387 | function reconcileChildFibers(returnFiber, currentFirstChild, newChild) {
388 | //现在需要处理更新的逻辑了,处理dom diff
389 | //现在暂时只考虑新的节点只有一个的情况
390 | if (typeof newChild === "object" && newChild !== null) {
391 | switch (newChild.$$typeof) {
392 | case REACT_ELEMENT_TYPE:
393 | return placeSingleChild(
394 | reconcileSingleElement(returnFiber, currentFirstChild, newChild)
395 | );
396 | default:
397 | break;
398 | }
399 | }
400 | //newChild [hello文本节点,span虚拟DOM元素]
401 | if (isArray(newChild)) {
402 | return reconcileChildrenArray(returnFiber, currentFirstChild, newChild);
403 | }
404 | return null;
405 | }
406 | return reconcileChildFibers;
407 | }
408 | //有老父fiber更新的时候用这个
409 | export const reconcileChildFibers = createChildReconciler(true);
410 | //如果没有老父fiber,初次挂载的时候用这个
411 | export const mountChildFibers = createChildReconciler(false);
412 |
--------------------------------------------------------------------------------
/src/react-reconciler/src/ReactFiberWorkLoop.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React Fiber Work Loop - Core Rendering Engine
3 | *
4 | * This module contains the main work loop that drives React's rendering process.
5 | * It implements the concurrent rendering algorithm with time slicing, priority
6 | * scheduling, and interruptible rendering.
7 | *
8 | * The work loop is responsible for:
9 | * - Scheduling and executing render work
10 | * - Managing work-in-progress fiber tree
11 | * - Handling interruptions and yielding
12 | * - Coordinating commit phase operations
13 | *
14 | * @module ReactFiberWorkLoop
15 | */
16 |
17 | import {
18 | scheduleCallback as Scheduler_scheduleCallback,
19 | shouldYield,
20 | ImmediatePriority as ImmediateSchedulerPriority,
21 | UserBlockingPriority as UserBlockingSchedulerPriority,
22 | NormalPriority as NormalSchedulerPriority,
23 | IdlePriority as IdleSchedulerPriority,
24 | cancelCallback as Scheduler_cancelCallback,
25 | now,
26 | } from "./scheduler";
27 | import { createWorkInProgress } from "./ReactFiber";
28 | import { beginWork } from "./ReactFiberBeginWork";
29 | import { completeWork } from "./ReactFiberCompleteWork";
30 | import { NoFlags, MutationMask, Passive } from "./ReactFiberFlags";
31 | import {
32 | commitMutationEffectsOnFiber, // Execute DOM operations
33 | commitPassiveUnmountEffects, // Execute cleanup functions
34 | commitPassiveMountEffects, // Execute effect functions
35 | commitLayoutEffects,
36 | } from "./ReactFiberCommitWork";
37 | import { finishQueueingConcurrentUpdates } from "./ReactFiberConcurrentUpdates";
38 | import {
39 | NoLanes,
40 | markRootUpdated,
41 | getNextLanes,
42 | getHighestPriorityLane,
43 | SyncLane,
44 | includesBlockingLane,
45 | NoLane,
46 | markStarvedLanesAsExpired,
47 | includesExpiredLane,
48 | markRootFinished,
49 | NoTimestamp,
50 | mergeLanes,
51 | } from "./ReactFiberLane";
52 | import {
53 | getCurrentUpdatePriority,
54 | lanesToEventPriority,
55 | DiscreteEventPriority,
56 | ContinuousEventPriority,
57 | DefaultEventPriority,
58 | IdleEventPriority,
59 | setCurrentUpdatePriority,
60 | } from "./ReactEventPriorities";
61 | import { getCurrentEventPriority } from "react-dom-bindings/src/client/ReactDOMHostConfig";
62 | import {
63 | scheduleSyncCallback,
64 | flushSyncCallbacks,
65 | } from "./ReactFiberSyncTaskQueue";
66 |
67 | // Global work loop state
68 | let workInProgress = null; // Current fiber being worked on
69 | let workInProgressRoot = null; // Root fiber currently being built
70 | let rootDoesHavePassiveEffect = false; // Whether root has passive effects (useEffect)
71 | let rootWithPendingPassiveEffects = null; // Root with pending passive effects
72 | let workInProgressRootRenderLanes = NoLanes; // Lanes being rendered
73 |
74 | // Work loop exit status constants
75 | const RootInProgress = 0; // Fiber tree construction in progress
76 | const RootCompleted = 5; // Fiber tree construction completed
77 |
78 | // Current render state
79 | let workInProgressRootExitStatus = RootInProgress; // Current fiber tree status
80 | let currentEventTime = NoTimestamp; // Current event timestamp
81 |
82 | /**
83 | * Schedule Update on Fiber
84 | *
85 | * Schedules an update on a fiber root. This is the entry point for all updates
86 | * in React, whether they come from setState, props changes, or other sources.
87 | *
88 | * @param {FiberRoot} root - The fiber root to update
89 | * @param {Fiber} fiber - The fiber that triggered the update
90 | * @param {Lane} lane - Priority lane for this update
91 | * @param {number} eventTime - Time when the event occurred
92 | */
93 | export function scheduleUpdateOnFiber(root, fiber, lane, eventTime) {
94 | // Mark the root as having updates in this lane
95 | markRootUpdated(root, lane);
96 |
97 | // Ensure the root is scheduled for updates
98 | ensureRootIsScheduled(root, eventTime);
99 | }
100 |
101 | /**
102 | * Ensure Root is Scheduled
103 | *
104 | * Ensures that a root is scheduled for updates. This function implements
105 | * React's scheduling logic, including priority management and batching.
106 | *
107 | * @param {FiberRoot} root - The fiber root to schedule
108 | * @param {number} currentTime - Current time for scheduling calculations
109 | */
110 | function ensureRootIsScheduled(root, currentTime) {
111 | // Get existing callback node if any
112 | const existingCallbackNode = root.callbackNode;
113 |
114 | // Mark starved lanes as expired to prevent starvation
115 | markStarvedLanesAsExpired(root, currentTime);
116 |
117 | // Get the next lanes to work on (highest priority)
118 | const nextLanes = getNextLanes(root, workInProgressRootRenderLanes);
119 |
120 | // If no work to do, exit early
121 | if (nextLanes === NoLanes) {
122 | return;
123 | }
124 |
125 | // Get the priority of the new work
126 | let newCallbackPriority = getHighestPriorityLane(nextLanes);
127 |
128 | // Get the priority of existing work
129 | const existingCallbackPriority = root.callbackPriority;
130 |
131 | // If priorities match, we can batch the updates
132 | if (existingCallbackPriority === newCallbackPriority) {
133 | return;
134 | }
135 |
136 | // Cancel existing callback if priorities differ
137 | if (existingCallbackNode !== null) {
138 | console.log("cancelCallback");
139 | Scheduler_cancelCallback(existingCallbackNode);
140 | }
141 |
142 | // Schedule new callback
143 | let newCallbackNode = null;
144 | //如果新的优先级是同步的话
145 | if (newCallbackPriority === SyncLane) {
146 | //先把performSyncWorkOnRoot添回到同步队列中
147 | scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
148 | //再把flushSyncCallbacks放入微任务
149 | queueMicrotask(flushSyncCallbacks);
150 | //如果是同步执行的话
151 | newCallbackNode = null;
152 | } else {
153 | //如果不是同步,就需要调度一个新的任务
154 | let schedulerPriorityLevel;
155 | switch (lanesToEventPriority(nextLanes)) {
156 | case DiscreteEventPriority:
157 | schedulerPriorityLevel = ImmediateSchedulerPriority;
158 | break;
159 | case ContinuousEventPriority:
160 | schedulerPriorityLevel = UserBlockingSchedulerPriority;
161 | break;
162 | case DefaultEventPriority:
163 | schedulerPriorityLevel = NormalSchedulerPriority;
164 | break;
165 | case IdleEventPriority:
166 | schedulerPriorityLevel = IdleSchedulerPriority;
167 | break;
168 | default:
169 | schedulerPriorityLevel = NormalSchedulerPriority;
170 | break;
171 | }
172 | newCallbackNode = Scheduler_scheduleCallback(
173 | schedulerPriorityLevel,
174 | performConcurrentWorkOnRoot.bind(null, root)
175 | );
176 | }
177 | //the task executed on the root is newCallbackNode
178 | root.callbackNode = newCallbackNode;
179 | root.callbackPriority = newCallbackPriority;
180 | /* if (workInProgressRoot) return;
181 | workInProgressRoot = root;
182 | //告诉 浏览器要执行performConcurrentWorkOnRoot 在此触发更新
183 | scheduleCallback(NormalSchedulerPriority, performConcurrentWorkOnRoot.bind(null, root)); */
184 | }
185 | /**
186 | * perform synchronous work on the root
187 | */
188 | function performSyncWorkOnRoot(root) {
189 | //get the highest priority lane
190 | const lanes = getNextLanes(root);
191 | //render the new fiber tree
192 | renderRootSync(root, lanes);
193 | //get the new rendered fiber root node
194 | const finishedWork = root.current.alternate;
195 | root.finishedWork = finishedWork;
196 | commitRoot(root);
197 | return null;
198 | }
199 | /**
200 | * build the fiber tree according to the fiber, to create the real DOM node, you also need to insert the real DOM node into the container
201 | * @param {*} root
202 | */
203 | function performConcurrentWorkOnRoot(root, didTimeout) {
204 | //get the task on the current root node
205 | const originalCallbackNode = root.callbackNode;
206 | //get the highest priority lane
207 | const lanes = getNextLanes(root, NoLanes); //16
208 | if (lanes === NoLanes) {
209 | return null;
210 | }
211 | //if it does not contain the blocking lane, and it is not timed out, it can be rendered in parallel, that is, the time slice is enabled
212 | //所以说默认更新车道是同步的,不能启用时间分片
213 | //whether it does not contain the blocking lane
214 | const nonIncludesBlockingLane = !includesBlockingLane(root, lanes);
215 | //whether it does not contain the expired lane
216 | const nonIncludesExpiredLane = !includesExpiredLane(root, lanes);
217 | //the time slice is not expired
218 | const nonTimeout = !didTimeout;
219 | //three variables are true, the time slice can be enabled, that is, the concurrent rendering can be performed, that is, the execution can be interrupted
220 | const shouldTimeSlice =
221 | nonIncludesBlockingLane && nonIncludesExpiredLane && nonTimeout;
222 | // console.log('shouldTimeSlice', shouldTimeSlice);
223 | //execute rendering, get the exit status
224 | const exitStatus = shouldTimeSlice
225 | ? renderRootConcurrent(root, lanes)
226 | : renderRootSync(root, lanes);
227 | //if it is not rendering, then it means that the rendering must be finished
228 | if (exitStatus !== RootInProgress) {
229 | const finishedWork = root.current.alternate;
230 | root.finishedWork = finishedWork;
231 | commitRoot(root);
232 | }
233 | //it means that the task is not completed
234 | if (root.callbackNode === originalCallbackNode) {
235 | //return this function, next time to continue
236 | return performConcurrentWorkOnRoot.bind(null, root);
237 | }
238 | return null;
239 | }
240 | function renderRootConcurrent(root, lanes) {
241 | //because this method will be repeatedly entered during the construction of the fiber tree, it will be entered multiple times
242 | //only when the first time comes in will the new fiber tree be created, or the new fiber
243 | if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
244 | prepareFreshStack(root, lanes);
245 | }
246 | //execute the fiber tree construction or rendering within the current time slice(5ms)
247 | workLoopConcurrent();
248 | //if workInProgress is not null, it means that the fiber tree construction is not completed
249 | if (workInProgress !== null) {
250 | return RootInProgress;
251 | }
252 | //if workInProgress is null, it means that the rendering work is completely finished
253 | return workInProgressRootExitStatus;
254 | }
255 | function flushPassiveEffect() {
256 | if (rootWithPendingPassiveEffects !== null) {
257 | const root = rootWithPendingPassiveEffects;
258 | //execute the unmount effect, destroy
259 | commitPassiveUnmountEffects(root.current);
260 | //execute the mount effect, create
261 | commitPassiveMountEffects(root, root.current);
262 | }
263 | }
264 | function commitRoot(root) {
265 | const previousUpdatePriority = getCurrentUpdatePriority();
266 | try {
267 | //set the current update priority to 1
268 | setCurrentUpdatePriority(DiscreteEventPriority);
269 | commitRootImpl(root);
270 | } finally {
271 | setCurrentUpdatePriority(previousUpdatePriority);
272 | }
273 | }
274 | function commitRootImpl(root) {
275 | //get the new built fiber tree root fiber tag=3
276 | const { finishedWork } = root;
277 | workInProgressRoot = null;
278 | workInProgressRootRenderLanes = NoLanes;
279 | root.callbackNode = null;
280 | root.callbackPriority = NoLane;
281 | //merge the remaining lanes on the current new root
282 | const remainingLanes = mergeLanes(
283 | finishedWork.lanes,
284 | finishedWork.childLanes
285 | );
286 | markRootFinished(root, remainingLanes);
287 | if (
288 | (finishedWork.subtreeFlags & Passive) !== NoFlags ||
289 | (finishedWork.flags & Passive) !== NoFlags
290 | ) {
291 | if (!rootDoesHavePassiveEffect) {
292 | rootDoesHavePassiveEffect = true;
293 | Scheduler_scheduleCallback(NormalSchedulerPriority, flushPassiveEffect);
294 | }
295 | }
296 | //whether the subtree has effects
297 | const subtreeHasEffects =
298 | (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
299 | const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
300 | //if the effect or the subtree has effects, the DOM operation should be committed
301 | if (subtreeHasEffects || rootHasEffect) {
302 | //after the DOM is executed, the DOM operation should be committed
303 | commitMutationEffectsOnFiber(finishedWork, root);
304 | //execute the layout effect
305 | commitLayoutEffects(finishedWork, root);
306 | if (rootDoesHavePassiveEffect) {
307 | rootDoesHavePassiveEffect = false;
308 | rootWithPendingPassiveEffects = root;
309 | }
310 | }
311 | //after the DOM is changed, the root's current can be pointed to the new fiber tree
312 | root.current = finishedWork;
313 | //after the submission, because the root may have skipped updates, so the scheduling needs to be re-scheduled
314 | ensureRootIsScheduled(root, now());
315 | }
316 | function prepareFreshStack(root, renderLanes) {
317 | workInProgress = createWorkInProgress(root.current, null);
318 | workInProgressRootRenderLanes = renderLanes;
319 | workInProgressRoot = root;
320 | finishQueueingConcurrentUpdates();
321 | }
322 | function renderRootSync(root, renderLanes) {
323 | //if the new root is different from the old root, or the new render priority is different from the old render priority
324 | if (
325 | root !== workInProgressRoot ||
326 | workInProgressRootRenderLanes !== renderLanes
327 | ) {
328 | // create a substitute
329 | prepareFreshStack(root, renderLanes);
330 | }
331 | workLoopSync();
332 | return RootCompleted;
333 | }
334 | function workLoopConcurrent() {
335 | //if there is a next fiber to build and the time slice is not expired
336 | while (workInProgress !== null && !shouldYield()) {
337 | //console.log('shouldYield()', shouldYield(), workInProgress);
338 | sleep(5);
339 | performUnitOfWork(workInProgress);
340 | }
341 | }
342 | function workLoopSync() {
343 | while (workInProgress !== null) {
344 | performUnitOfWork(workInProgress);
345 | }
346 | }
347 | /**
348 | * execute a work unit
349 | * @param {*} unitOfWork
350 | */
351 | function performUnitOfWork(unitOfWork) {
352 | //get the old fiber corresponding to the new fiber
353 | const current = unitOfWork.alternate;
354 | //after the current fiber's child fiber chain list is built, complete the work
355 | const next = beginWork(current, unitOfWork, workInProgressRootRenderLanes);
356 | unitOfWork.memoizedProps = unitOfWork.pendingProps;
357 | if (next === null) {
358 | //if there is no child node, it means that the current fiber has been completed
359 | completeUnitOfWork(unitOfWork);
360 | } else {
361 | //if there is a child node, let the child node become the next work unit
362 | workInProgress = next;
363 | }
364 | }
365 |
366 | function completeUnitOfWork(unitOfWork) {
367 | let completedWork = unitOfWork;
368 | do {
369 | const current = completedWork.alternate;
370 | const returnFiber = completedWork.return;
371 | //execute the completion work of this fiber, if it is a native component, it is to create the real DOM node
372 | completeWork(current, completedWork);
373 | //if there is a sibling, build the fiber child chain list corresponding to the sibling
374 | const siblingFiber = completedWork.sibling;
375 | if (siblingFiber !== null) {
376 | workInProgress = siblingFiber;
377 | return;
378 | }
379 | //if there is no sibling, it means that the current completed is the last node of the parent fiber
380 | //也就是说一个父fiber,所有的子fiber全部完成了
381 | completedWork = returnFiber;
382 | workInProgress = completedWork;
383 | } while (completedWork !== null);
384 | //if you get here, it means that the entire fiber tree has been built, and the build status is set to empty
385 | if (workInProgressRootExitStatus === RootInProgress) {
386 | workInProgressRootExitStatus = RootCompleted;
387 | }
388 | }
389 |
390 | export function requestUpdateLane() {
391 | const updateLane = getCurrentUpdatePriority();
392 | if (updateLane !== NoLanes) {
393 | return updateLane;
394 | }
395 | const eventLane = getCurrentEventPriority();
396 | return eventLane;
397 | }
398 | function sleep(duration) {
399 | const timeStamp = new Date().getTime();
400 | const endTime = timeStamp + duration;
401 | while (true) {
402 | if (new Date().getTime() > endTime) {
403 | return;
404 | }
405 | }
406 | }
407 | //request the current time
408 | export function requestEventTime() {
409 | currentEventTime = now();
410 | return currentEventTime; //performance.now()
411 | }
412 |
--------------------------------------------------------------------------------
/src/react-reconciler/src/ReactFiberHooks.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React Fiber Hooks - Internal Hooks Implementation
3 | *
4 | * This module contains the internal implementation of React hooks. It manages
5 | * hook state, effects, and the hook dispatcher system that switches between
6 | * mount and update phases.
7 | *
8 | * Key concepts:
9 | * - Hook objects store state and form a linked list per component
10 | * - Different dispatchers for mount vs update phases
11 | * - Effect hooks manage side effects with dependency tracking
12 | * - State hooks manage component state with update queues
13 | *
14 | * @module ReactFiberHooks
15 | */
16 |
17 | import ReactSharedInternals from "shared/ReactSharedInternals";
18 | import {
19 | scheduleUpdateOnFiber,
20 | requestUpdateLane,
21 | requestEventTime,
22 | } from "./ReactFiberWorkLoop";
23 | import { enqueueConcurrentHookUpdate } from "./ReactFiberConcurrentUpdates";
24 | import {
25 | Passive as PassiveEffect,
26 | Update as UpdateEffect,
27 | } from "./ReactFiberFlags";
28 | import {
29 | HasEffect as HookHasEffect,
30 | Passive as HookPassive,
31 | Layout as HookLayout,
32 | } from "./ReactHookEffectTags";
33 | import { NoLane, NoLanes, isSubsetOfLanes, mergeLanes } from "./ReactFiberLane";
34 |
35 | // Global hook state
36 | const { ReactCurrentDispatcher } = ReactSharedInternals;
37 | let currentlyRenderingFiber = null; // Fiber currently being rendered
38 | let workInProgressHook = null; // Current hook being processed
39 | let currentHook = null; // Hook from previous render
40 | let renderLanes = NoLanes; // Current render priority lanes
41 |
42 | const HooksDispatcherOnMount = {
43 | useReducer: mountReducer,
44 | useState: mountState,
45 | useEffect: mountEffect,
46 | useLayoutEffect: mountLayoutEffect,
47 | useRef: mountRef,
48 | };
49 | const HooksDispatcherOnUpdate = {
50 | useReducer: updateReducer,
51 | useState: updateState,
52 | useEffect: updateEffect,
53 | useLayoutEffect: updateLayoutEffect,
54 | useRef: updateRef,
55 | };
56 | function mountRef(initialValue) {
57 | const hook = mountWorkInProgressHook();
58 | const ref = {
59 | current: initialValue,
60 | };
61 | hook.memoizedState = ref;
62 | return ref;
63 | }
64 | function updateRef() {
65 | const hook = updateWorkInProgressHook();
66 | return hook.memoizedState;
67 | }
68 | function mountLayoutEffect(create, deps) {
69 | return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
70 | }
71 | function updateLayoutEffect(create, deps) {
72 | return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
73 | }
74 | function updateEffect(create, deps) {
75 | return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
76 | }
77 | function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
78 | const hook = updateWorkInProgressHook();
79 | const nextDeps = deps === undefined ? null : deps;
80 | let destroy;
81 | // the previous old hook
82 | if (currentHook !== null) {
83 | // get the old effect object on this useEffect hook create deps destroy
84 | const prevEffect = currentHook.memoizedState;
85 | destroy = prevEffect.destroy;
86 | if (nextDeps !== null) {
87 | const prevDeps = prevEffect.deps;
88 | // compare the new array with the old array, if they are the same
89 | if (areHookInputsEqual(nextDeps, prevDeps)) {
90 | // regardless of whether it needs to be re-executed, the new effect needs to be added to the fiber.updateQueue
91 | hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
92 | return;
93 | }
94 | }
95 | }
96 | // if it needs to be executed, the fiber's flags need to be modified
97 | currentlyRenderingFiber.flags |= fiberFlags;
98 | // if it needs to be executed, add the HookHasEffect flag
99 | // recently a student asked Passive还需HookHasEffect, because not every Passive will be executed
100 | hook.memoizedState = pushEffect(
101 | HookHasEffect | hookFlags,
102 | create,
103 | destroy,
104 | nextDeps
105 | );
106 | }
107 | function areHookInputsEqual(nextDeps, prevDeps) {
108 | if (prevDeps === null) return null;
109 | for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
110 | if (Object.is(nextDeps[i], prevDeps[i])) {
111 | continue;
112 | }
113 | return false;
114 | }
115 | return true;
116 | }
117 | function mountEffect(create, deps) {
118 | return mountEffectImpl(PassiveEffect, HookPassive, create, deps);
119 | }
120 | /* function mountLayoutEffect(create, deps) {
121 | return mountEffectImpl(PassiveEffect, HookPassive, create, deps);
122 | } */
123 | function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
124 | const hook = mountWorkInProgressHook();
125 | const nextDeps = deps === undefined ? null : deps;
126 | //给当前的函数组件fiber添加flags
127 | currentlyRenderingFiber.flags |= fiberFlags;
128 | hook.memoizedState = pushEffect(
129 | HookHasEffect | hookFlags,
130 | create,
131 | undefined,
132 | nextDeps
133 | );
134 | }
135 | /**
136 | * add effect list
137 | * @param {*} tag effect tag
138 | * @param {*} create create method
139 | * @param {*} destroy destroy method
140 | * @param {*} deps dependencies array
141 | */
142 | function pushEffect(tag, create, destroy, deps) {
143 | const effect = {
144 | tag,
145 | create,
146 | destroy,
147 | deps,
148 | next: null,
149 | };
150 | let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
151 | if (componentUpdateQueue === null) {
152 | componentUpdateQueue = createFunctionComponentUpdateQueue();
153 | currentlyRenderingFiber.updateQueue = componentUpdateQueue;
154 | componentUpdateQueue.lastEffect = effect.next = effect;
155 | } else {
156 | const lastEffect = componentUpdateQueue.lastEffect;
157 | if (lastEffect === null) {
158 | componentUpdateQueue.lastEffect = effect.next = effect;
159 | } else {
160 | const firstEffect = lastEffect.next;
161 | lastEffect.next = effect;
162 | effect.next = firstEffect;
163 | componentUpdateQueue.lastEffect = effect;
164 | }
165 | }
166 | return effect;
167 | }
168 | function createFunctionComponentUpdateQueue() {
169 | return {
170 | lastEffect: null,
171 | };
172 | }
173 | // useState is actually a useReducer with a reducer built in
174 | function baseStateReducer(state, action) {
175 | return typeof action === "function" ? action(state) : action;
176 | }
177 | function updateState(initialState) {
178 | return updateReducer(baseStateReducer, initialState);
179 | }
180 | /**
181 | * hook attributes
182 | * hook.memoizedState the current hook's真正显示出来的状态
183 | * hook.baseState the old state before the first skipped update
184 | * hook.queue.lastRenderedState the previous calculated state
185 | */
186 | function mountState(initialState) {
187 | const hook = mountWorkInProgressHook();
188 | hook.memoizedState = hook.baseState = initialState;
189 | const queue = {
190 | pending: null,
191 | dispatch: null,
192 | lastRenderedReducer: baseStateReducer, // the previous reducer
193 | lastRenderedState: initialState, // the previous state
194 | };
195 | hook.queue = queue;
196 | const dispatch = (queue.dispatch = dispatchSetState.bind(
197 | null,
198 | currentlyRenderingFiber,
199 | queue
200 | ));
201 | return [hook.memoizedState, dispatch];
202 | }
203 | function dispatchSetState(fiber, queue, action) {
204 | // get the current update lane lane 1
205 | const lane = requestUpdateLane();
206 | const update = {
207 | lane, // the current update priority is 1
208 | action,
209 | hasEagerState: false, // whether there is an eager update
210 | eagerState: null, // eager update state
211 | next: null,
212 | };
213 | const alternate = fiber.alternate;
214 |
215 | // when you dispatch an action, I immediately use the previous state and the previous reducer to calculate the new state
216 | // as long as the first update can perform this optimization
217 | if (
218 | fiber.lanes === NoLanes &&
219 | (alternate === null || alternate.lanes == NoLanes)
220 | ) {
221 | // get the old state and reducer on the queue
222 | const { lastRenderedReducer, lastRenderedState } = queue;
223 | // use the previous state and reducer to calculate the new state
224 | const eagerState = lastRenderedReducer(lastRenderedState, action);
225 | update.hasEagerState = true;
226 | update.eagerState = eagerState;
227 | if (Object.is(eagerState, lastRenderedState)) {
228 | return;
229 | }
230 | }
231 | // below is the actual enqueue update, and schedule update logic
232 | const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
233 | const eventTime = requestEventTime();
234 | scheduleUpdateOnFiber(root, fiber, lane, eventTime);
235 | }
236 | /**
237 | * build new hooks
238 | */
239 | function updateWorkInProgressHook() {
240 | // get the old hook of the new hook to be built
241 | if (currentHook === null) {
242 | const current = currentlyRenderingFiber.alternate;
243 | currentHook = current.memoizedState;
244 | } else {
245 | currentHook = currentHook.next;
246 | }
247 | // create new hook based on the old hook
248 | const newHook = {
249 | memoizedState: currentHook.memoizedState,
250 | queue: currentHook.queue,
251 | next: null,
252 | baseState: currentHook.baseState,
253 | baseQueue: currentHook.baseQueue,
254 | };
255 | if (workInProgressHook === null) {
256 | currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
257 | } else {
258 | workInProgressHook = workInProgressHook.next = newHook;
259 | }
260 | return workInProgressHook;
261 | }
262 | function updateReducer(reducer) {
263 | const hook = updateWorkInProgressHook();
264 | const queue = hook.queue;
265 | queue.lastRenderedReducer = reducer;
266 | const current = currentHook;
267 | let baseQueue = current.baseQueue;
268 | const pendingQueue = queue.pending;
269 | // merge the new and old update lists
270 | if (pendingQueue !== null) {
271 | if (baseQueue !== null) {
272 | const baseFirst = baseQueue.next;
273 | const pendingFirst = pendingQueue.next;
274 | baseQueue.next = pendingFirst;
275 | pendingQueue.next = baseFirst;
276 | }
277 | current.baseQueue = baseQueue = pendingQueue;
278 | queue.pending = null;
279 | }
280 | if (baseQueue !== null) {
281 | printQueue(baseQueue);
282 | const first = baseQueue.next;
283 | let newState = current.baseState;
284 | let newBaseState = null;
285 | let newBaseQueueFirst = null;
286 | let newBaseQueueLast = null;
287 | let update = first;
288 | do {
289 | const updateLane = update.lane;
290 | const shouldSkipUpdate = !isSubsetOfLanes(renderLanes, updateLane);
291 | if (shouldSkipUpdate) {
292 | const clone = {
293 | lane: updateLane,
294 | action: update.action,
295 | hasEagerState: update.hasEagerState,
296 | eagerState: update.eagerState,
297 | next: null,
298 | };
299 | if (newBaseQueueLast === null) {
300 | newBaseQueueFirst = newBaseQueueLast = clone;
301 | newBaseState = newState;
302 | } else {
303 | newBaseQueueLast = newBaseQueueLast.next = clone;
304 | }
305 | currentlyRenderingFiber.lanes = mergeLanes(
306 | currentlyRenderingFiber.lanes,
307 | updateLane
308 | );
309 | } else {
310 | if (newBaseQueueLast !== null) {
311 | const clone = {
312 | lane: NoLane,
313 | action: update.action,
314 | hasEagerState: update.hasEagerState,
315 | eagerState: update.eagerState,
316 | next: null,
317 | };
318 | newBaseQueueLast = newBaseQueueLast.next = clone;
319 | }
320 | if (update.hasEagerState) {
321 | newState = update.eagerState;
322 | } else {
323 | const action = update.action;
324 | newState = reducer(newState, action);
325 | }
326 | }
327 | update = update.next;
328 | } while (update !== null && update !== first);
329 | if (newBaseQueueLast === null) {
330 | newBaseState = newState;
331 | } else {
332 | newBaseQueueLast.next = newBaseQueueFirst;
333 | }
334 | hook.memoizedState = newState;
335 | hook.baseState = newBaseState;
336 | hook.baseQueue = newBaseQueueLast;
337 | queue.lastRenderedState = newState;
338 | }
339 | if (baseQueue === null) {
340 | queue.lanes = NoLanes;
341 | }
342 | const dispatch = queue.dispatch;
343 | return [hook.memoizedState, dispatch];
344 | }
345 | function printQueue(queue) {
346 | const first = queue.next;
347 | let desc = "";
348 | let update = first;
349 | do {
350 | desc += "=>" + update.action.id;
351 | update = update.next;
352 | } while (update !== null && update !== first);
353 | desc += "=>null";
354 | console.log(desc);
355 | }
356 | function mountReducer(reducer, initialArg) {
357 | const hook = mountWorkInProgressHook();
358 | hook.memoizedState = hook.baseState = initialArg;
359 | const queue = {
360 | pending: null,
361 | dispatch: null,
362 | lastRenderedReducer: reducer,
363 | lastRenderedState: initialArg,
364 | };
365 | hook.queue = queue;
366 | const dispatch = (queue.dispatch = dispatchReducerAction.bind(
367 | null,
368 | currentlyRenderingFiber,
369 | queue
370 | ));
371 | return [hook.memoizedState, dispatch];
372 | }
373 | /**
374 | * execute the method of dispatching an action, it needs to update the state, and make the interface re-update
375 | * @param {*} fiber the fiber corresponding to the function
376 | * @param {*} queue the update queue corresponding to the hook
377 | * @param {*} action the action to be dispatched
378 | */
379 | function dispatchReducerAction(fiber, queue, action) {
380 | // in each hook, there will be an update queue, the update queue is a circular list of update objects update1.next=update2.next=update1
381 | const lane = requestUpdateLane();
382 | const update = {
383 | lane, // the current update priority
384 | action, //{ type: 'add', payload: 1 } the action to be dispatched
385 | hasEagerState: false, // whether there is an eager update
386 | eagerState: null, // eager update state
387 | next: null, // point to the next update object
388 | };
389 | // add the current latest update to the update queue, and return the current root fiber
390 | const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
391 | const eventTime = requestEventTime();
392 | scheduleUpdateOnFiber(root, fiber, lane, eventTime);
393 | }
394 | /**
395 | * mount the hook being built
396 | * */
397 | function mountWorkInProgressHook() {
398 | const hook = {
399 | memoizedState: null, //hook's state 0
400 | queue: null, // the update queue corresponding to the hook queue.pending=update's circular list
401 | next: null, // point to the next hook, a function can have multiple hooks, they will form a singly linked list
402 | baseState: null, // the state before the first skipped update
403 | baseQueue: null, // the list of skipped updates
404 | };
405 | if (workInProgressHook === null) {
406 | // the state of the fiber corresponding to the current function is equal to the first hook object
407 | currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
408 | } else {
409 | workInProgressHook = workInProgressHook.next = hook;
410 | }
411 | return workInProgressHook;
412 | }
413 | /**
414 | * render the function component
415 | * @param {*} current old fiber
416 | * @param {*} workInProgress new fiber
417 | * @param {*} Component component definition
418 | * @param {*} props component properties
419 | * @returns virtual DOM or React element
420 | */
421 | export function renderWithHooks(
422 | current,
423 | workInProgress,
424 | Component,
425 | props,
426 | nextRenderLanes
427 | ) {
428 | // the current render lane
429 | renderLanes = nextRenderLanes;
430 | currentlyRenderingFiber = workInProgress;
431 | // the update queue of the function component stores the effect
432 | workInProgress.updateQueue = null;
433 | // the linked list of hooks stored in the function component state
434 | workInProgress.memoizedState = null;
435 | // if there is an old fiber and an old hook linked list
436 | if (current !== null && current.memoizedState !== null) {
437 | ReactCurrentDispatcher.current = HooksDispatcherOnUpdate;
438 | } else {
439 | ReactCurrentDispatcher.current = HooksDispatcherOnMount;
440 | }
441 | // need to assign the value of ReactCurrentDispatcher.current before executing the function component
442 | const children = Component(props);
443 | currentlyRenderingFiber = null;
444 | workInProgressHook = null;
445 | currentHook = null;
446 | renderLanes = NoLanes;
447 | return children;
448 | }
449 |
--------------------------------------------------------------------------------