美文网首页
React 虚拟DOM

React 虚拟DOM

作者: 飞飞廉 | 来源:发表于2018-07-22 19:30 被阅读0次

    virtul DOM 也就是虚拟节点。通过JS的Object对象模拟DOM中的真实节点对象,再通过特定的render方法将其渲染成真实的DOM节点。

    一、渲染步骤

    生成vNode---->渲染成真实节点 --------->挂载到页面--------->diff比较
    1、模拟方法和渲染方法
    需求,生成一个如下图所示的DOM结构


    image.png

    调用

    let virtualDom1 = createElement('ul', {class: 'list'}, [
        createElement('li', {class: 'item'}, ['a']),
        createElement('li', {class: 'item'}, ['b']),
        createElement('li', {class: 'item'}, ['c']),
    ])
    let virtualDom2 = createElement('ul', {class: 'list'}, [
        createElement('li', {class: 'item'}, ['1']),
        createElement('li', {class: 'item'}, ['2']),
        createElement('li', {class: 'item'}, ['3']),
    ])
    let el = render(virtualDom);
    renderDom(el, window.root);
    let patchs = diff(virtualDom1, virtualDom2);
    

    生成虚拟对象的方法createElement

    function createElement(type, props, children) {
        return new Element(type, props, children)
    }
    class Element{
        constructor(type, props, children){
            this.type = type;
            this.props = props;
            this.children = children
        }
    }
    

    将虚拟对象渲染成真实DOM的render方法

    //render方法将vNode转化成真实DOM
    function render(eleObj){
        //创建元素
        let el = document.createElement(eleObj.type);
        //设置属性
        for(let key in eleObj.props) {
            setAttr(el, key, eleObj.props[key]);
        }
        //递归渲染子元素
        eleObj.children.foEach(child => {
            child = child instanceof Element ? render(child) : document.createTextNode(child);
            el.appendChild(child);
        })
    }
    setAttr(node, key, value) {
        switch(key) {
            case 'value':
                if (node.tagName.toUpperCase() === 'INPUT' || node.tagName.toUpperCase() === 'TEXTAREA') {
                    node.value = value;          
                }else {
                    node.setAttribute(key, value);
                }
                break;
            case 'style':
                node.style.cssText = value;
                break;
            default:
                node.setAttribute(key, value);
                break;
        }
    }
    

    渲染节点到页面的方法renderDom

    //将真实DOM渲染到页面
    function renderDom(el, target) {
        target.appendChild(el);
    }
    

    二、DOM DIFF 算法

    1、核心思想

    DOM DIFF 就是比较两个虚拟DOM的区别,实际上就是比较两个对象的区别。根据两个虚拟对象创建出补丁,描述改变的内容。将这个补丁用来更新DOM。


    image.png

    【注意】不会更改所有节点,只更改有改变的部分

    2、DOM DIFF 两种优化策略

    1)分层比较,一层一层比,不会跨级对比
    2)如果一层的对象只是换了下位置,可以通过key值直接换位置。

    2、算法实现

    差异计算:先序深度优先遍历


    image.png

    规则:
    1、若节点类型不相同,直接采用替换模式,{type:'REPLACE',newNode:newNode}
    2、当节点类型相同时,去看一下属性是否相同,产生一个属性的补丁包,比如{type:'ATTRS',attrs:{class: 'list-group'}
    3、新的DOM节点不存在,也返回一个不存在的补丁包{type:'REMOVE',index:XXX}
    4、文本的变化{type:'TEXT', text:1}

    DIff 算法
    //diff 算法
    let Index = 0;
    function diff(oldTree, newTree) {
        let patches = {};
        let index = 0;
        //递归数比较后的结果放到补丁包中
        walk(oldTree, newTree, index, patches);
        return patches;
    }
    function walk(oldTree, newTree, index, patches){
        let currentPatch = [];//每个元素都有一个补丁对象
        if (!newTree) {
            currentPatch.push({type:'REMOVE', index})
        } 
        if (isString(oldTree) && isString(newTree)) {
            // 判断文本是否一致
            if (oldTree !== newTree) {
                currentPatch.push({type:'TEXT',text:newTree}); 
            }
        }else if(oldTree.type === newTree.type) {
            //比较属性是否有更改
            let attrs = diffAttr(oldTree.props, newTree.props);
            if(Object.keys(attrs).length) {
                currentPatch.push({type:'ATTRS', attrs});
            }
            // 如果有儿子节点,遍历子节点
              diffChildren(oldTree.children, newTree.children, index, patches);
        } else {
            // 节点类型不同的时候,直接替换
            currentPatch.push({type:'REPLACE', newTree});
        }
        // 当前元素有补丁的情况下,将元素和补丁对应起来,放到大补丁包中
        if(currentPatch.length) {
            patches[index] = currentPatch; 
        }
    }
    function diffAttr(oldAttrs, newAttrs) {
        let patch = {};
        for(let key in oldAttrs) {
            if(oldAttrs[key] !== newAttrs[key]) {
                patch[key] = newAttrs[key];//有可能是undefined,新节点没有旧节点的属性      
            }
        }
        for(let key in newAttrs) {
            //老节点没有新节点的属性
            if(! oldAttrs.hasOwnProperty(key)) {
                patch[key] = newAttrs[key]
            }
        }
        return patch;
    }
    
    function diffChildren(oldChildren, newChildren, index, patches){
        // 比较老的第一个和新的第一个
        oldChildren.forEach((child, idx) => {
            // 记得索引得改
            // Index 每次传递给walk时,index是递增的,所有节点都基于一个序号实现,因此需要维护一个全局Index
            walk(child, newChildren[idx], ++Index, patches);
        }) 
    }
    
    
    function isString(node) {
        return Object.prototype.toString.call(node) === '[object string]';
    }
    
    
    function patch(node, patches) {
     // 给某个元素打补丁
     
    }
    

    相关文章

      网友评论

          本文标题:React 虚拟DOM

          本文链接:https://www.haomeiwen.com/subject/atpppftx.html