漸進式源碼解析React更新流程驅動

正文

前面兩篇文章介紹瞭fiber架構和workLoop如何調度。但是缺瞭一塊非常重要的地方,那就是開發者寫的代碼是如何對接到上面流程的?

在日常開發中,我們隻關心瞭如何寫組件,但是寫完的組件是如何被渲染到頁面當中?又是如何驅動更新流程?如果不知道這塊內容,其實大傢還是雲裡霧裡的停留在概念層面。

所以這篇文章主要是從開發者角度闡述開發代碼->編譯->觸發更新流程的介紹。

這裡的更新流程指schedule(調度)->reconciler(協調)->commit (渲染)

老規矩還是先制定5個小目標:

  • react組件編譯成什麼瞭?
  • reactElement元素是什麼?
  • 什麼是雙緩存技術?
  • react.createRoot().render()做瞭什麼事情?
  • 還有哪些更新方式對接到目前的更新流程當中?

ok,我們一一解答:

一、react.createElement和ReactElement元素

首先我們書寫的函數式組件、類組件、jsx等代碼全部會被babel-react編譯成react.createElement()的調用或者jsx()調用(取決於react版本)。

舉個栗子:

<div>
    <ul>
    <li key='1'>1</li>
    <li key='2'>2</li>
    <li key='3'>3</li>
    </ul>
</div>

轉換成

React.createElement(
	'div',
	null,
	React.createElement(
		'ul',
		null,
		React.createElement(
                'li',
                {
                  key: '1'
                },
                '1'
		),
		React.createElement(
                'li',
                {
                 key: '2'
                },
                '2'
		),
		React.createElement(
                'li',
                {
                        key: '3'
                },
                '3'
		)
	)
);

接下來我們需要知道React.createElement內部到底做瞭什麼?源碼位置

內部的實現其實很簡單,就是處理傳入的type/config/children等參數,再返回一個新的對象。

  • 從config中分離出特殊屬性 key 和 ref
  • 將普通屬性以及children添加到props中
  • 最後返回一個對象,這個對象我們稱之為ReactElement元素

ReactElement數據結構如下:

  const element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type,
    key,
    ref,
    props,
  };
  • '$$typeof':ReactElement的標識
  • 'type':可能是'div' 'span'這樣的字符串標簽,也可以是個函數(函數式組件)、類(類組件)
  • 'key/ref/props': ReactElement的屬性

所以上述栗子的調用結果是下面的樹形結構:

{
    type: 'div',
    key: null,
    ref: null,
    props: {
        children: {
                    type: 'ul',
                    key: null,
                    ref: null,
                    props: {
                        children: [
                                    {
                                        type: 'li',
                                        key: null,
                                        ref: null,
                                        props: {
                                            children: '1'
                                        }
                                    },
                                    {
                                        type: 'li',
                                        key: null,
                                        ref: null,
                                        props: {
                                                children: '2'
                                        }
                                    },
                                    {
                                        type: 'li',
                                        key: null,
                                        ref: null,
                                        props: {
                                               children: '3'
                                        }
                                    }
                                ]
                    }
        }
    }
}

到這裡就已經完成第一個和第二個小目標

不過在這裡要多提一下,上述的樹形結構,在react15版本及以前就可以直接拿來diff以及生成頁面,不過正如第一篇文章所說,這樣會遇到很大的問題(任務過重js執行時間久,影響渲染)。

所以16之後做的事情,就是依據上述的樹形結構進行重構,重構出來的fiber數據結構用於滿足異步渲染之需

二、雙緩存技術

上篇文章中已經介紹瞭fiber節點的數據結構,這裡我們再介紹下fiberRoot以及rootFiber。 fiberRoot源碼位置

FiberRoot數據結構:

