主頁 > 企業開發 > React16原始碼解讀:揭秘ReactDOM.render

React16原始碼解讀:揭秘ReactDOM.render

2020-10-04 15:57:25 企業開發

引言

在上一篇文章中我們通過create-react-app腳手架快速搭建了一個簡單的示例,并基于該示例講解了在類組件中React.ComponentReact.PureComponent背后的實作原理,同時我們也了解到,通過使用Babel預置工具包@babel/preset-react可以將類組件中render方法的回傳值和函式定義組件中的回傳值轉換成使用React.createElement方法包裝而成的多層嵌套結構,并基于原始碼逐行分析了React.createElement方法背后的實作程序和ReactElement建構式的成員結構,最后根據分析結果總結出了幾道面試中可能會碰到或者自己以前遇到過的面試考點,上篇文章中的內容相對而言還是比較簡單基礎,主要是為本文以及后續的任務調度相關內容打下基礎,幫助我們更好地理解原始碼的用意,本文就結合上篇文章的基礎內容,從組件渲染的入口點ReactDOM.render方法開始,一步一步深入原始碼,揭秘ReactDOM.render方法背后的實作原理,如有錯誤,還請指出,

原始碼中有很多判斷類似__DEV__變數的控制陳述句,用于區分開發環境和生產環境,筆者在閱讀原始碼的程序中不太關心這些內容,就直接略過了,有興趣的小伙伴兒可以自己研究研究,

render VS hydrate

本系列的原始碼分析是基于Reactv16.10.2版本的,為了保證原始碼一致還是建議你選擇相同的版本,下載該版本的地址和筆者選擇該版本的具體原因可以在上篇文章的準備階段小節中查看,這里就不做過多講解了,專案示例本身也比較簡單,可以按照準備階段的步驟自行使用create-react-app快速將一個簡單的示例搭建起來,然后我們定位到src/index.js檔案下,可以看到如下代碼:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
...
ReactDOM.render(<App />, document.getElementById('root'));
...

該檔案即為專案的主入口檔案,App組件即為根組件,ReactDOM.render就是我們要開始分析原始碼的入口點,我們通過以下路徑可以找到ReactDOM物件的完整代碼:

packages -> react-dom -> src -> client -> ReactDOM.js

然后我們將代碼定位到第632行,可以看到ReactDOM物件包含了很多我們可能使用過的方法,例如rendercreatePortalfindDOMNodehydrateunmountComponentAtNode等,本文中我們暫且只關心render方法,但為了方便對比,也可以簡單看下hydrate方法:

const ReactDOM: Object = {
  ...
  /**
   * 服務端渲染
   * @param element 表示一個ReactNode,可以是一個ReactElement物件
   * @param container 需要將組件掛載到頁面中的DOM容器
   * @param callback 渲染完成后需要執行的回呼函式
   */
  hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
    invariant(
      isValidContainer(container),
      'Target container is not a DOM element.',
    );
    ...
    // TODO: throw or warn if we couldn't hydrate?
    // 注意第一個引數為null,第四個引數為true
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      true,
      callback,
    );
  },

  /**
   * 客戶端渲染
   * @param element 表示一個ReactElement物件
   * @param container 需要將組件掛載到頁面中的DOM容器
   * @param callback 渲染完成后需要執行的回呼函式
   */
  render(
    element: React$Element<any>,
    container: DOMContainer,
    callback: ?Function,
  ) {
    invariant(
      isValidContainer(container),
      'Target container is not a DOM element.',
    );
    ...
    // 注意第一個引數為null,第四個引數為false
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback,
    );
  },
  ...
};

發現沒,render方法的第一個引數就是我們在上篇文章中講過的ReactElement物件,所以說上篇文章的內容就是為了在這里打下基礎的,便于我們對引數的理解,事實上,在原始碼中幾乎所有方法引數中的element欄位均可以傳入一個ReactElement實體,這個實體就是通過Babel編譯器在編譯程序中使用React.createElement方法得到的,接下來在render方法中呼叫legacyRenderSubtreeIntoContainer來正式進入渲染流程,不過這里需要留意一下的是,render方法和hydrate方法在執行legacyRenderSubtreeIntoContainer時,第一個引數的值均為null,第四個引數的值恰好相反,

然后將代碼定位到第570行,進入legacyRenderSubtreeIntoContainer方法的具體實作:

/**
 * 開始構建FiberRoot和RootFiber,之后開始執行更新任務
 * @param parentComponent 父組件,可以把它當成null值來處理
 * @param children ReactDOM.render()或者ReactDOM.hydrate()中的第一個引數,可以理解為根組件
 * @param container ReactDOM.render()或者ReactDOM.hydrate()中的第二個引數,組件需要掛載的DOM容器
 * @param forceHydrate 表示是否融合,用于區分客戶端渲染和服務端渲染,render方法傳false,hydrate方法傳true
 * @param callback ReactDOM.render()或者ReactDOM.hydrate()中的第三個引數,組件渲染完成后需要執行的回呼函式
 * @returns {*}
 */
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: DOMContainer,
  forceHydrate: boolean,
  callback: ?Function,
) {
  ...
  // TODO: Without `any` type, Flow says "Property cannot be accessed on any
  // member of intersection type." Whyyyyyy.
  // 在第一次執行的時候,container上是肯定沒有_reactRootContainer屬性的
  // 所以第一次執行時,root肯定為undefined
  let root: _ReactSyncRoot = (container._reactRootContainer: any);
  let fiberRoot;
  if (!root) {
    // Initial mount
    // 首次掛載,進入當前流程控制中,container._reactRootContainer指向一個ReactSyncRoot實體
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    // root表示一個ReactSyncRoot實體,實體中有一個_internalRoot方法指向一個fiberRoot實體
    fiberRoot = root._internalRoot;
    // callback表示ReactDOM.render()或者ReactDOM.hydrate()中的第三個引數
    // 重寫callback,通過fiberRoot去找到其對應的rootFiber,然后將rootFiber的第一個child的stateNode作為callback中的this指向
    // 一般情況下我們很少去寫第三個引數,所以可以不必關心這里的內容
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    // Initial mount should not be batched.
    // 對于首次掛載來說,更新操作不應該是批量的,所以會先執行unbatchedUpdates方法
    // 該方法中會將executionContext(執行背景關系)切換成LegacyUnbatchedContext(非批量背景關系)
    // 切換背景關系之后再呼叫updateContainer執行更新操作
    // 執行完updateContainer之后再將executionContext恢復到之前的狀態
    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    // 不是首次掛載,即container._reactRootContainer上已經存在一個ReactSyncRoot實體
    fiberRoot = root._internalRoot;
    // 下面的控制陳述句和上面的邏輯保持一致
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    // Update
    // 對于非首次掛載來說,是不需要再呼叫unbatchedUpdates方法的
    // 即不再需要將executionContext(執行背景關系)切換成LegacyUnbatchedContext(非批量背景關系)
    // 而是直接呼叫updateContainer執行更新操作
    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}

上面代碼的內容稍微有些多,咋一看可能不太好理解,我們暫且可以不用著急看完整個函式內容,試想當我們第一次啟動運行專案的時候,也就是第一次執行ReactDOM.render方法的時候,這時去獲取container._reactRootContainer肯定是沒有值的,所以我們先關心第一個if陳述句中的內容:

if (!root) {
    // Initial mount
    // 首次掛載,進入當前流程控制中,container._reactRootContainer指向一個ReactSyncRoot實體
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    ...
}

這里通過呼叫legacyCreateRootFromDOMContainer方法將其回傳值賦值給container._reactRootContainer,我們將代碼定位到同檔案下的第517行,去看看legacyCreateRootFromDOMContainer的具體實作:

/**
 * 創建并回傳一個ReactSyncRoot實體
 * @param container ReactDOM.render()或者ReactDOM.hydrate()中的第二個引數,組件需要掛載的DOM容器
 * @param forceHydrate 是否需要強制融合,render方法傳false,hydrate方法傳true
 * @returns {ReactSyncRoot}
 */
function legacyCreateRootFromDOMContainer(
  container: DOMContainer,
  forceHydrate: boolean,
): _ReactSyncRoot {
  // 判斷是否需要融合
  const shouldHydrate =
    forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
  // First clear any existing content.
  // 針對客戶端渲染的情況,需要將container容器中的所有元素移除
  if (!shouldHydrate) {
    let warned = false;
    let rootSibling;
    // 回圈遍歷每個子節點進行洗掉
    while ((rootSibling = container.lastChild)) {
      ...
      container.removeChild(rootSibling);
    }
  }
  ...
  // Legacy roots are not batched.
  // 回傳一個ReactSyncRoot實體
  // 該實體具有一個_internalRoot屬性指向fiberRoot
  return new ReactSyncRoot(
    container,
    LegacyRoot,
    shouldHydrate
      ? {
          hydrate: true,
        }
      : undefined,
  );
}

/**
 * 根據nodeType和attribute判斷是否需要融合
 * @param container DOM容器
 * @returns {boolean}
 */
function shouldHydrateDueToLegacyHeuristic(container) {
  const rootElement = getReactRootElementInContainer(container);
  return !!(
    rootElement &&
    rootElement.nodeType === ELEMENT_NODE &&
    rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
  );
}

/**
 * 根據container來獲取DOM容器中的第一個子節點
 * @param container DOM容器
 * @returns {*}
 */
function getReactRootElementInContainer(container: any) {
  if (!container) {
    return null;
  }

  if (container.nodeType === DOCUMENT_NODE) {
    return container.documentElement;
  } else {
    return container.firstChild;
  }
}

其中在shouldHydrateDueToLegacyHeuristic方法中,首先根據container來獲取DOM容器中的第一個子節點,獲取該子節點的目的在于通過節點的nodeType和是否具有ROOT_ATTRIBUTE_NAME屬性來區分是客戶端渲染還是服務端渲染,ROOT_ATTRIBUTE_NAME位于packages/react-dom/src/shared/DOMProperty.js檔案中,表示data-reactroot屬性,我們知道,在服務端渲染中有別于客戶端渲染的是,node服務會在后臺先根據匹配到的路由生成完整的HTML字串,然后再將HTML字串發送到瀏覽器端,最終生成的HTML結構簡化后如下:

<body>
    <div id="root">
        <div data-reactroot=""></div>
    </div>
</body>

在客戶端渲染中是沒有data-reactroot屬性的,因此就可以區分出客戶端渲染和服務端渲染,在React中的nodeType主要包含了五種,其對應的值和W3C中的nodeType標準是保持一致的,位于與DOMProperty.js同級的HTMLNodeType.js檔案中:

// 代表元素節點
export const ELEMENT_NODE = 1;
// 代表文本節點
export const TEXT_NODE = 3;
// 代表注釋節點
export const COMMENT_NODE = 8;
// 代表整個檔案,即document
export const DOCUMENT_NODE = 9;
// 代表檔案片段節點
export const DOCUMENT_FRAGMENT_NODE = 11;

經過以上分析,現在我們就可以很容易地區分出客戶端渲染和服務端渲染,并且在面試中如果被問到兩種渲染模式的區別,我們就可以很輕松地在原始碼級別上說出兩者的實作差異,讓面試官眼前一亮,怎么樣,到目前為止,其實還是覺得挺簡單的吧?

FiberRoot VS RootFiber

在這一小節中,我們將嘗試去理解兩個比較容易混淆的概念:FiberRootRootFiber,這兩個概念在React的整個任務調度程序中起著關鍵性的作用,如果不理解這兩個概念,后續的任務調度程序就是空談,所以這里也是我們必須要去理解的部分,接下來接著上一小節的內容,繼續分析legacyCreateRootFromDOMContainer方法中的剩余內容,在函式體的結尾回傳了一個ReactSyncRoot實體,我們重新回到ReactDOM.js檔案可以很容易找到ReactSyncRoot建構式的具體內容:

/**
 * ReactSyncRoot建構式
 * @param container DOM容器
 * @param tag fiberRoot節點的標記(LegacyRoot、BatchedRoot、ConcurrentRoot)
 * @param options 配置資訊,只有在hydrate時才有值,否則為undefined
 * @constructor
 */
function ReactSyncRoot(
  container: DOMContainer,
  tag: RootTag,
  options: void | RootOptions,
) {
  this._internalRoot = createRootImpl(container, tag, options);
}

/**
 * 創建并回傳一個fiberRoot
 * @param container DOM容器
 * @param tag fiberRoot節點的標記(LegacyRoot、BatchedRoot、ConcurrentRoot)
 * @param options 配置資訊,只有在hydrate時才有值,否則為undefined
 * @returns {*}
 */
function createRootImpl(
  container: DOMContainer,
  tag: RootTag,
  options: void | RootOptions,
) {
  // Tag is either LegacyRoot or Concurrent Root
  // 判斷是否是hydrate模式
  const hydrate = options != null && options.hydrate === true;
  const hydrationCallbacks =
    (options != null && options.hydrationOptions) || null;
  
  // 創建一個fiberRoot
  const root = createContainer(container, tag, hydrate, hydrationCallbacks);
  // 給container附加一個內部屬性用于指向fiberRoot的current屬性對應的rootFiber節點
  markContainerAsRoot(root.current, container);
  if (hydrate && tag !== LegacyRoot) {
    const doc =
      container.nodeType === DOCUMENT_NODE
        ? container
        : container.ownerDocument;
    eagerlyTrapReplayableEvents(doc);
  }
  return root;
}

從上述原始碼中,我們可以看到createRootImpl方法通過呼叫createContainer方法來創建一個fiberRoot實體,并將該實體回傳并賦值到ReactSyncRoot建構式的內部成員_internalRoot屬性上,我們繼續深入createContainer方法去探究一下fiberRoot完整的創建程序,該方法被抽取到與react-dom包同級的另一個相關的依賴包react-reconciler包中,然后定位到react-reconciler/src/ReactFiberReconciler.js的第299行:

/**
 * 內部呼叫createFiberRoot方法回傳一個fiberRoot實體
 * @param containerInfo DOM容器
 * @param tag fiberRoot節點的標記(LegacyRoot、BatchedRoot、ConcurrentRoot)
 * @param hydrate 判斷是否是hydrate模式
 * @param hydrationCallbacks 只有在hydrate模式時才可能有值,該物件包含兩個可選的方法:onHydrated和onDeleted
 * @returns {FiberRoot}
 */
export function createContainer(
  containerInfo: Container,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {
  return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}

/**
 * 創建fiberRoot和rootFiber并相互參考
 * @param containerInfo DOM容器
 * @param tag fiberRoot節點的標記(LegacyRoot、BatchedRoot、ConcurrentRoot)
 * @param hydrate 判斷是否是hydrate模式
 * @param hydrationCallbacks 只有在hydrate模式時才可能有值,該物件包含兩個可選的方法:onHydrated和onDeleted
 * @returns {FiberRoot}
 */
export function createFiberRoot(
  containerInfo: any,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
  // 通過FiberRootNode建構式創建一個fiberRoot實體
  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
  if (enableSuspenseCallback) {
    root.hydrationCallbacks = hydrationCallbacks;
  }

  // Cyclic construction. This cheats the type system right now because
  // stateNode is any.
  // 通過createHostRootFiber方法創建fiber tree的根節點,即rootFiber
  // 需要留意的是,fiber節點也會像DOM樹結構一樣形成一個fiber tree單鏈表樹結構
  // 每個DOM節點或者組件都會生成一個與之對應的fiber節點(生成的程序會在后續的文章中進行解讀)
  // 在后續的調和(reconciliation)階段起著至關重要的作用
  const uninitializedFiber = createHostRootFiber(tag);
  // 創建完rootFiber之后,會將fiberRoot實體的current屬性指向剛創建的rootFiber
  root.current = uninitializedFiber;
  // 同時rootFiber的stateNode屬性會指向fiberRoot實體,形成相互參考
  uninitializedFiber.stateNode = root;
  // 最后將創建的fiberRoot實體回傳
  return root;
}

一個完整的FiberRootNode實體包含了很多有用的屬性,這些屬性在任務調度階段都發揮著各自的作用,可以在ReactFiberRoot.js檔案中看到完整的FiberRootNode建構式的實作(這里只列舉部分屬性):

/**
 *  FiberRootNode建構式
 * @param containerInfo DOM容器
 * @param tag fiberRoot節點的標記(LegacyRoot、BatchedRoot、ConcurrentRoot)
 * @param hydrate 判斷是否是hydrate模式
 * @constructor
 */
function FiberRootNode(containerInfo, tag, hydrate) {
  // 用于標記fiberRoot的型別
  this.tag = tag;
  // 指向當前激活的與之對應的rootFiber節點
  this.current = null;
  // 和fiberRoot關聯的DOM容器的相關資訊
  this.containerInfo = containerInfo;
  ...
  // 當前的fiberRoot是否處于hydrate模式
  this.hydrate = hydrate;
  ...
  // 每個fiberRoot實體上都只會維護一個任務,該任務保存在callbackNode屬性中
  this.callbackNode = null;
  // 當前任務的優先級
  this.callbackPriority = NoPriority;
  ...
}

部分屬性資訊如上所示,由于屬性過多并且在本文中暫時還用不到,這里就先不一一列舉出來了,剩余的屬性及其注釋資訊已經上傳至Github,感興趣的朋友可以自行查看,在了解完了fiberRoot的屬性結構之后,接下來繼續探究createFiberRoot方法的后半部分內容:

// 以下代碼來自上文中的createFiberRoot方法
// 通過createHostRootFiber方法創建fiber tree的根節點,即rootFiber
const uninitializedFiber = createHostRootFiber(tag);
// 創建完rootFiber之后,會將fiberRoot實體的current屬性指向剛創建的rootFiber
root.current = uninitializedFiber;
// 同時rootFiber的stateNode屬性會指向fiberRoot實體,形成相互參考
uninitializedFiber.stateNode = root;

// 以下代碼來自ReactFiber.js檔案
/**
 * 內部呼叫createFiber方法創建一個FiberNode實體
 * @param tag fiberRoot節點的標記(LegacyRoot、BatchedRoot、ConcurrentRoot)
 * @returns {Fiber}
 */
export function createHostRootFiber(tag: RootTag): Fiber {
  let mode;
  // 以下代碼根據fiberRoot的標記型別來動態設定rootFiber的mode屬性
  // export const NoMode = 0b0000;          => 0
  // export const StrictMode = 0b0001;      => 1
  // export const BatchedMode = 0b0010;     => 2
  // export const ConcurrentMode = 0b0100;  => 4
  // export const ProfileMode = 0b1000;     => 8
  if (tag === ConcurrentRoot) {
    mode = ConcurrentMode | BatchedMode | StrictMode;
  } else if (tag === BatchedRoot) {
    mode = BatchedMode | StrictMode;
  } else {
    mode = NoMode;
  }
  ...

  // 呼叫createFiber方法創建并回傳一個FiberNode實體
  // HostRoot表示fiber tree的根節點
  // 其他標記型別可以在shared/ReactWorkTags.js檔案中找到
  return createFiber(HostRoot, null, null, mode);
}

/**
 * 創建并回傳一個FiberNode實體
 * @param tag 用于標記fiber節點的型別(所有的型別存放在shared/ReactWorkTags.js檔案中)
 * @param pendingProps 表示待處理的props資料
 * @param key 用于唯一標識一個fiber節點(特別在一些串列資料結構中,一般會要求為每個DOM節點或組件加上額外的key屬性,在后續的調和階段會派上用場)
 * @param mode 表示fiber節點的模式
 * @returns {FiberNode}
 */
const createFiber = function(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
): Fiber {
  // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
  // FiberNode建構式用于創建一個FiberNode實體,即一個fiber節點
  return new FiberNode(tag, pendingProps, key, mode);
};

至此我們就成功地創建了一個fiber節點,上文中我們提到過,和DOM樹結構類似,fiber節點也會形成一個與DOM樹結構對應的fiber tree,并且是基于單鏈表的樹結構,我們在上面剛創建的fiber節點可作為整個fiber tree的根節點,即RootFiber節點,在目前階段,我們暫時不用關心一個fiber節點所包含的所有屬性,但可以稍微留意一下以下相關屬性:

/**
 * FiberNode建構式
 * @param tag 用于標記fiber節點的型別
 * @param pendingProps 表示待處理的props資料
 * @param key 用于唯一標識一個fiber節點(特別在一些串列資料結構中,一般會要求為每個DOM節點或組件加上額外的key屬性,在后續的調和階段會派上用場)
 * @param mode 表示fiber節點的模式
 * @constructor
 */
function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // Instance
  // 用于標記fiber節點的型別
  this.tag = tag;
  // 用于唯一標識一個fiber節點
  this.key = key;
  ...
  // 對于rootFiber節點而言,stateNode屬性指向對應的fiberRoot節點
  // 對于child fiber節點而言,stateNode屬性指向對應的組件實體
  this.stateNode = null;

  // Fiber
  // 以下屬性創建單鏈表樹結構
  // return屬性始終指向父節點
  // child屬性始終指向第一個子節點
  // sibling屬性始終指向第一個兄弟節點
  this.return = null;
  this.child = null;
  this.sibling = null;
  // index屬性表示當前fiber節點的索引
  this.index = 0;
  ...

  // 表示待處理的props資料
  this.pendingProps = pendingProps;
  // 表示之前已經存盤的props資料
  this.memoizedProps = null;
  // 表示更新佇列
  // 例如在常見的setState操作中
  // 其實會先將需要更新的資料存放到這里的updateQueue佇列中用于后續調度
  this.updateQueue = null;
  // 表示之前已經存盤的state資料
  this.memoizedState = null;
  ...

  // 表示fiber節點的模式
  this.mode = mode;

  // 表示當前更新任務的過期時間,即在該時間之后更新任務將會被完成
  this.expirationTime = NoWork;
  // 表示當前fiber節點的子fiber節點中具有最高優先級的任務的過期時間
  // 該屬性的值會根據子fiber節點中的任務優先級進行動態調整
  this.childExpirationTime = NoWork;

  // 用于指向另一個fiber節點
  // 這兩個fiber節點使用alternate屬性相互參考,形成雙緩沖
  // alternate屬性指向的fiber節點在任務調度中又稱為workInProgress節點
  this.alternate = null;
  ...
}

其他有用的屬性筆者已經在原始碼中寫好相關注釋,感興趣的朋友可以在Github上查看完整的注釋資訊幫助理解,當然在現階段,其中的一些屬性還暫時難以理解,不過沒有關系,在后續的內容和系列文章中將會逐個擊破,在本小節中我們主要是為了理解FiberRootRootFiber這兩個容易混淆的概念以及兩者之間的聯系,同時在這里我們需要特別注意的是,多個fiber節點可形成基于單鏈表的樹形結構,通過自身的returnchildsibling屬性可以在多個fiber節點之間建立聯系,為了更加容易理解多個fiber節點及其屬性之間的關系,這里先回顧一下在上一篇文章中的簡單示例,我們在src/App.js檔案中將create-react-app腳手架生成的默認根組件App修改為如下形式:

import React, {Component} from 'react';

function List({data}) {
    return (
        <ul className="data-list">
            {
                data.map(item => {
                    return <li className="data-item" key={item}>{item}</li>
                })
            }
        </ul>
    );
}

export default class App extends Component {

    constructor(props) {
        super(props);
        this.state = {
            data: [1, 2, 3]
        };
    }

    render() {
        return (
            <div className="container">
                <h1 className="title">React learning</h1>
                <List data=https://www.cnblogs.com/tangshiwei/p/{this.state.data} />
            
); } }

最終生成的DOM結構如下所示:

<div >
    <h1 >React learning</h1>
    <ul >
        <li >1</li>
        <li >2</li>
        <li >3</li>
    </ul>
</div>

基于該DOM結構再結合上文中對原始碼的分析程序,最后我們可以嘗試得出一張關系圖來加深印象:

總結

本文主要是在上一篇文章內容的基礎之上從零開始逐行分析ReactDOM.render方法的實作原理,其背后的實作程序和呼叫堆疊還是非常復雜的,自己也是處于不斷的摸索程序中,在本文中主要是介紹兩個核心概念:FiberRootRootFiber,只有理解并區分這兩個概念之后才能更好地理解React的Fiber架構和任務調度階段中任務的執行程序,閱讀原始碼的程序是痛苦的,但與此同時自己所獲得的收益也是巨大的,為了避免文章過于枯燥,還是打算將原始碼內容劃分到系列文章中來單獨解讀,期間的間隔時間可用于對之前內容進行回顧,避免一口吃個胖子反而效果不好,

感謝閱讀

如果你覺得這篇文章的內容對你有幫助,能否幫個忙關注一下筆者的公眾號[前端之境],每周都會努力原創一些前端技術干貨,關注公眾號后可以邀你加入前端技術交流群,我們可以一起互相交流,共同進步,

文章已同步更新至Github博客,若覺文章尚可,歡迎前往star!

你的一個點贊,值得讓我付出更多的努力!

逆境中成長,只有不斷地學習,才能成為更好的自己,與君共勉!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/154857.html

標籤:JavaScript

上一篇:解決el-tree lazy懶加載時,連續勾選前兩個子節點后第二次進入默認選中時,將父節點也勾選的問題

下一篇:vue基礎中的注意事項,以及一些學習心得

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more