摘要
經過之前的幾篇文章,我們已經實現了一個可以進行更新渲染的假React。但是如果我們把我們的jsx修改成這樣:
function App() {const [age, setAge] = useState(20)const click = function() {setAge(age + 1)}return age % 2 === 0 ? jsx("div", {key: 'div1',children: jsx("span", {key: 'span',children: 'div1',onClick: click})}) : jsx("div", {key: 'div1',children: jsx("span", {key: 'span',children: 'div2',onClick: click})})
}ReactDOM.createRoot(root).render(<App />)
雖然頁面的效果是正確的。但對于兩個jsx,二者的區別僅僅是span標簽里面的內容不同。
但是在程序里面,我們是相當于每次都重新beginWork,重新的創建Filber樹,重新的創建真實DOM。
而對于這里div和span標簽,它沒有任何的改變,我們是否可以用一種優化策略,從而對舊資源進行利用呢? 所以Diff由此而來。
這一篇先只說單節點的Diff,因為目前還沒實現帶有sibling的情況。
1.修改beginWork
我們回顧一下在beginWork里面干了什么。在將jsx轉換為ReactElement后,我們會通過beginWork來構建一顆Filber樹。
那如果,對于可復用的FilberNode,我們是否可以不去創建,直接復用呢?
可以的,那對于React來說,什么節點是可復用的呢:
如果舊的FilberNode和新的ReactElement
key相同,type相同。
那么就是可以復用的。
所以在beginWork中的reconcileChildren方法里,如果我們發現上面的情況,我們就不會創建新的FilberNode。
function reconcileChildren(parent,element) {const newChild = diffReconcileChildren(parent, element);if(newChild) {return newChild}//其他代碼。。。return filberNode
}
2.實現diffReconcileChildren方法
該方法接受兩個參數,第一個是父節點,第二個是新的ReactElement。
(1)我們要先拿到父節點的child,比較child和element的key和type。
(2)將element保存在child的memoizedState里面。
(3)然后其他邏輯和reconcileChildren里的一樣即可。
(4)直接返回child。
function diffReconcileChildren(filberNode, element) {const child = filberNode.child;if(child && child.key && child.type) {if(child.key === element.key && child.type === child.type) {child.memoizedProps = element;child.pendingProps = element.props;if(child.tag === HostText) {child.pendingProps = element}return child;}}
}
這里為什么要將element保存在memoizedState里面,是因為雖然節點沒有改變,但是子節點可能有改變,或者屬性會有改變。所以要在后面的completeWork里進行處理。
3.修改completeWork
經過上面的處理后,FilberNode不會無腦的重復創建,而是復用了。而completeWork的工作,主要是創建真實DOM,掛載在FilberNode的stateNode上。
所以在completeWork中,也不能無腦的創建真實DOM,而是也要判斷是否是可復用的。
export const completeWork = (filberNode) => {const tag = filberNode.tagswitch (tag) {case HostComponent: {if(filberNode.stateNode !== null){//更新updateCompleteHostComponent(filberNode)}else{completeHostComponent(filberNode)}break;}//其他代碼。。。。}
}
所以在對HostComponent的處理上,如果發現不是mount階段,就要判斷是否需要復用舊的。
4.實現updateCompleteHostComponent方法
在該方法中,我們接受filberNode。同時我們可以拿到它的memoizedState(在beginWork中傳過來的)。
再判斷一下key和type,如果依舊相同,那么說明是可復用的。我們直接不創建新的DOM即可。
function updateCompleteHostComponent(filberNode) {const element = filberNode.memoizedProps;if(element.key === filberNode.key && element.type === filberNode.type) {addPropsToDOM(filberNode.stateNode, filberNode.pendingProps)}else{const type = filberNode.type;const element = document.createElement(type);addPropsToDOM(element, filberNode.pendingProps)filberNode.stateNode = element;const parent = filberNode.return;if(parent && parent.stateNode && parent.tag === HostComponent) {parent.stateNode.appendChild(element)}}completeWork(filberNode.child)
}
addPropsToDOM方法是我們在實現事件機制的時候,需要調用的方法。
OK,這樣我們就實現了單節點的Diff算法。