class FiberRootNode {
  current: FiberNode;
  container: any | null;
  finishedWork: FiberNode | null;
  pendingLanes: Lanes;
  finishedLane: Lane;
  pendingPassiveEffects: PendingPassiveEffects;
  constructor(container: any | null, hostRootFiber: FiberNode) {
    this.current = hostRootFiber;
    this.container = container;
    hostRootFiber.stateNode = this;
    this.finishedWork = null;
    this.pendingLanes = NoLanes;
    this.finishedLane = NoLane;
    this.pendingPassiveEffects = {
      unmount: [],
      update: []
    };
  }
}

其中很多屬性我們暫時無視,後續涉及到的時候會詳細講解,這裡重點關註節點的關系。 rootFiber的數據結構和普通的FiberNode節點區別不大,這裡不再贅述~

整個React應用有且隻有一個fiberRoot

整個應用中同時存在兩棵rootFiber樹

當前頁面對應的稱為currentFiber,另外一顆在內存中構建的稱為workInProgressFiber,它們通過alternate屬性連接。

fiberRoot中的current指針指向瞭currentFiber樹。

當整個應用更新完成,fiberRoot會修改current指針指向內存中構建好的workInProgressFiber。

圖形描述如下:

以上我們稱之為雙緩存技術,當然這個技術不光用在react中,其他很多地方都有涉及,大傢感興趣的話自行Google。

三、React初始化的執行函數

在mount階段的時候,應用是需要一個執行函數的,而這個函數就是(源碼位置)

    react.createRoot(root).render(<App/>)
  • root: 模版文件中的id為root的div
  • <App>: 整個應用的根組件

源碼簡化後的代碼如下:

    const createRoot = (container: Container) => {
            const root = createContainer(container);
            return {
                render(element: ReactElementType) {
                        return updateContainer(element, root);
                }
            };
    };

createRoot會返回一個對象,其中包含瞭render函數,我們具體看看createContainer做瞭哪些事情。

const createContainer = (container: Container) => {
    // 創建rootFiber
    const hostRootFiber = new FiberNode(HostRoot, {}, null);
    // 創建fiberRoot
    const root = new FiberRootNode(container, hostRootFiber);
    hostRootFiber.updateQueue = createUpdateQueue();
    return root;
};

react.createRoot()在內部會去創建整個應用唯一的fiberRoot和rootFiber,並進行關聯。(如上述圖形結構)

render內部執行的是updateContainer(),我們查看下內部實現:

const updateContainer = (
	element: ReactElementType,
	root: FiberRootNode
) => {
	// mount時
	const hostRootFiber = root.current;
	// 添加update任務
	const lane = requestUpdateLane();
	const update = createUpdate<ReactElementType | null>(element, lane);
	enqueueUpdate(
		hostRootFiber?.updateQueue as UpdateQueue<ReactElementType | null>,
		update
	);
	scheduleUpdateOnFiber(hostRootFiber, lane);
	return element;
};

其中有很多地方我們此時無須關心,但是我們看到內部調用瞭scheduleUpdateOnFiber, 而這個就是更新流程(schedule(調度)->reconciler(協調)->commit (渲染))的入口。

而這個入口不僅僅在初始化執行函數中render調用會喚起,還有其他的方式:

  • 類組件中setState -> scheduleUpdateOnFiber()
  • 函數組件useState -> scheduleUpdateOnFiber()

至此,我們知道瞭開發代碼->編譯->觸發更新流程的鏈路。

ok,以上就是文章的所有內容瞭,我們總結下:

  • react組件會被編譯成react.createElement的調用,而調用結果是一顆樹形結構。
  • react16之後會重構增強這顆樹,變成fiber結構。
  • 在react應用中,使用瞭雙緩存技術,用於更新。
  • mount階段的執行函數(createRoot().render)會創建fiberRoot並且喚起更新流程。
  • 更新流程的喚起還有setState、useState等方式。

此篇文章完成瞭5個小目標,相信大傢對整體的鏈路會更加清晰,後續的文章,會進一步深入到具體實現當中,敬請期待~

以上就是漸進式源碼解析React更新流程驅動的詳細內容,更多關於React更新流程驅動的資料請關註WalkonNet其它相關文章!

推薦閱讀: