您当前的位置: 首页 > 

【03】

暂无认证

  • 3浏览

    0关注

    196博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

实现vue的diff更新dom

【03】 发布时间:2020-12-05 20:56:38 ,浏览量:3

项目结构

public/index.html




    
    Title





src/index.js

import {createElement, render, renderDom} from './virtualDom'
import domDiff from './domDiff'
import doPatch from './doPatch'
const vDomOld = createElement('ul', {
    class: 'list1',
    style: 'width: 300px;height: 300px; background-color: orange',
}, [
    createElement('li', {
        class: 'item',
        'data-index': 0
    }, [
        createElement('p', {class: 'text'}, ['第1个列表项'])
    ]),
    createElement('li', {
        class: 'item',
        'data-index': 1
    }, [
        createElement('p', {class: 'text'}, ['第2个列表项'])
    ]),
    createElement('li', {
        class: 'item',
        'data-index': 2
    }, [
        createElement('p', {class: 'text'}, ['第3个列表项'])
    ]),
    createElement('li', {
        class: 'item',
        'data-index': 3
    }, [
        createElement('p', {class: 'text'}, ['第4个列表项'])
    ])
    ]
)
const rDom = render(vDomOld);
// 渲染dom
renderDom(rDom,document.getElementById('app'));
console.log(vDomOld)
console.log(rDom)


const vDomNew = createElement('ul', {
        class: 'list2',
        style: 'width: 300px;height: 300px; background-color: orange',
    }, [
        createElement('li', {
            class: 'item',
            'data-index': 0
        }, [
            createElement('p', {class: 'text'}, ['第1个列表项'])
        ]),
        createElement('li', {
            class: 'item',
            'data-index': 1
        }, [
            createElement('p', {class: 'text'}, ['第2个列表项'])
        ]),
        createElement('li', {
            class: 'item',
            'data-index': 2
        }, [
            createElement('p', {class: 'text'}, ['第3个列表项'])
        ]),
        createElement('li', {
            class: 'item',
            'data-index': 3
        }, [
            createElement('h1', {class: 'text'}, ['零三的笔记','https://web03.cn'])
        ])
    ]
)
// diff计算差异
const patches = domDiff(vDomOld,vDomNew)
console.log(patches)

// 将计算出来的差异更新到dom中
doPatch(rDom, patches)

vDomOld是原有dom,vDomNew是新的虚拟dom

src/virtualDom.js

import Element from "./Element";
function createElement(type, props, children) {
    return new Element(type,props,children)
}

/**
 * 考虑input以及其他组件
 */
function setAttrs(node,prop,value) {
    switch (prop) {
        case 'value':
            if (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA'){
                node.value = value
            } else {
                node.setAttribute(prop, value)
            }
            break
        case 'style':
            node.style.cssText = value
            break
        default:
            node.setAttribute(prop,value)
    }
}
//将虚拟dom转换成真实dom
function render(vDom) {
    const {type, props, children} = vDom,
        el = document.createElement(type);
    // 处理父一级
    for (let key in props) {
        setAttrs(el,key, props[key])
    }
    // 处理子节点
    children.map(cEl => {
        if (cEl instanceof Element){
            cEl = render(cEl)
        } else {
            cEl = document.createTextNode(cEl)
        }
        el.appendChild(cEl)
    })
    return el;
}
// 挂载dom
function renderDom(rDom, rooTel) {
    rooTel.appendChild(rDom)
}
export {
    createElement,
    render,
    renderDom,
    setAttrs
}

src/pathType.js

const ATTR = 'ATTR',//更改attr 自定义属性
    TEXT = 'TEXT',//更改文本
    REPLACE = 'REPLACE',//替换节点
    REMOVE = 'REMOVE';//删除节点
export {
    ATTR,
    TEXT,
    REPLACE,
    REMOVE
}

src/domDiff.js

import {
    ATTR,
    TEXT,
    REPLACE,
    REMOVE
} from './patchTypes'
// 存储dom差异
let patches = {},
    vnIndex = 0;
function domDiff(oldVDom, newVDom) {
    let index = 0;
    vNodeWalk(oldVDom,newVDom,index)
    return patches;
}
function vNodeWalk(oldNode, newNode, index) {
    let vnPatch = [];
    if (!newNode){
        // 被删除
        vnPatch.push({
            type: REMOVE,
            index
        })
    } else if (typeof oldNode === 'string' && typeof newNode === 'string'){
        //两个节点都是文本节点 比较文本内容
        if (oldNode !== newNode){
            // 更新
            vnPatch.push({
                type: TEXT,
                text: newNode
            })
        }
    } else if (oldNode.type === newNode.type){
        // 组件名一样
        // 对比props
        const attrPath = attrsWalk(oldNode.props, newNode.props);
        // 有差异
        if (Object.keys(attrPath).length > 0){
            vnPatch.push({
                type: ATTR,
                attrs: attrPath
            })
        }
        childrenWalk(oldNode.children, newNode.children)
    } else {
        // 替换
        vnPatch.push({
            type: REPLACE,
            newNode
        })
    }
    if (vnPatch.length > 0){
        patches[index] = vnPatch;
    }
}
// 对比属性异同
function attrsWalk(oldAttrs, newAttrs) {
    let attrPath = {};
    for (let key in oldAttrs) {
        // 修改
        if (oldAttrs[key] !== newAttrs[key]){
            attrPath[key] = newAttrs[key]
        }
    }
    for (let key in newAttrs) {
        // 添加
        if (!oldAttrs.hasOwnProperty(key)){
            attrPath[key] = newAttrs[key]
        }
    }
    return attrPath;
}
// 对比儿子异同
function childrenWalk(oldChildren, newChildren) {
    oldChildren.map((children, index) => {
        vNodeWalk(children,newChildren[index], ++vnIndex)
    })
}
export default domDiff;

src/Element.js

class Element {
    constructor(type, props, children) {
        this.type = type;
        this.props = props;
        this.children = children;
    }
}
export default Element;

src/doPath.js

import {
    ATTR,
    TEXT,
    REPLACE,
    REMOVE
} from './patchTypes';
import {setAttrs, render} from './virtualDom';
import Element from "./Element";
let finalPatches = {},//储存patches
    rnIndex = 0;//真实节点index
function doPatch(rDom, patches) {
    finalPatches = patches;
    rNodeWalk(rDom)
}
// 处理真实节点
function rNodeWalk(rNode) {
    const rnPath = finalPatches[rnIndex++],
        childNodes = rNode.childNodes;
    //子节点为类数组
    [...childNodes].map((childrenNode)=>{
        rNodeWalk(childrenNode)
    })
    // 判断是否需要更新节点
    if (rnPath){
        patchAction(rNode, rnPath);//当前节点所对应的需要更新的内容
    }
}
function patchAction(rNode, rnPath) {
    rnPath.map(path => {
        switch (path.type) {
            case ATTR:
                for(let key in path.attrs){
                    const value = path.attrs[key];
                    // 之前有的,现在有的->添加更新
                    if (value){
                        setAttrs(rNode, key, value)
                    }else {
                        // 之前有的,现在没了 ->删除
                        rNode.removeAttribute(key)
                    }
                }
                break
            case TEXT:
                // 设置节点文本内容
                rNode.textContent = path.text
                break
            case REPLACE:
                // 判断是否被Element构造出来的,不是那会是文本节点,是的话直接给render转换成真实节点
                const newNode = (path.newNode instanceof Element)
                    ? render(path.newNode)
                    : document.createTextNode(path.newNode);
                // 替换真实节点
                rNode.parentNode.replaceChild(newNode, rNode);
                break
            case REMOVE:
                // 找到父亲杀儿子
                rNode.parentNode.removeChild(rNode);
                break
            default:
                break
        }
    })
}
export default doPatch;

计算结果

以上代码思想已经实现了最最基本的dom更新,仅供参考学习

流程图

关注
打赏
1657344724
查看更多评论
立即登录/注册

微信扫码登录

0.0382s