mini-react 實現原理講解 第三講

文章首發於個人博客 https://github.com/mcuking/bl...

相關代碼請查閱 https://github.com/mcuking/bl...node

上一講中 state 發生變化時,將會從新渲染,使用新生成的 dom 替換老的 dom。可是當 dom 樹很大時,而每次更改的地方很小時,可能只是修改某個節點的屬性,這種粗暴的替換方式就顯得很浪費性能。react

所以 react 提出了 diff 算法:vnode(純 js 對象) 表明 dom, 在渲染以前,先比較出 oldvnode 和 newvode 的區別。而後增量的更新 dom。git

如何增量更新呢?github

複用 DOM

在第一講中,render 函數裏對於每個斷定爲 dom 類型的 VDOM,都是直接建立一個新的 DOM:算法

...
else if(typeof vnode.nodeName == "string") {
    dom = document.createElement(vnode.nodeName)
    ...
}
...

必定要建立一個 新的 DOM 結構嗎?瀏覽器

考慮以下狀況:
如一個組件, 初次渲染爲 renderBefore, 調用 setState 再次渲染爲 renderAfter 調用 setState 再再次渲染爲 renderAfterAfter。 VNODE 以下:dom

const renderBefore = {
    tagName: 'div',
    props: {
        width: '20px',
        className: 'xx'
    },
    children:[vnode1, vnode2, vnode3]
}
const renderAfter = {
    tagName: 'div',
    props: {
        width: '30px',
        title: 'yy'
    },
    children:[vnode1, vnode2]
}
const renderAfterAfter = {
    tagName: 'span',
    props: {
        className: 'xx'
    },
    children:[vnode1, vnode2, vnode3]
}

renderBefore 和 renderAfter 都是 div,只不過 props 和 children 有部分區別,那咱們是否是能夠經過修改 DOM 屬性,修改 DOM 子節點,把 rederBefore 變化爲 renderAfter 呢?函數

而 renderAfter 和 renderAfterAfter 屬於不一樣的 DOM 類型, 瀏覽器還沒提供修改 DOM 類型的 Api,是沒法複用的,所以必定要建立新的 DOM 的。性能

因此 diff 機制以下:spa

  • 不一樣元素類型沒法複用
  • 相同元素:

    • 更新屬性
    • 複用子節點

所以代碼大體以下:

...
else if(typeof vnode.nodeName == "string") {
    if(!olddom || olddom.nodeName != vnode.nodeName.toUpperCase()) {
        createNewDom(vnode, parent, comp, olddom)
    } else {
        diffDOM(vnode, parent, comp, olddom) // 包括 更新屬性, 子節點複用
    }
}
...

在後面的兩講中我將分別介紹更新屬性以及複用子節點這兩種機制。

相關文章

相關文章
相關標籤/搜索