## **如何閱讀大型開源專案的程式碼** 1. 不要為了看程式碼而看程式碼 1. 解決遇到的問題 2. 想了解整個 React 系統結構的想法 & 實踐過程🤔 2. 前置了解 → 本文分享內容🥹 1. 這個專案中可以分成幾個部分 2. 之前的交互關係是什麼 3. clone → local build https://github.com/facebook/react React 中有個方便測試的檔案 dev.htm 可以快速查看我們在 local 端的變動 > The easiest way to try your changes is to run yarn build core,dom –type=UMD and then open fixtures/packaging/babel-standalone/dev.html. This file already uses React.development.js from the build folder so it will pick up your changes. > > > **[Development Workflow](https://legacy.reactjs.org/docs/how-to-contribute.html#development-workflow) - React official website** > 4. 了解目錄結構 5. 善用 debugger & 全局搜索 6. 關注專案 & 核心開發者 1. [React blog](https://react.dev/blog) 2. [**Dan Abramov](https://twitter.com/dan_abramov)** → 已離開 meta 3. [Andrew Clark](https://twitter.com/acdlite) 4. [Sebastian Markbåge](https://twitter.com/sebmarkbage) ## Core :::info 🚧 **架構概覽圖** ![image](https://hackmd.io/_uploads/BJtEq1486.png) ::: :::spoiler 🧑‍💻 從 user 的角度去理解 React 的核心運作 1. **User Interaction** 使用者點擊按鈕觸發點擊事件。 2. **Event Handling** React DOM 捕獲並處理點擊事件,呼叫 **`handleClick`** 函式。 3. **React Reconciler** **`handleClick`** 使用 **`setCount`** 更新狀態,啟動 **`react-reconciler`**。 4. **Scheduler ➡️** 更新過程的起點 **`scheduler`** 安排一個更新任務。 5. **Re-render** **`react-reconciler`** 比較虛擬 DOM,生成新的 Virtual DOM。 → Diff 6. **React DOM** **`react-dom`** 使用新的虛擬 DOM 操作實際 DOM。 7. **Browser Rendering** 瀏覽器接收到對 DOM 的變更,執行重新排版(Reflow)和重繪(Repaint)。 ::: ### Scheduler 為 React v16 之後新增的機制(React fiber) > **安排**一個更新的**任務**,確保 React 的整體性能和用戶體驗。 > - **避免阻塞**瀏覽器的主線程(避免畫面卡頓or白屏) - 大任務分隔成多個小任務(chunks) - 將小任務分到多個時間片段(time slice)執行 → 通常對應瀏覽器的一幀(Frame) - **資源利用** - 盡可能利用空閒時間執行任務,最佳化系統資源。 - 🎈**調度優先的任務**優先進入 `Reconciler` 執行(藉由 `Reconciler` 提供的 `callback function`) - 中斷和恢復機制 - → 維護一個任務 queue → [work loop] 循環直至清空 queue [](https://github.com/facebook/react/blob/1fb18e22ae66fdb1dc127347e169e73948778e5a/packages/scheduler/README.md) ⬆️ 是獨立於 react 的 experimental package - **Appendix** Scheduler 的概念其實已經有部分瀏覽器實踐出來了 → **[requestIdleCallback](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback)** 但基於以下考量 React 團隊放棄使用 選擇自己創建🧐 - 跨瀏覽器一致性 - 更好的配搭 React Fiber 架構 - 有更高的優先級 - 更精細的任務控制(time slice) - 可根據不同場景進行優化 ### Reconciler > 負責處理與協調 Virtual DOM 與 Real DOM 的同步。 > [](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/README.md#practical-examples) ⬆️ 也是獨立於 react 的 experimental package - Virtual DOM 的生成、比較和更新過程(包含 `Diff` 算法) - [work loop] 建構 `fiber樹` - 使用不同的 Reconciler 以實踐不同的環境 - React DOM Reconciler → ****Web - **React Native Reconciler** → mobile - **React Three Fiber Reconciler** → web with 3D(three.js) - **React VR Reconciler** → VR 最終 React-Reconciler 會將 Virtual DOM 變動的部分做上記號([如下](https://github.com/facebook/react/blob/1fb18e22ae66fdb1dc127347e169e73948778e5a/packages/react-reconciler/src/ReactSideEffectTags.js))交給 Renderer(react-dom 接口) 🙌 ```jsx export const Placement = /* */ 0b0000000000010; export const Update = /* */ 0b0000000000100; export const PlacementAndUpdate = /* */ 0b0000000000110; export const Deletion = /* */ 0b0000000001000; ``` ### R**eact DOM** > 根據 Reconciler 為 Virtual DOM打的標記,同步執行對應的 DOM 操作。 > ## APIs ### ReactElement #### createElement ```jsx // createElement 這個 function 接收 **type**, **config**, **children** 三個參數 export function createElement(**type**, **config**, **children**){} ``` 除了 div 之外,react-component 也是 type 的一種。 - **config** ```jsx if (config != null) { if (hasValidRef(config)) { // ①:ref ref = config.ref; if (__DEV__) { warnIfStringRefCannotBeAutoConverted(config); } } if (hasValidKey(config)) { // ②:key if (__DEV__) { checkKeyStringCoercion(config.key); } key = '' + config.key; // string } // ③:babel 轉換的屬性 self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; // ④:Remaining properties are added to a new props object for (propName in config) { if ( hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) ) { props[propName] = config[propName]; } } } ``` 1. **handle `ref` and `key`** 2. **handle `__self` and `__source` covert by babel** 使用 Babel 或其他 JSX 轉譯器來轉換 JSX 語法為 JavaScript 時,這些轉換器有時會在轉換過程中添加額外的屬性到 React 元素上,以**提供開發者更多的調試**和**錯誤追蹤的資訊**。 - __self:用於追蹤 React 元素被建立時的 **`this`** 指向 - __source:包含元素在原始程式碼中的位置信息 - JSX → JS code(by Babel) ```jsx import React from 'react'; const MyComponent = () => { return React.createElement("div", { __self: this, __source: { fileName: _jsxFileName, lineNumber: 6, columnNumber: 12 } }, "Hello, React!"); }; export default MyComponent; ``` ```jsx import React from 'react'; const MyComponent = () => { return React.createElement("div", { __self: this, // -> App __source: { fileName: _jsxFileName, // -> path/to/MyComponent.js lineNumber: 6, columnNumber: 12 } }, "Hello, React!"); }; export default MyComponent; ``` - **children** ```jsx // Children can be more than one argument, and those are transferred onto // the newly allocated props object. const childrenLength = arguments.length - 2; // ①:處理第三個 arg and 之後的 if (childrenLength === 1) { // ②: 1 個 child 會是 object props.children = children; } else if (childrenLength > 1) { // ③:大於 1 個 child 會是 array const childArray = Array(childrenLength); for (let i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } if (__DEV__) { // 凍結 object 防止之後的處理被修改 if (Object.freeze) { Object.freeze(childArray); } } props.children = childArray; } ``` - return **reactElement** ```jsx return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, ); ``` ![image](https://hackmd.io/_uploads/SJw7i1E8a.png) - 調度優先的任務**優先**進入 `Reconciler` 執行 - 中斷和恢復機制