Try   HackMD

如何閱讀大型開源專案的程式碼

  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 - React official website

  4. 了解目錄結構

  5. 善用 debugger & 全局搜索

  6. 關注專案 & 核心開發者

    1. React blog
    2. **Dan Abramov** → 已離開 meta
    3. Andrew Clark
    4. Sebastian Markbåge

Core

🚧 架構概覽圖

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

🧑‍💻 從 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

⬆️ 是獨立於 react 的 experimental package

  • Appendix

    Scheduler 的概念其實已經有部分瀏覽器實踐出來了 → requestIdleCallback
    但基於以下考量 React 團隊放棄使用 選擇自己創建🧐

    • 跨瀏覽器一致性
    • 更好的配搭 React Fiber 架構
      • 有更高的優先級
      • 更精細的任務控制(time slice)
      • 可根據不同場景進行優化

Reconciler

負責處理與協調 Virtual DOM 與 Real DOM 的同步。

⬆️ 也是獨立於 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 變動的部分做上記號(如下)交給 Renderer(react-dom 接口) 🙌

export const Placement = /*             */ 0b0000000000010;
export const Update = /*                */ 0b0000000000100;
export const PlacementAndUpdate = /*    */ 0b0000000000110;
export const Deletion = /*              */ 0b0000000001000;

React DOM

根據 Reconciler 為 Virtual DOM打的標記,同步執行對應的 DOM 操作。

APIs

ReactElement

createElement

// createElement 這個 function 接收 **type**, **config**, **children** 三個參數
export function createElement(**type**, **config**, **children**){}

除了 div 之外,react-component 也是 type 的一種。

  • config

    ​​​​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)

        ​​​​​​​​​​​​import React from 'react';
        ​​​​​​​​​​​​
        ​​​​​​​​​​​​const MyComponent = () => {
        ​​​​​​​​​​​​  return React.createElement("div", {
        ​​​​​​​​​​​​    __self: this,
        ​​​​​​​​​​​​    __source: {
        ​​​​​​​​​​​​      fileName: _jsxFileName,
        ​​​​​​​​​​​​      lineNumber: 6,
        ​​​​​​​​​​​​      columnNumber: 12
        ​​​​​​​​​​​​    }
        ​​​​​​​​​​​​  }, "Hello, React!");
        ​​​​​​​​​​​​};
        ​​​​​​​​​​​​
        ​​​​​​​​​​​​export default MyComponent;
        
        ​​​​​​​​​​​​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

    ​​​​// 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

    ​​​​return ReactElement(
    ​​​​        type,
    ​​​​        key,
    ​​​​        ref,
    ​​​​        self,
    ​​​​        source,
    ​​​​        ReactCurrentOwner.current,
    ​​​​        props,
    ​​​​     );
    

image

  • 調度優先的任務優先進入 Reconciler 執行
    • 中斷和恢復機制