vue3中VNode渲染節點的實現


vue的源碼包含三大核心:

Compiler模塊:編譯模板系統

Runtime模塊:也可以稱之為Renderer模塊,真正的渲染的模塊

Reactivity模塊:響應式系統

 三個系統之間如何協同工作呢?

 

 

 實現一個Mini-Vue

包含三個模塊:

渲染系統模塊

可響應式系統模塊

應用程序入口模塊

渲染系統的實現

該模塊主要包含三個功能:

功能一:h函數,用於返回一個VNode對象;

功能二:mount函數,用於將VNode掛載到DOM

功能三:patch函數,用於對兩個VNode進行對比,決定如何處理新的VNode

 

第一步,創建一個renderer.js文件,定義一個h函數

const h = (tag, props, children) => {
    // vnode就是一個JavaScript對象
    return {
        tag,
        props,
        children
    }
}

在html文件中,引入文件,並創建一個虛擬節點,可以輸出打印一下這個vnode

   <div id="app"></div>
        <script src="./renderer.js"></script>
        <script>
            // 1、 通過h函數來創建一個vnode
            const vnode = h('div', {
                class: 'vnode'
            }, [
                h('h2', null, '當前計數:100'),
                h('button', null, "+1")
            ])
            console.log(vnode)
        </script>

 

 第二步,實現掛載功能

在renderer.js文件中定義mount方法

const mount = (vnode, container) => {
    //1、 將vnode變為elemnt,創建出真實的dom,並且在vnode上保存一份el
    const el = vnode.el = document.createElement(vnode.tag)
    //    2、處理props
    if (vnode.props) {
        for (const key in vnode.props) {
            const value = vnode.props[key]
            // 判斷傳遞過來的是否是方法,比如onClick
            if (key.startsWith("on")) {
                el.addEventListener(key.slice(2).toLowerCase(), value)
            }
            // 設置屬性
            el.setAttribute(key, value)
        }
    }
    // 3、處理children
    if (vnode.children) {
        // 如果子節點存在並且子節點是字符串,說明是其中的內容
        if (typeof vnode.children === 'string') {
            // 將內容放進去
            el.textContent = vnode.children
        } else {
            // 說明子節點中是一個數組,其內部還有子節點
            vnode.children.forEach((item) => {
                // 再次調用掛載到el上
                mount(item,el)
            })
        }
    }
    // 4、將el掛載到container上
    container.appendChild(el)
}

在html文件中調用該mount方法

// 2、通過mount函數,將vnode掛載到#app上
            mount(vnode,document.getElementById('app'))

再次刷新頁面的時候就可以看到界面已經加載出來了vnode

 

 第三步實現diff算法

第一種情況:節點不相同

新建一個vnode

 // 3、創建一個新的vnode
            const vnode1 = h('h2', {
                class: 'vnode'
            }, 'jerry')

將新的vnode替換舊的vnode,兩個vnode之間進行一個diff算法,根據diff算法找到需要修改真實dom的那個地方,找到之后在進行修改

在renderer.js文件中定義一個patch方法

const patch=(n1,n2)=>{
    // 判斷兩個vnode的類型是否一樣,比如說n1為div,n2為h2
    if(n1.tag!==n2.tag){
        // 拿到n1節點的父元素
        const n1ElementParent=n1.el.parentElement;
        // 移除n1節點
        n1ElementParent.removeChild(n1.el)
        // 將n2節點添加上去
        mount(n2,n1ElementParent)
    }else{

    }
}

在html文件中使用patch方法

patch(vnode,vnode1)

再次刷新頁面可以看到已經替換

 

 第二種情況:節點相同,類名不同

patch方法

const patch = (n1, n2) => {
    // 判斷兩個vnode的類型是否一樣,比如說n1為div,n2為h2
    if (n1.tag !== n2.tag) {
        // 拿到n1節點的父元素
        const n1ElementParent = n1.el.parentElement;
        // 移除n1節點
        n1ElementParent.removeChild(n1.el)
        // 將n2節點添加上去
        mount(n2, n1ElementParent)
    } else {
        // 1、拿出element對象,並在n2中保留一份
        const el = n2.el = n1.el
        // 2、處理props
        const oldProps = n1.props || {}
        const newProps = n2.props || {}
        // 2、1獲取所有的newProps添加到el中
        for (const key in newProps) {
            const oldValue = oldProps[key]
            const newValue = newProps[key]
            if (newValue !== oldValue) {
                // 判斷傳遞過來的是否是方法,比如onClick
                if (key.startsWith("on")) {
                    el.addEventListener(key.slice(2).toLowerCase(), newValue)
                } else {
                    el.setAttribute(key, newValue)
                }
            }
        }
        // 2、2刪除舊的props
        for(const key in oldProps){
            if(!(key in  newProps)){
                if (key.startsWith("on")) {
                    const value=oldProps[key]
                    el.removeEventListener(key.slice(2).toLowerCase(), value)
                } else {
                    el.removeAttribute(key)
                }
            }
        }
        // 3、處理children
    }
}

在html中新建一個節點,調用patch方法

            // 3、創建一個新的vnode
            const vnode1 = h('div', {
                class: 'jerry'
            }, 'jerry')
            patch(vnode,vnode1)

之前

 

 更新之后

 接下來處理子節點

    // 3、處理children
        const oldChildren = n1.children || [];
        const newChildren = n2.children || [];
        // 情況一:newChildren是一個string類型
        if (typeof newChildren === "string") {
            if (typeof oldChildren === "string") {
                if (newChildren !== oldChildren) {
                    el.textContent = newChildren
                }
            } else {
                el.innerHTML = newChildren;
            }
        }else{
            // 情況二:newChildren是一個數組
            if(typeof oldChildren==='string'){
                el.innerHTML=""
                newChildren.forEach(item=>{
                    mount(item,el)
                })
            }else{
                // oldChildren:[n1,n2,n3]
                // newChildren:[n1,n2,n3,n4,n5]
                // 前面有相同節點的元素進行patch操作
                const commonLength=Math.min(oldChildren.length,newChildren.length)
                for(let i=0;i<commonLength;i++){
                    patch(oldChildren[i],newChildren[i])
                }
                // 如果newChildren.length>oldChildren
                // oldChildren:[n1,n2,n3]
                // newChildren:[n1,n2,n3,n4,n5]
                if(newChildren.length>oldChildren.length){
                    newChildren.slice(oldChildren.length).forEach(item=>{
                        mount(item,el)
                    })
                }
                // 如果newChildren.length<oldChildren
                // oldChildren:[n1,n2,n3,n4,n5]
                // newChildren:[n1,n2,n3]
                if(newChildren.length<oldChildren.length){
                   oldChildren.slice(newChildren.length).forEach(item=>{
                       el.removeChild(item.el)
                   })
                }
            }
        }
    }

創建兩個不同的節點,在進行patch操作

 // 1、 通過h函數來創建一個vnode
            const vnode = h('div', {
                class: 'vnode'
            }, [
                h('h2', null, '當前計數:100'),
                h('button',{onClick:function(){}}, "+1")
            ])
            
            // 2、通過mount函數,將vnode掛載到#app上
            mount(vnode,document.getElementById('app'))
            // 3、創建一個新的vnode
            const vnode1 = h('div', {
                class: 'jerry'
            }, 'jerry')
            patch(vnode,vnode1)

此時頁面就成為:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM