摘要
OK,經過上一篇文章。我們調用了:
const root = document.querySelector('#root');
ReactDOM.createRoot(root)
生成了FilberRootNode和HostRootFilber。
并且二者之間的對應關系也已經確定。
而下一步我們就需要調用render方法來講react元素掛載在root上:
//第一節實現的jsx方法
const reactElement = jsx("div", {ref: "123",children: jsx("span", {children: "456"})
});
ReactDOM.createRoot(root).render(reactElement)
所以我們現在要實現ReactDOM.createRoot中返回的render方法。
1.update和updateQueue
首先我們思考一下,在React中,除了通過上面的render方法,會讓React組件更新。還有setState等邏輯,也可以觸發React的更新。
也就是說,我們要實現一個方法。在觸發React組件更新時調用:updateContainer。
在實現updateContainer方法前,我們先實現一套機制,用來保存更新的內容。這里可以創建一個update.js用來寫這部分內容:
//創建一個更新內容
function createUpdate(action) {return {action}
}//給FilberNode創建一個更新隊列
function createUpdateQueue() {return {shared: {pending: null}}
}//在更新隊列里添加更新內容
function enqueueUpdate(updateQueue, update) {updateQueue.shared.pending = update
}//根據更新的內容,去更新FilberNode(this.setState)
function processUpdateQueue(baseState, pendingUpdate) {const result = {memoizedState: baseState}if(pendingUpdate !== null){const action = pendingUpdate.action;//setState(() => {}) 傳入方法if(typeof action === 'function'){result.memoizedState = action(baseState);}else {//setState()result.memoizedState = action;}}return result;
}
這個時候,我們的更新相關的方法已經準備好了。現在就要開始干了。首先要在FilberNode上增加一個屬性,updateQueue用來保存更新的內容:
this.updateQueue = null;
2.updateContainer方法
現在我們開始實現updateContainer方法,該方法接受兩個參數,第一個是通過createContainer方法創建出來的FilberRootNode,第二個就是render方法傳入的ReactElement。
function createRoot(root) {const filberRootNode = createContainer(root);return {render(element) {}}
}function updateContainer(root, element) {}
在方法里,我們思考一下,如果不考慮setState的情況。第一次渲染的時候,對于最外層的FilberNode,他需要更新的內容,不就是傳入的element嗎。
所以我們通過root.current拿到最外層的FilberNode。執行對應的更新操作:
function createRoot(root) {const filberRootNode = createContainer(root);return {render(element) {updateContainer(filberRootNode, element)}}
}function updateContainer(root, element) {const hostRootFilber = root.current;const update = createUpdate(element);hostRootFilber.updateQueue = createUpdateQueue()enqueueUpdate(hostRootFilber.updateQueue, update);console.log(hostRootFilber)
}
我們將對應的節點打印出來看一下,最外層的FilberNode此時已經有了updateQueue,并且里面的內容就是對應的ReactElement
3.實現beginWork
OK,現在我們最外層的FilberNode已經準備好,我們開始準備構建Filber樹。其實構建Filber樹的過程,就是創建好所有的FilberNode,并且通過return,sibling,child這三個屬性進行構建。
而表示整棵樹的結構,都存在updateQueue中的ReactElement,我們就是要通過它去創建這顆Filber樹。
現在我們不考慮有sibling屬性的情況,只考慮有return和child屬性的情況,創建beginWork方法:
function beginWork(nowFilberNode) {}function updateContainer(root, element) {const hostRootFilber = root.current;const update = createUpdate(element);hostRootFilber.updateQueue = createUpdateQueue()enqueueUpdate(hostRootFilber.updateQueue, update);beginWork(hostRootFilber);
}
我們主要要做的就是,在beginWork里面,創建好所有的fiberNode。并且找清楚他們之間的對應關系。所以我們的beginWork一定是一個遞歸的方法:
我們會判斷當前filberNode的tag:
function beginWork(nowFilberNode) {switch (nowFilberNode) {case HostRoot: {return updateHostRoot(nowFilberNode); }case HostComponent: {}case HostText: {}case FunctionComponent: {}default: {console.error('錯誤的類型')}}
}
由于第一次調用,傳入的是最外面的filberNode,所以tag應該為HostRoot。我們針對于這種情況寫一個方法updateHostRoot。
function updateHostRoot(filberNode) {const baseState = filberNode.memoizedState;const updateQueue = filberNode.updateQueue;const pending = updateQueue.shared.pending;updateQueue.shared.pending = null;const { memoizedState } = processUpdateQueue(baseState, pending);filberNode.memoizedProps = memoizedState;const nextChildren = filberNode.memoizedProps;console.log(nextChildren);console.log(filberNode);
}
對于首屏渲染,我們知道對于FilberNode來說,更新的內容就是他的子節點。所以我們更新好FilebrNode的updateQueue屬性和memoizedState,memoizedProps屬性后。
可以直接拿到它的子節點nextChildren,不過這個節點是ReactElement類型,我們要將它轉換成FilberNode,所以我們還需要一個方法:reconcilerChildren。
function reconcileChildren(element) {let tag;if(element.$$typeof === REACT_ELEMENT_TYPE){tag = HostComponent;}else if(typeof element === 'string'){}return new FilberNode(tag, element.props, element.key)}
我們創建好的子FilberNode,用element的props初始化FilberNode的penddingProps。
這個時候我們在updateHostRoot中調用該方法,并將子FilberNode打印出來。
function updateHostRoot(filberNode) {/*** 其他代碼**/const newFilberNode = reconcileChildren(nextChildren);filberNode.child = newFilberNode;newFilberNode.return = filberNodeconsole.log(newFilberNode);beginWork(newFilberNode)
}
可以看到子FilberNode和父節點的關系已經更新好,同時也將自己的ReactElement放在了pendingProps里。
Ok,對于HostRoot類型(最外層的FilberNode),我們有了updateHostRoot方法處理,那對于HostComponent類型,我們自然也需要updateHostComponent方法:
function updateHostComponent(filberNode) {const nextChildren = filberNode.pendingProps.children;const newFilberNode = reconcileChildren(nextChildren);filberNode.child = newFilberNode;newFilberNode.return = filberNode;beginWork(newFilberNode)
}
同樣的,對于文本類型的節點,自然也不需要去給它創建FilberNode。這里面我們不做處理就好。
到這里,我們就已經簡單的處理了只有child和return屬性的Filber樹,最終也可以打印出來這顆樹的樣子:
可以看到每個FilberNode都具有child和return兩個屬性。