1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        React16源碼解讀:揭秘ReactDOM.render

        共 17075字,需瀏覽 35分鐘

         ·

        2020-11-25 05:31

        點擊上方"前端之境",選擇"置頂或者星標"

        你的關(guān)注意義重大!

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

        源碼中有很多判斷類似__DEV__變量的控制語句,用于區(qū)分開發(fā)環(huán)境和生產(chǎn)環(huán)境,筆者在閱讀源碼的過程中不太關(guān)心這些內(nèi)容,就直接略過了,有興趣的小伙伴兒可以自己研究研究。

        render VS hydrate

        本系列的源碼分析是基于 Reactv16.10.2版本的,為了保證源碼一致還是建議你選擇相同的版本,下載該版本的地址和筆者選擇該版本的具體原因可以在上一篇文章的準備階段小節(jié)中查看,這里就不做過多講解了。項目示例本身也比較簡單,可以按照準備階段的步驟自行使用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對象包含了很多我們可能使用過的方法,例如render、createPortalfindDOMNode,hydrateunmountComponentAtNode等。本文中我們暫且只關(guān)心render方法,但為了方便對比,也可以簡單看下hydrate方法:

        const ReactDOM: Object = {
        ...
        /**
        * 服務(wù)端渲染
        * @param element 表示一個ReactNode,可以是一個ReactElement對象
        * @param container 需要將組件掛載到頁面中的DOM容器
        * @param callback 渲染完成后需要執(zhí)行的回調(diào)函數(shù)
        */

        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?
        // 注意第一個參數(shù)為null,第四個參數(shù)為true
        return legacyRenderSubtreeIntoContainer(
        null,
        element,
        container,
        true,
        callback,
        );
        },

        /**
        * 客戶端渲染
        * @param element 表示一個ReactElement對象
        * @param container 需要將組件掛載到頁面中的DOM容器
        * @param callback 渲染完成后需要執(zhí)行的回調(diào)函數(shù)
        */

        render(
        element: React$Element,
        container: DOMContainer,
        callback: ?Function,
        ) {
        invariant(
        isValidContainer(container),
        'Target container is not a DOM element.',
        );
        ...
        // 注意第一個參數(shù)為null,第四個參數(shù)為false
        return legacyRenderSubtreeIntoContainer(
        null,
        element,
        container,
        false,
        callback,
        );
        },
        ...
        };

        發(fā)現(xiàn)沒,render方法的第一個參數(shù)就是我們在上篇文章中講過的ReactElement對象,所以說上篇文章的內(nèi)容就是為了在這里打下基礎(chǔ)的,便于我們對參數(shù)的理解。事實上,在源碼中幾乎所有方法參數(shù)中的element字段均可以傳入一個ReactElement實例,這個實例就是通過 Babel 編譯器在編譯過程中使用React.createElement方法得到的。接下來在render方法中調(diào)用legacyRenderSubtreeIntoContainer來正式進入渲染流程,不過這里需要留意一下的是,render方法和hydrate方法在執(zhí)行legacyRenderSubtreeIntoContainer時,第一個參數(shù)的值均為null,第四個參數(shù)的值恰好相反。

        然后將代碼定位到第570行,進入legacyRenderSubtreeIntoContainer方法的具體實現(xiàn):

        /**
        * 開始構(gòu)建FiberRoot和RootFiber,之后開始執(zhí)行更新任務(wù)
        * @param parentComponent 父組件,可以把它當(dāng)成null值來處理
        * @param children ReactDOM.render()或者ReactDOM.hydrate()中的第一個參數(shù),可以理解為根組件
        * @param container ReactDOM.render()或者ReactDOM.hydrate()中的第二個參數(shù),組件需要掛載的DOM容器
        * @param forceHydrate 表示是否融合,用于區(qū)分客戶端渲染和服務(wù)端渲染,render方法傳false,hydrate方法傳true
        * @param callback ReactDOM.render()或者ReactDOM.hydrate()中的第三個參數(shù),組件渲染完成后需要執(zhí)行的回調(diào)函數(shù)
        * @returns {*}
        */

        function legacyRenderSubtreeIntoContainer(
        parentComponent: ?React$Component,
        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.
        // 在第一次執(zhí)行的時候,container上是肯定沒有_reactRootContainer屬性的
        // 所以第一次執(zhí)行時,root肯定為undefined
        let root: _ReactSyncRoot = (container._reactRootContainer: any);
        let fiberRoot;
        if (!root) {
        // Initial mount
        // 首次掛載,進入當(dāng)前流程控制中,container._reactRootContainer指向一個ReactSyncRoot實例
        root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
        container,
        forceHydrate,
        );
        // root表示一個ReactSyncRoot實例,實例中有一個_internalRoot方法指向一個fiberRoot實例
        fiberRoot = root._internalRoot;
        // callback表示ReactDOM.render()或者ReactDOM.hydrate()中的第三個參數(shù)
        // 重寫callback,通過fiberRoot去找到其對應(yīng)的rootFiber,然后將rootFiber的第一個child的stateNode作為callback中的this指向
        // 一般情況下我們很少去寫第三個參數(shù),所以可以不必關(guān)心這里的內(nèi)容
        if (typeof callback === 'function') {
        const originalCallback = callback;
        callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
        };
        }
        // Initial mount should not be batched.
        // 對于首次掛載來說,更新操作不應(yīng)該是批量的,所以會先執(zhí)行unbatchedUpdates方法
        // 該方法中會將executionContext(執(zhí)行上下文)切換成LegacyUnbatchedContext(非批量上下文)
        // 切換上下文之后再調(diào)用updateContainer執(zhí)行更新操作
        // 執(zhí)行完updateContainer之后再將executionContext恢復(fù)到之前的狀態(tài)
        unbatchedUpdates(() => {
        updateContainer(children, fiberRoot, parentComponent, callback);
        });
        } else {
        // 不是首次掛載,即container._reactRootContainer上已經(jīng)存在一個ReactSyncRoot實例
        fiberRoot = root._internalRoot;
        // 下面的控制語句和上面的邏輯保持一致
        if (typeof callback === 'function') {
        const originalCallback = callback;
        callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
        };
        }
        // Update
        // 對于非首次掛載來說,是不需要再調(diào)用unbatchedUpdates方法的
        // 即不再需要將executionContext(執(zhí)行上下文)切換成LegacyUnbatchedContext(非批量上下文)
        // 而是直接調(diào)用updateContainer執(zhí)行更新操作
        updateContainer(children, fiberRoot, parentComponent, callback);
        }
        return getPublicRootInstance(fiberRoot);
        }

        上面代碼的內(nèi)容稍微有些多,咋一看可能不太好理解,我們暫且可以不用著急看完整個函數(shù)內(nèi)容。試想當(dāng)我們第一次啟動運行項目的時候,也就是第一次執(zhí)行ReactDOM.render方法的時候,這時去獲取container._reactRootContainer肯定是沒有值的,所以我們先關(guān)心第一個if語句中的內(nèi)容:

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

        這里通過調(diào)用legacyCreateRootFromDOMContainer方法將其返回值賦值給container._reactRootContainer,我們將代碼定位到同文件下的第517行,去看看legacyCreateRootFromDOMContainer的具體實現(xiàn):

        /**
        * 創(chuàng)建并返回一個ReactSyncRoot實例
        * @param container ReactDOM.render()或者ReactDOM.hydrate()中的第二個參數(shù),組件需要掛載的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;
        // 循環(huán)遍歷每個子節(jié)點進行刪除
        while ((rootSibling = container.lastChild)) {
        ...
        container.removeChild(rootSibling);
        }
        }
        ...
        // Legacy roots are not batched.
        // 返回一個ReactSyncRoot實例
        // 該實例具有一個_internalRoot屬性指向fiberRoot
        return new ReactSyncRoot(
        container,
        LegacyRoot,
        shouldHydrate
        ? {
        hydrate: true,
        }
        : undefined,
        );
        }

        /**
        * 根據(jù)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)
        );
        }

        /**
        * 根據(jù)container來獲取DOM容器中的第一個子節(jié)點
        * @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方法中,首先根據(jù)container來獲取 DOM 容器中的第一個子節(jié)點,獲取該子節(jié)點的目的在于通過節(jié)點的nodeType和是否具有ROOT_ATTRIBUTE_NAME屬性來區(qū)分是客戶端渲染還是服務(wù)端渲染,ROOT_ATTRIBUTE_NAME位于packages/react-dom/src/shared/DOMProperty.js文件中,表示data-reactroot屬性。我們知道,在服務(wù)端渲染中有別于客戶端渲染的是,node服務(wù)會在后臺先根據(jù)匹配到的路由生成完整的HTML字符串,然后再將HTML字符串發(fā)送到瀏覽器端,最終生成的HTML結(jié)構(gòu)簡化后如下:

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

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

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

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

        FiberRoot VS RootFiber

        在這一小節(jié)中,我們將嘗試去理解兩個比較容易混淆的概念:FiberRootRootFiber。這兩個概念在 React 的整個任務(wù)調(diào)度過程中起著關(guān)鍵性的作用,如果不理解這兩個概念,后續(xù)的任務(wù)調(diào)度過程就是空談,所以這里也是我們必須要去理解的部分。接下來接著上一小節(jié)的內(nèi)容,繼續(xù)分析legacyCreateRootFromDOMContainer方法中的剩余內(nèi)容,在函數(shù)體的結(jié)尾返回了一個ReactSyncRoot實例,我們重新回到ReactDOM.js文件可以很容易找到ReactSyncRoot構(gòu)造函數(shù)的具體內(nèi)容:

        /**
        * ReactSyncRoot構(gòu)造函數(shù)
        * @param container DOM容器
        * @param tag fiberRoot節(jié)點的標記(LegacyRoot、BatchedRoot、ConcurrentRoot)
        * @param options 配置信息,只有在hydrate時才有值,否則為undefined
        * @constructor
        */

        function ReactSyncRoot(
        container: DOMContainer,
        tag: RootTag,
        options: void | RootOptions,
        )
        {
        this._internalRoot = createRootImpl(container, tag, options);
        }

        /**
        * 創(chuàng)建并返回一個fiberRoot
        * @param container DOM容器
        * @param tag fiberRoot節(jié)點的標記(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;

        // 創(chuàng)建一個fiberRoot
        const root = createContainer(container, tag, hydrate, hydrationCallbacks);
        // 給container附加一個內(nèi)部屬性用于指向fiberRoot的current屬性對應(yīng)的rootFiber節(jié)點
        markContainerAsRoot(root.current, container);
        if (hydrate && tag !== LegacyRoot) {
        const doc =
        container.nodeType === DOCUMENT_NODE
        ? container
        : container.ownerDocument;
        eagerlyTrapReplayableEvents(doc);
        }
        return root;
        }

        從上述源碼中,我們可以看到createRootImpl方法通過調(diào)用createContainer方法來創(chuàng)建一個fiberRoot實例,并將該實例返回并賦值到ReactSyncRoot構(gòu)造函數(shù)的內(nèi)部成員_internalRoot屬性上。我們繼續(xù)深入createContainer方法去探究一下fiberRoot完整的創(chuàng)建過程,該方法被抽取到與react-dom包同級的另一個相關(guān)的依賴包react-reconciler包中,然后定位到react-reconciler/src/ReactFiberReconciler.js的第299行:

        /**
        * 內(nèi)部調(diào)用createFiberRoot方法返回一個fiberRoot實例
        * @param containerInfo DOM容器
        * @param tag fiberRoot節(jié)點的標記(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);
        }

        /**
        * 創(chuàng)建fiberRoot和rootFiber并相互引用
        * @param containerInfo DOM容器
        * @param tag fiberRoot節(jié)點的標記(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構(gòu)造函數(shù)創(chuàng)建一個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方法創(chuàng)建fiber tree的根節(jié)點,即rootFiber
        // 需要留意的是,fiber節(jié)點也會像DOM樹結(jié)構(gòu)一樣形成一個fiber tree單鏈表樹結(jié)構(gòu)
        // 每個DOM節(jié)點或者組件都會生成一個與之對應(yīng)的fiber節(jié)點(生成的過程會在后續(xù)的文章中進行解讀)
        // 在后續(xù)的調(diào)和(reconciliation)階段起著至關(guān)重要的作用
        const uninitializedFiber = createHostRootFiber(tag);
        // 創(chuàng)建完rootFiber之后,會將fiberRoot實例的current屬性指向剛創(chuàng)建的rootFiber
        root.current = uninitializedFiber;
        // 同時rootFiber的stateNode屬性會指向fiberRoot實例,形成相互引用
        uninitializedFiber.stateNode = root;
        // 最后將創(chuàng)建的fiberRoot實例返回
        return root;
        }

        一個完整的FiberRootNode實例包含了很多有用的屬性,這些屬性在任務(wù)調(diào)度階段都發(fā)揮著各自的作用,可以在ReactFiberRoot.js文件中看到完整的FiberRootNode構(gòu)造函數(shù)的實現(xiàn)(這里只列舉部分屬性):

        /**
        * FiberRootNode構(gòu)造函數(shù)
        * @param containerInfo DOM容器
        * @param tag fiberRoot節(jié)點的標記(LegacyRoot、BatchedRoot、ConcurrentRoot)
        * @param hydrate 判斷是否是hydrate模式
        * @constructor
        */

        function FiberRootNode(containerInfo, tag, hydrate) {
        // 用于標記fiberRoot的類型
        this.tag = tag;
        // 指向當(dāng)前激活的與之對應(yīng)的rootFiber節(jié)點
        this.current = null;
        // 和fiberRoot關(guān)聯(lián)的DOM容器的相關(guān)信息
        this.containerInfo = containerInfo;
        ...
        // 當(dāng)前的fiberRoot是否處于hydrate模式
        this.hydrate = hydrate;
        ...
        // 每個fiberRoot實例上都只會維護一個任務(wù),該任務(wù)保存在callbackNode屬性中
        this.callbackNode = null;
        // 當(dāng)前任務(wù)的優(yōu)先級
        this.callbackPriority = NoPriority;
        ...
        }

        部分屬性信息如上所示,由于屬性過多并且在本文中暫時還用不到,這里就先不一一列舉出來了,剩余的屬性及其注釋信息已經(jīng)上傳至Github(https://github.com/qq591468061/react-16.10.2),感興趣的朋友可以自行查看。在了解完了fiberRoot的屬性結(jié)構(gòu)之后,接下來繼續(xù)探究createFiberRoot方法的后半部分內(nèi)容:

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

        // 以下代碼來自ReactFiber.js文件
        /**
        * 內(nèi)部調(diào)用createFiber方法創(chuàng)建一個FiberNode實例
        * @param tag fiberRoot節(jié)點的標記(LegacyRoot、BatchedRoot、ConcurrentRoot)
        * @returns {Fiber}
        */

        export function createHostRootFiber(tag: RootTag): Fiber {
        let mode;
        // 以下代碼根據(jù)fiberRoot的標記類型來動態(tài)設(shè)置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;
        }
        ...

        // 調(diào)用createFiber方法創(chuàng)建并返回一個FiberNode實例
        // HostRoot表示fiber tree的根節(jié)點
        // 其他標記類型可以在shared/ReactWorkTags.js文件中找到
        return createFiber(HostRoot, null, null, mode);
        }

        /**
        * 創(chuàng)建并返回一個FiberNode實例
        * @param tag 用于標記fiber節(jié)點的類型(所有的類型存放在shared/ReactWorkTags.js文件中)
        * @param pendingProps 表示待處理的props數(shù)據(jù)
        * @param key 用于唯一標識一個fiber節(jié)點(特別在一些列表數(shù)據(jù)結(jié)構(gòu)中,一般會要求為每個DOM節(jié)點或組件加上額外的key屬性,在后續(xù)的調(diào)和階段會派上用場)
        * @param mode 表示fiber節(jié)點的模式
        * @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構(gòu)造函數(shù)用于創(chuàng)建一個FiberNode實例,即一個fiber節(jié)點
        return new FiberNode(tag, pendingProps, key, mode);
        };

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

        /**
        * FiberNode構(gòu)造函數(shù)
        * @param tag 用于標記fiber節(jié)點的類型
        * @param pendingProps 表示待處理的props數(shù)據(jù)
        * @param key 用于唯一標識一個fiber節(jié)點(特別在一些列表數(shù)據(jù)結(jié)構(gòu)中,一般會要求為每個DOM節(jié)點或組件加上額外的key屬性,在后續(xù)的調(diào)和階段會派上用場)
        * @param mode 表示fiber節(jié)點的模式
        * @constructor
        */

        function FiberNode(
        tag: WorkTag,
        pendingProps: mixed,
        key: null | string,
        mode: TypeOfMode,
        )
        {
        // Instance
        // 用于標記fiber節(jié)點的類型
        this.tag = tag;
        // 用于唯一標識一個fiber節(jié)點
        this.key = key;
        ...
        // 對于rootFiber節(jié)點而言,stateNode屬性指向?qū)?yīng)的fiberRoot節(jié)點
        // 對于child fiber節(jié)點而言,stateNode屬性指向?qū)?yīng)的組件實例
        this.stateNode = null;

        // Fiber
        // 以下屬性創(chuàng)建單鏈表樹結(jié)構(gòu)
        // return屬性始終指向父節(jié)點
        // child屬性始終指向第一個子節(jié)點
        // sibling屬性始終指向第一個兄弟節(jié)點
        this.return = null;
        this.child = null;
        this.sibling = null;
        // index屬性表示當(dāng)前fiber節(jié)點的索引
        this.index = 0;
        ...

        // 表示待處理的props數(shù)據(jù)
        this.pendingProps = pendingProps;
        // 表示之前已經(jīng)存儲的props數(shù)據(jù)
        this.memoizedProps = null;
        // 表示更新隊列
        // 例如在常見的setState操作中
        // 其實會先將需要更新的數(shù)據(jù)存放到這里的updateQueue隊列中用于后續(xù)調(diào)度
        this.updateQueue = null;
        // 表示之前已經(jīng)存儲的state數(shù)據(jù)
        this.memoizedState = null;
        ...

        // 表示fiber節(jié)點的模式
        this.mode = mode;

        // 表示當(dāng)前更新任務(wù)的過期時間,即在該時間之后更新任務(wù)將會被完成
        this.expirationTime = NoWork;
        // 表示當(dāng)前fiber節(jié)點的子fiber節(jié)點中具有最高優(yōu)先級的任務(wù)的過期時間
        // 該屬性的值會根據(jù)子fiber節(jié)點中的任務(wù)優(yōu)先級進行動態(tài)調(diào)整
        this.childExpirationTime = NoWork;

        // 用于指向另一個fiber節(jié)點
        // 這兩個fiber節(jié)點使用alternate屬性相互引用,形成雙緩沖
        // alternate屬性指向的fiber節(jié)點在任務(wù)調(diào)度中又稱為workInProgress節(jié)點
        this.alternate = null;
        ...
        }

        其他有用的屬性筆者已經(jīng)在源碼中寫好相關(guān)注釋,感興趣的朋友可以在Github上查看完整的注釋信息幫助理解。當(dāng)然在現(xiàn)階段,其中的一些屬性還暫時難以理解,不過沒有關(guān)系,在后續(xù)的內(nèi)容和系列文章中將會逐個擊破。在本小節(jié)中我們主要是為了理解FiberRootRootFiber這兩個容易混淆的概念以及兩者之間的聯(lián)系。同時在這里我們需要特別注意的是,多個fiber節(jié)點可形成基于單鏈表的樹形結(jié)構(gòu),通過自身的return,childsibling屬性可以在多個fiber節(jié)點之間建立聯(lián)系。為了更加容易理解多個fiber節(jié)點及其屬性之間的關(guān)系,這里先回顧一下在上一篇文章中的簡單示例,我們在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 learningh1>

        <List data={this.state.data} />
        div>
        );
        }
        }

        最終生成的 DOM 結(jié)構(gòu)如下所示:

        class="container">
        <h1 class="title">React learningh1>
        <ul class="data-list">
        <li class="data-item">1li>

        <li class="data-item">2li>
        <li class="data-item">3li>
        ul>
        div>

        基于該 DOM 結(jié)構(gòu)再結(jié)合上文中對源碼的分析過程,最后我們可以嘗試得出一張關(guān)系圖來加深印象:

        總結(jié)

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

        ?

        瀏覽 50
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产69精品久久久久久 | 亚洲无码一二区 | 探花网址在线观看 | 免费看日产一区二区三区 | 大陆偷拍一本到 | 大鸡巴日B | 久久人人爽人人爽人人片亞洲 | 亚洲高清无码免费 | 熟女一区二区三区视频 | 美女被日视频 |