Vue3渲染器的实现(局部)

105 阅读1分钟

渲染系统局部模拟实现

const h = (tag, props, children) => {
    // vnode => JavaScript对象 -> {}
    return {
        tag,
        props,
        children
    }
}

const mounted = (vnode, container) => {
    // 1.创建出真实的原生,并且在vnode上保留el
    const el = vnode.el = document.createElement(vnode.tag)
    // console.log('render', el, vnode.el)
    // 2.处理props
    if (vnode.props) {
        for (const key in vnode.props) {
            const value = vnode.props[key]
            if (key.startsWith('on')) {
                el.addEventListener(key.slice(2).toLowerCase(), value)
            } else {
                el.setAttribute(key, value)
            }
        }
    }
    // 3.处理children
    if (vnode.children) {
        if (typeof vnode.children == 'string') {
            el.textContent = vnode.children
        } else {
            vnode.children.forEach(item => {
                mounted(item, el)
            });
        }
    }
    // 4.将el挂载到container上
    container.appendChild(el)
}
const patch = (n1, n2) => {
    if (n1.tag !== n2.tag) {
        // 拿到当前元素 n1.el
       const n1ElParent =  n1.el.parentElement
       n1ElParent.removeChild(n1.el)
       mount(n2, n1ElParent)
    } else {
        // 1.取出element对象,并且在n2中进行保存
        const el = n2.el = n1.el
        // console.log('patch-el', 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) {
                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.startsWith('on')) {
                const value = oldProps[key]
                el.removeEventListener(key.slice(2).toLowerCase(), value)
            }
            if (!key in newProps) {
                el.removeAttribute(key)
            }
        }
        // 3.处理children
        const oldChildren = n1.children || []
        const newChildren = n2.children || []
        // 情况1:newChildren本身是一个string类型
        if (typeof newChildren === 'string') {
            // el.innerHTML = newChildren // 还可以做更细的判断:前后的字符串是都一样,一样则无需更改
            // 边界情况
            if(typeof oldChildren === 'string') {
                if (newChildren !== oldChildren) {
                    // console.log('el', el)
                    el.textContent = newChildren
                }
            } else {
                el.innerHTML = newChildren
            }
        } else {
            // 情况2:newChildren本身就是一个数组
            if(typeof oldChildren === 'string') {
                el.innerHTML = ''
                newChildren.forEach(item => {
                    mount(item, el)
                })
            } else {
                /***
                 * 例如
                 * oldChildren [v1, v2, v3]
                 * newChildren [v1, v5, v6, v8, v9]
                 */
                /**
                 * 拿到两个数组中更短的
                 * 如果newLength < oldLength 则把旧的多出来的删除
                 * 如果newLength > oldLength 则把新的多出来的直接增加
                 */
                const commonLength = Math.min(oldChildren.length, newChildren.length)
                // 1.前面有 相同节点 的原生进行patch操作
                for (let i = 0; i < commonLength; i++) {
                    patch(oldChildren[i], newChildren[i])
                }
                // 2.newLength > oldLength
                if (newChildren.length > oldChildren.length) {
                    newChildren.slice(oldChildren.length).forEach(item => {
                        // 挂载
                        mount(item, el)
                    })
                }
                // 2.newLength < oldLength
                if (newChildren.length < oldChildren.length) {
                    oldChildren.slice(newChildren.length).forEach(item => {
                        // 移除多余的内容(卸载unmount)
                        el.removeChild(item.el)
                    })
                }
            }
        }
    }
}