RubyLouvre / anu

the React16-compat library with hooks
https://rubylouvre.github.io/anu/
Apache License 2.0
3.19k stars 320 forks source link

1.1.2的updateChildren #57

Closed RubyLouvre closed 7 years ago

RubyLouvre commented 7 years ago

1.1.2的节点排序算法,通过一次循环,同时构建三个辅助对象,actions, removeHits, fuzzyHits 第二次循环,实现所有节点排序,第三次循环,实现多余节点的移除。

function diffChildren(lastVnode, nextVnode, parentNode, context, mountQueue) {
    let lastChildren = lastVnode.vchildren,
        nextChildren = flattenChildren(nextVnode),
        nextLength = nextChildren.length,
        lastLength = lastChildren.length;
    //如果旧数组长度为零
    if (nextLength && !lastLength) {
        nextChildren.forEach(function(vnode){
            let curNode = mountVnode(null, vnode, lastVnode, context, mountQueue);
            parentNode.appendChild(curNode);
        });
        return;
    }
    let maxLength = Math.max(nextLength, lastLength),
        insertPoint = parentNode.firstChild,
        removeHits = {},
        fuzzyHits = {},
        actions = [],
        i = 0,
        hit,
        dom,
        oldDom,
        nextChild,
        lastChild;
    //第一次循环,构建移动指令(actions)与移除名单(removeHits)与命中名单(fuzzyHits)
    if(nextLength){
        actions.length = nextLength;
        while (i < maxLength) {
            nextChild = nextChildren[i];
            lastChild = lastChildren[i];
            if (nextChild && lastChild && isSameNode(lastChild, nextChild)) {
            //  如果能直接找到,命名90%的情况
                actions[i] = {
                    last: lastChild,
                    next: nextChild,
                    directive: "update"
                };
                removeHits[i] = true;
            } else {
                if (nextChild) {
                    hit = nextChild.type + (nextChild.key || "");
                    if (fuzzyHits[hit] && fuzzyHits[hit].length) {
                        var oldChild = fuzzyHits[hit].shift();
                        // 将旧的节点移动新节点的位置,向后移动
                        actions[i] = {
                            last: oldChild,
                            next: nextChild,
                            directive: "moveAfter"
                        };
                        removeHits[oldChild._i] = true;
                    }
                }
                if (lastChild) {
                //如果不相同,储存它们的key
                    lastChild._i = i;
                    hit = lastChild.type + (lastChild.key || "");
                    let hits = fuzzyHits[hit];
                    if (hits) {
                        hits.push(lastChild);
                    } else {
                        fuzzyHits[hit] = [lastChild];
                    }
                }
            }
            i++;
        }
    }
    for (let j = 0, n = actions.length; j < n; j++) {
        let action = actions[j];
        if (!action) {
            let curChild = nextChildren[j];
            hit = curChild.type + (curChild.key || "");
            if (fuzzyHits[hit] && fuzzyHits[hit].length) {
                oldChild = fuzzyHits[hit].shift();
                oldDom = oldChild._hostNode;
                parentNode.insertBefore(oldDom, insertPoint);
                dom = updateVnode(oldChild, curChild, lastVnode, context, mountQueue);
                removeHits[oldChild._i] = true;
            } else {
                //如果找不到对应的旧节点,创建一个新节点放在这里
                dom = mountVnode(null, curChild, lastVnode, context, mountQueue);
                parentNode.insertBefore(dom, insertPoint);
            }
        } else {
            oldDom = action.last._hostNode;
            if(action.action === "moveAfter"){
                parentNode.insertBefore(oldDom, insertPoint); 
            }
            dom = updateVnode(
                action.last,
                action.next,
                lastVnode,
                context,
                mountQueue
            );
        }
        insertPoint = dom.nextSibling;
    }
    //移除
    lastChildren.forEach(function(el, i) {
        if (!removeHits[i]) {
            var node = el._hostNode;
            if (node) {
                removeDOMElement(node);
            }
            disposeVnode(el);
        }
    });
}

function isSameNode(a, b) {
    if (a.type === b.type && a.key === b.key) {
        return true;
    }
}
RubyLouvre commented 7 years ago

1.1.1的节点排序算法

function updateChildren(lastVnode, nextVnode, parentNode, context, mountQueue) {
    let lastChildren = lastVnode.vchildren,
        nextChildren = flattenChildren(nextVnode), //nextVnode.props.children;
        childNodes = parentNode.childNodes,
        hashcode = {},
        hasExecutor = mountQueue.executor;
    if (nextChildren.length == 0) {
        lastChildren.forEach(function(el) {
            var node = el._hostNode;
            if (node) {
                removeDOMElement(node);
            }
            disposeVnode(el);
        });
        return;
    }

    lastChildren.forEach(function(el, i) {
        let key = el.type + (el.key || "");
        if (el._disposed) {
            return;
        }
        let list = hashcode[key];
        el._index = i;
        if (list) {
            list.push(el);
        } else {
            hashcode[key] = [el];
        }
    });
    nextChildren.forEach(function(el) {
        let key = el.type + (el.key || "");
        let list = hashcode[key];
        if (list) {
            let old = list.shift();
            if (old) {
                el.old = old;
                if (!list.length) {
                    delete hashcode[key];
                }
            }
        }
    });
    var removed = [];
    for (let i in hashcode) {
        let list = hashcode[i];
        if (Array.isArray(list)) {
            removed.push.apply(removed, list);
        }
    }
    removed.sort(function(a, b) {
        return a._index - b._index;
    });
    var queue = hasExecutor ? mountQueue : [];
    nextChildren.forEach(function(el, index) {
        let old = el.old,
            ref,
            dom;

        removeNodes(removed, true);

        if (old) {
            delete el.old;

            if (el === old && old._hostNode && !contextHasChange) {
                //cloneElement
                dom = old._hostNode;
                if (dom !== childNodes[index]) {
                    parentNode.replaceChild(dom, childNodes[index]);
                    return;
                }
            } else {
                dom = updateVnode(old, el, lastVnode, context, queue);
                if (!dom) {
                    dom = createDOMElement({ vtype: "#comment", text: "placeholder" });
                    replaceChildDeday(
                        [old, el, lastVnode, context, queue],
                        dom,
                        parentNode
                    );
                }
            }
        } else {
            dom = mountVnode(null, el, lastVnode, context, queue);
        }

        ref = childNodes[index];
        if (dom !== ref) {
            insertDOM(parentNode, dom, ref);
        }
        if (!hasExecutor && queue.length) {
            clearRefsAndMounts(queue);
        }
    });

    removeNodes(removed);
}
function removeNodes(removed, one) {
    while (removed.length) {
        let removedEl = removed.shift();
        let node = removedEl._hostNode;
        if (node) {
            removeDOMElement(node);
        }
        disposeVnode(removedEl);
        if (one) {
            break;
        }
    }
}

function replaceChildDeday(args, dom1, parentNode) {
    setTimeout(function() {
        var dom2 = updateVnode.apply(null, args);
        parentNode.replaceChild(dom2, dom1);
    });
}

function insertDOM(parentNode, dom, ref) {
    if (!dom) {
    return console.warn("元素末初始化"); // eslint-disable-line
    }

    if (!ref) {
        parentNode.appendChild(dom);
    } else {
        parentNode.insertBefore(dom, ref);
    }
}
RubyLouvre commented 7 years ago

国庆假期对1.1.2的节点排序进行大调整

function diffChildren(lastVnode, nextVnode, parentNode, context, updateQueue) {
    let lastChildren = parentNode.vchildren,
        nextChildren = flattenChildren(nextVnode),
        nextLength = nextChildren.length,
        lastLength = lastChildren.length,
        dom;

    //如果旧数组长度为零, 直接添加
    if (nextLength && !lastLength) {
        emptyElement(parentNode);
        return mountChildren(parentNode, nextChildren, lastVnode, context, updateQueue);
    }
    if (nextLength === lastLength && lastLength === 1) {
        lastChildren[0]._hostNode = parentNode.firstChild;
        return alignVnode(lastChildren[0], nextChildren[0], lastVnode, context, updateQueue);
    }
    let maxLength = Math.max(nextLength, lastLength),
        insertPoint = parentNode.firstChild,
        removeHits = {},
        fuzzyHits = {},
        actions = [],
        i = 0,
        hit,
        nextChild,
        lastChild;
    //第一次循环,构建移动指令(actions)与移除名单(removeHits)与命中名单(fuzzyHits)

    if (nextLength) {
        actions.length = nextLength;
        while (i < maxLength) {
            nextChild = nextChildren[i];
            lastChild = lastChildren[i];
            if (nextChild && lastChild && isSameNode(lastChild, nextChild)) {
                //  如果能直接找到,命名90%的情况
                actions[i] = [lastChild, nextChild];
                removeHits[i] = true;
            } else {
                if (nextChild) {
                    hit = nextChild.type + (nextChild.key || "");
                    if (fuzzyHits[hit] && fuzzyHits[hit].length) {
                        var oldChild = fuzzyHits[hit].shift();
                        // 如果命中旧的节点,将旧的节点移动新节点的位置,向后移动
                        actions[i] = [oldChild, nextChild, "moveAfter"];
                        removeHits[oldChild._i] = true;
                    }
                }
                if (lastChild) {
                    //如果没有命中或多了出来,那么放到命中名单中,留给第二轮循环使用
                    lastChild._i = i;
                    hit = lastChild.type + (lastChild.key || "");
                    let hits = fuzzyHits[hit];
                    if (hits) {
                        hits.push(lastChild);
                    } else {
                        fuzzyHits[hit] = [lastChild];
                    }
                }
            }
            i++;
        }
    }
    for (let j = 0, n = actions.length; j < n; j++) {
        let action = actions[j];
        if (!action) {
            nextChild = nextChildren[j];
            hit = nextChild.type + (nextChild.key || "");
            if (fuzzyHits[hit] && fuzzyHits[hit].length) {
                lastChild = fuzzyHits[hit].shift();
                action = [lastChild, nextChild, "moveAfter"];
            }
        }
        if (action) {
            lastChild = action[0];
            nextChild = action[1];
            dom = lastChild._hostNode;
            if (action[2]) {
                parentNode.insertBefore(dom, insertPoint);
            }
            insertPoint = updateVnode(lastChild, nextChild, lastVnode, context, updateQueue);
            if (!nextChild._hostNode) {
                nextChildren[j] = lastChild;
            }
            removeHits[lastChild._i] = true;
        } else {
            //为了兼容 react stack reconciliation的执行顺序,添加下面三行,
            //在插入节点前,将原位置上节点对应的组件先移除
            var removed = lastChildren[j];
            if (removed && !removed._disposed && !removeHits[j]) {
                disposeVnode(removed);
            }
            //如果找不到对应的旧节点,创建一个新节点放在这里
            dom = mountVnode(null, nextChild, lastVnode, context, updateQueue);
            parentNode.insertBefore(dom, insertPoint);
            insertPoint = dom;
        }
        insertPoint = insertPoint.nextSibling;
    }

    parentNode.vchildren = nextChildren;

    //移除
    lastChildren.forEach(function(el, i) {
        if (!removeHits[i]) {
            var node = el._hostNode;
            if (node) {
                removeDOMElement(node);
            }
            disposeVnode(el);
        }
    });
}
RubyLouvre commented 7 years ago

1.1.4的diffChildren

function diffChildren(parentVnode, nextChildren, parentNode, context) {
    let lastChildren = parentVnode.vchildren || emptyArray, //parentNode.vchildren,
        nextLength = nextChildren.length,
        insertPoint = parentNode.firstChild,
        lastLength = lastChildren.length;
    //optimize 1: 如果旧数组长度为零, 只进行添加
    if (!lastLength) {
        emptyElement(parentNode);
        return mountChildren(parentNode, nextChildren, parentVnode, context);
    }
    //optimize 2: 如果新数组长度为零, 只进行删除
    if (!nextLength) {
        return lastChildren.forEach(function(el) {
            removeElement(el._hostNode);
            disposeVnode(el);
        });
    }
    //optimize 3: 如果1vs1, 不用进入下面复杂的循环
    if (nextLength === lastLength && lastLength === 1) {
        if (insertPoint) {
            lastChildren[0]._hostNode = insertPoint;
        }
        return alignVnode(lastChildren[0], nextChildren[0], parentVnode, context);
    }
    let mergeChildren = [], //确保新旧数组都按原顺数排列
        fuzzyHits = {},
        i = 0,
        hit,
        oldDom,
        dom,
        nextChild;

    lastChildren.forEach(function(lastChild) {
        hit = genkey(lastChild);
        mergeChildren.push(lastChild);
        let hits = fuzzyHits[hit];
        if (hits) {
            hits.push(lastChild);
        } else {
            fuzzyHits[hit] = [lastChild];
        }
    });
    while (i < nextLength) {
        nextChild = nextChildren[i];
        nextChild._new = true;
        hit = genkey(nextChild);
        if (fuzzyHits[hit] && fuzzyHits[hit].length) {
            var oldChild = fuzzyHits[hit].shift();
            // 如果命中旧节点,置空旧节点,并在新位置放入旧节点(向后移动)
            var lastIndex = mergeChildren.indexOf(oldChild);
            if (lastIndex !== -1) {
                mergeChildren[lastIndex] = fakeLastNode;
                //  mergeChildren.splice(lastIndex, 1);
            }
            nextChild._new = oldChild;
        }
        mergeChildren.splice(i, 0, nextChild);
        i++;
    }

    for (var j = 0, n = mergeChildren.length; j < n; j++) {
        let nextChild = mergeChildren[j];
        if (nextChild._new) {
            var lastChild = nextChild._new;
            delete nextChild._new;
            if (dom) {
                insertPoint = dom.nextSibling;
            }
            if (lastChild === true) {
                //新节点有两种情况,命中位置更后方的旧节点或就地创建实例化
                // console.log("添加",getName(nextChild.type));
                dom = mountVnode(null, nextChild, parentVnode, context);
                insertElement(parentNode, dom, insertPoint);
            } else {
                oldDom = lastChild._hostNode;
                if (oldDom !== insertPoint) {
                    insertElement(parentNode, oldDom, insertPoint);
                }
                //console.log("更新",getName(nextChild.type));
                dom = alignVnode(lastChild, nextChild, parentVnode, context);
            }
        } else {
            if (nextChild._hostNode) {
                //console.log("移除",getName(nextChild.type));
                removeElement(nextChild._hostNode);
                disposeVnode(nextChild);
            }
        }
    }
}
RubyLouvre commented 6 years ago

1.1.5-pre2的diffChildren

function diffChildren(lastChildren, nextChildren, parentVnode, parentContext, updateQueue) {
    let nextLength = nextChildren.length,
        parentNode = parentVnode.stateNode,
        lastLength = lastChildren.length;
    var allChildNodes = parentVnode.childNodes;
    console.log("lastChildren",lastChildren);
    var lastFragments = getFragments(lastChildren);
    // if(lastFragments)
    //  var stateIndex = allChildNodes.indexOf(lastFragments[0]) 
    //  stateIndex = stateIndex = -1 ? 0: stateIndex
    parentVnode.childNodes = [];

    //得到要更新旧节点

    //optimize 1: 如果旧数组长度为零, 只进行添加
    if (!lastLength) {
        emptyElement(parentNode);
        return mountChildren(parentVnode, nextChildren, parentContext, updateQueue);
    }
    //optimize 2: 如果新数组长度为零, 只进行删除
    if (!nextLength && lastLength) {
        disposeVnode(lastChildren);
        return;
    }
    //optimize 3: 如果1vs1, 不用进入下面复杂的循环
    if (nextLength === lastLength && lastLength === 1) {
        var lastChild = lastChildren[0],
            nextChild = nextChildren[0];
        if (parentNode.firstChild && parentVnode.vtype === 1) {
            lastChild.stateNode = parentNode.firstChild;
        }
        return alignVnode(lastChild, nextChild, parentContext, updateQueue);
    }
    //从这里开始非常复杂的节点排序算法
    //step1: 构建模糊匹配对象fuzzyHits,以虚拟DOM的key/type为键名,并记录它的旧位置
    let fuzzyHits = {},
        hit,
        i = 0;

    lastChildren.forEach(function(lastChild) {
        hit = genkey(lastChild);
        let hits = fuzzyHits[hit];
        if (hits) {
            hits.push(lastChild);
        } else {
            fuzzyHits[hit] = [lastChild];
        }
    });
    //step2: 碰撞检测,并筛选离新节点最新的节点,执行null ref与updateComponent
    var React15 = false;
    var priorityQueue = [],
        reorderQueue = [];
    while (i < nextLength) {
        nextChild = nextChildren[i];
        hit = genkey(nextChild);
        let fLength = fuzzyHits[hit] && fuzzyHits[hit].length,
            hitVnode = null;
        if (fLength) {
            let fnodes = fuzzyHits[hit];
            React15 = true;
            if (fLength > 1) {
                hitVnode = getNearestNode(fnodes, i, nextChild);
            } else {
                hitVnode = nextChild._hit = fnodes[0];
                delete fuzzyHits[hit];
            }
            if (hitVnode) {
                lastChildren[hitVnode.index] = null;
                if (hitVnode.index !== nextChild.index) {
                    reorderQueue.push(nextChild);
                }
                if (hitVnode.vtype > 1) {
                    var updater = hitVnode.stateNode.updater;

                    receiveComponent(hitVnode, nextChild, parentContext, updater._ending ? updateQueue : priorityQueue);
                } else {
                    Refs.detachRef(hitVnode, nextChild);
                }
            }
        }
        i++;
    }

    drainQueue(priorityQueue);
    //step3: 移除没有命中的虚拟DOM,执行它们的钩子与ref
    if (React15) {
        disposeChildren(lastChildren);
    }
    //step4: 更新元素,调整位置或插入新元素
    var child = nextChildren[0];
    alignVnode(child._hit, child, parentContext, updateQueue, true);

    //React的怪异行为,如果没有组件发生更新,那么先执行添加,再执行移除
    if (!React15) {
        disposeChildren(lastChildren);
    }
}
RubyLouvre commented 6 years ago

1.1.5-pre3 diff.js

import { options, innerHTML, emptyObject, showQueue, toLowerCase, emptyArray, toArray, deprecatedWarn } from "./util";
import { createElement as createDOMElement, emptyElement } from "./browser";
import { disposeVnode, disposeChildren, topVnodes, topNodes } from "./dispose";
import { instantiateComponent } from "./instantiateComponent";
import { processFormElement } from "./ControlledComponent";
import { createVnode, restoreChildren, fiberizeChildren, createElement } from "./createElement";
import { getContextByTypes } from "./updater";
import { drainQueue } from "./scheduler";
import { captureError } from "./error";
import { Refs, pendingRefs } from "./Refs";
import { diffProps } from "./diffProps";

//[Top API] React.isValidElement
export function isValidElement(vnode) {
    return vnode && vnode.vtype;
}

//[Top API] ReactDOM.render
export function render(vnode, container, callback) {
    return renderByAnu(vnode, container, callback);
}
//[Top API] ReactDOM.unstable_renderSubtreeIntoContainer
export function unstable_renderSubtreeIntoContainer(lastVnode, nextVnode, container, callback) {
    deprecatedWarn("unstable_renderSubtreeIntoContainer");
    var parentContext = (lastVnode && lastVnode.context) || {};
    return renderByAnu(nextVnode, container, callback, parentContext);
}
//[Top API] ReactDOM.unmountComponentAtNode
export function unmountComponentAtNode(container) {
    var lastVnode = container.__component;
    if (lastVnode) {
        disposeVnode(lastVnode);
        emptyElement(container);
        container.__component = null;
    }
}
//[Top API] ReactDOM.findDOMNode
export function findDOMNode(ref) {
    if (ref == null) {
        //null instance vnode
        return null;
    }
    var vnode = ref.stateNode;
    if (vnode.nodeType) {
        return vnode.nodeType === 8 ? null : vnode;
    } else {
        return findDOMNode(ref.child);
    }
}

//[Top API] ReactDOM.createPortal
export function createPortal(vchildren, container) {
    var parentVnode = createVnode(container);
    var lastChildren = parentVnode.child ? restoreChildren(parentVnode) : [];
    var nextChildren = fiberizeChildren(parentVnode);
    diffChildren(lastChildren, nextChildren, parentVnode, {}, []);
    return null;
}

var AnuWrapper = function(){};
AnuWrapper.prototype.render = function(){
    return this.props.child;
};

// ReactDOM.render的内部实现
function renderByAnu(vnode, container, callback, context = {}) {
    if (!isValidElement(vnode)) {
        throw `ReactDOM.render的第一个参数错误`; // eslint-disable-line
    }
    if (!(container && container.getElementsByTagName)) {
        throw `ReactDOM.render的第二个参数错误`; // eslint-disable-line
    }

    //__component用来标识这个真实DOM是ReactDOM.render的容器,通过它可以取得上一次的虚拟DOM
    // 但是在IE6-8中,文本/注释节点不能通过添加自定义属性来引用虚拟DOM,这时我们额外引进topVnode,
    //topNode来寻找它们。

    let nodeIndex = topNodes.indexOf(container),
        lastVnode,
        updateQueue = [];
    if (nodeIndex !== -1) {
        lastVnode = topVnodes[nodeIndex];
    } else {
        topNodes.push(container);
        nodeIndex = topNodes.length - 1;
    }

    Refs.currentOwner = null; //防止干扰
    var child = vnode;
    vnode = createElement(AnuWrapper, {
        isTop: true,
        child: child
    });

    topVnodes[nodeIndex] = vnode;
    if (lastVnode) {
        vnode.return = lastVnode.return;
        vnode.child = lastVnode.child;
        alignVnode(lastVnode, vnode, context, updateQueue);
    } else {
        var parent = (vnode.return = createVnode(container));
        parent.child = vnode;
        genVnodes(vnode, context, updateQueue);
    }

    container.__component = vnode; //兼容旧的
    vnode.return.batchMount();
    drainQueue(updateQueue);

    var rootNode = vnode.child.stateNode;
    if (callback) {
        callback.call(rootNode); //坑
    }
    //组件返回组件实例,而普通虚拟DOM 返回元素节点
    return rootNode;
}

function genVnodes(vnode, context, updateQueue) {
    let parentNode = vnode.return.stateNode;
    let nodes = toArray(parentNode.childNodes || emptyArray);
    let lastVnode = null;
    for (var i = 0, dom; (dom = nodes[i++]); ) {
        if (toLowerCase(dom.nodeName) === vnode.type) {
            lastVnode = createVnode(dom);
        } else {
            parentNode.removeChild(dom);
        }
    }
    if (lastVnode) {
        return alignVnode(lastVnode, vnode, context, updateQueue);
    } else {
        return mountVnode(vnode, context, updateQueue);
    }
}

//mountVnode只是转换虚拟DOM为真实DOM,不做插入DOM树操作
function mountVnode(vnode, context, updateQueue) {
    options.beforeInsert(vnode);
    if (vnode.vtype === 0 || vnode.vtype === 1) {
        var dom = createDOMElement(vnode, vnode.return);
        vnode.stateNode = dom;
        if (vnode.vtype === 1) {
            let { _hasRef, _hasProps, type, props } = vnode;
            let children = fiberizeChildren(vnode);
            mountChildren(vnode, children, context, updateQueue);
            vnode.batchMount(); //批量插入 dom节点
            if (_hasProps) {
                diffProps(dom, emptyObject, props, vnode);
            }
            if (formElements[type]) {
                processFormElement(vnode, dom, props);
            }
            if (_hasRef) {
                pendingRefs.push(vnode, dom);
            }
        }
    } else {
        mountComponent(vnode, context, updateQueue);
    }

    var sibling = vnode.sibling;
    if (sibling) {
        var lastSibling = sibling._hit;
        if (lastSibling) {
            // delete  sibling._hit;
            alignVnode(lastSibling, sibling, context, updateQueue);
        } else {
            mountVnode(sibling, context, updateQueue);
        }
    }
    return dom;
}

//通过组件虚拟DOM产生组件实例与内部操作实例updater
function mountComponent(vnode, parentContext, updateQueue, parentUpdater) {
    let { type, props } = vnode;
    let instance = instantiateComponent(type, vnode, props, parentContext); //互相持有引用
    let updater = instance.updater;
    if (parentUpdater) {
        updater.parentUpdater = parentUpdater;
    }
    updater.parentContext = parentContext;

    if (instance.componentWillMount) {
        captureError(instance, "componentWillMount", []);
        instance.state = updater.mergeStates();
    }
    updater._hydrating = true;
    updater.render(updateQueue);
    updateQueue.push(updater);
}

function mountChildren(vnode, children, context, updateQueue) {
    if (children[0]) {
        mountVnode(vnode.child, context, updateQueue);
    }
}

const formElements = {
    select: 1,
    textarea: 1,
    input: 1,
    option: 1
};

function updateVnode(lastVnode, nextVnode, context, updateQueue) {
    var dom = (nextVnode.stateNode = lastVnode.stateNode);
    options.beforeUpdate(nextVnode);

    if (lastVnode.vtype === 0) {
        if (nextVnode.text !== lastVnode.text) {
            dom.nodeValue = nextVnode.text;
        }
    } else if (lastVnode.vtype === 1) {
        nextVnode.childNodes = lastVnode.childNodes;
        let { props: lastProps, stateNode: dom, _hasProps, type } = lastVnode;
        let { props: nextProps, _hasProps: nextCheckProps } = nextVnode;
        let lastChildren = restoreChildren(lastVnode);
        if (nextProps[innerHTML]) {
            disposeChildren(lastChildren);
        } else {
            diffChildren(lastChildren, fiberizeChildren(nextVnode), nextVnode, context, updateQueue);
        }
        if (_hasProps || nextCheckProps) {
            diffProps(dom, lastProps, nextProps, nextVnode);
        }
        if (formElements[type]) {
            processFormElement(nextVnode, dom, nextProps);
        }
        Refs.detachRef(lastVnode, nextVnode, dom);
    } else {
        dom = receiveComponent(lastVnode, nextVnode, context, updateQueue);
    }
    return dom;
}

function receiveComponent(lastVnode, nextVnode, parentContext, updateQueue) {
    let { type, stateNode } = lastVnode;
    let updater = stateNode.updater,
        nextContext;

    //如果正在更新过程中接受新属性,那么去掉update,加上receive
    var willReceive = lastVnode !== nextVnode;
    if (!type.contextTypes) {
        nextContext = stateNode.context;
    } else {
        nextContext = getContextByTypes(parentContext, type.contextTypes);
        willReceive = true;
    }
    updater.context = nextContext;
    //parentContext在官方中被称之不nextUnmaskedContext, parentVnode称之为nextParentElement
    updater.props = nextVnode.props;
    updater.parentContext = parentContext;
    updater.pendingVnode = nextVnode;
    updater.willReceive = willReceive;

    if (!updater._dirty) {
        //如果在事件中使用了setState
        updater._receiving = [lastVnode, nextVnode, nextContext];
        updater.addJob("patch");
        updateQueue.push(updater);
    }

    return updater.stateNode;
}

function isSameNode(a, b) {
    if (a.type === b.type && a.key === b.key) {
        return true;
    }
}

function genkey(vnode) {
    return vnode.key ? "@" + vnode.key : vnode.type.name || vnode.type;
}

function alignVnode(lastVnode, nextVnode, context, updateQueue) {
    if (!lastVnode || lastVnode.nodeType) {
        return mountVnode(nextVnode, context, updateQueue);
    }
    if (isSameNode(lastVnode, nextVnode)) {
        //组件虚拟DOM已经在diffChildren生成并插入DOM树
        var resolveInDiffChildren = nextVnode._hit && nextVnode.vtype > 1;
        if (resolveInDiffChildren) {
            delete nextVnode._hit;
        } else {
            updateVnode(lastVnode, nextVnode, context, updateQueue);
        }

        var sibling = nextVnode.sibling;
        if (sibling) {
            alignVnode(sibling._hit, sibling, context, updateQueue);
        }
    } else {
        disposeVnode(lastVnode);
        mountVnode(nextVnode, context, updateQueue);
    }
    return nextVnode.stateNode;
}

function getNearestNode(vnodes, ii, newVnode) {
    var distance = Infinity,
        hit = null,
        vnode,
        i = 0;
    while ((vnode = vnodes[i])) {
        var delta = vnode.index - ii;
        if (delta === 0) {
            newVnode._hit = vnode;
            vnodes.splice(i, 1);
            return vnode;
        } else {
            var d = Math.abs(delta);
            if (d < distance) {
                distance = d;
                hit = vnode;
            }
        }
        i++;
    }
    newVnode._hit = hit;
    return hit;
}

function mergeNodes(children) {
    var nodes = [];
    for (var i = 0, el; (el = children[i++]); ) {
        if (!el._disposed) {
            if(el.stateNode && el.stateNode.nodeType){
                nodes.push(el.stateNode);
            }else{
                nodes.push.apply(nodes, el.collectNodes());
            }
        }
    }
    return nodes;
}

function diffChildren(lastChildren, nextChildren, parentVnode, parentContext, updateQueue) {
    var parentVElement = parentVnode,
        nextLength = nextChildren.length,
        lastLength = lastChildren.length;
    do {
        if (parentVElement.vtype === 1) {
            break;
        }
    } while ((parentVElement = parentVElement.return));
    var childNodes = parentVElement.childNodes;
    if (!lastLength) {
        return mountChildren(parentVnode, nextChildren, parentContext, updateQueue);
    }
    var lastChilds = mergeNodes(lastChildren);
    var React15 = false;
    if (!childNodes.updateMeta) {
        var startIndex = childNodes.indexOf(lastChilds[0]);
        var insertPoint = childNodes[startIndex] || null;
        childNodes.length = 0; //清空数组,以方便收集节点
        childNodes.updateMeta = {
            parentVnode,
            parentVElement,
            insertPoint,
            lastChilds
        };
    }
    // parentVnode.childNodes = childNodes;
    let fuzzyHits = {},
        hit,
        nextChild,
        i = 0;

    lastChildren.forEach(function(lastChild) {
        hit = genkey(lastChild);
        let hits = fuzzyHits[hit];
        if (hits) {
            hits.push(lastChild);
        } else {
            fuzzyHits[hit] = [lastChild];
        }
    });
    //step2: 碰撞检测,并筛选离新节点最新的节点,执行null ref与updateComponent
    var priorityQueue = [],
        reorderQueue = [];
    while (i < nextLength) {
        nextChild = nextChildren[i];
        hit = genkey(nextChild);
        let fLength = fuzzyHits[hit] && fuzzyHits[hit].length,
            hitVnode = null;
        if (fLength) {
            let fnodes = fuzzyHits[hit];
            React15 = true;
            if (fLength > 1) {
                hitVnode = getNearestNode(fnodes, i, nextChild);
            } else {
                hitVnode = nextChild._hit = fnodes[0];
                delete fuzzyHits[hit];
            }
            if (hitVnode) {
                lastChildren[hitVnode.index] = null;
                if (hitVnode.index !== nextChild.index) {
                    reorderQueue.push(nextChild);
                }
                if (hitVnode.vtype > 1) {
                    receiveComponent(hitVnode, nextChild, parentContext,  updateQueue);//原来updateQueue为priorityQueue
                } else {
                    Refs.detachRef(hitVnode, nextChild);
                }
            }
        }
        i++;
    }
    //step3: 移除没有命中的虚拟DOM,执行它们的钩子与ref
    disposeChildren(lastChildren);
    drainQueue(updateQueue);//原来updateQueue为priorityQueue
    //step4: 更新元素,调整位置或插入新元素
    var child = nextChildren[0];
    child && alignVnode(child._hit, child, parentContext, updateQueue);

    //React的怪异行为,如果没有组件发生更新,那么先执行添加,再执行移除
    if (!React15) {
        disposeChildren(lastChildren);
    }
    if (childNodes.updateMeta && childNodes.updateMeta.parentVnode == parentVnode) {
        parentVnode.batchUpdate(childNodes.updateMeta, mergeNodes(nextChildren));
    }
}

options.diffChildren = diffChildren;

updater.js

import { fiberizeChildren, restoreChildren, createVText } from "./createElement";
import { extend, options, typeNumber, isFn, showQueue } from "../src/util";
import { drainQueue, enqueueUpdater } from "./scheduler";
import { pushError, captureError } from "./error";
import { Refs } from "./Refs";

function alwaysNull() {
    return null;
}
let mountOrder = 1;
const support16 = true;
const errorType = {
    0: "undefined",
    2: "boolean",
    3: "number",
    4: "string",
    7: "array"
};
/**
 * 为了防止污染用户的实例,需要将操作组件虚拟DOM与生命周期钩子的逻辑全部抽象到这个类中
 * 
 * @export
 * @param {any} instance 
 * @param {any} vnode 
 */
export function Updater(instance, vnode) {
    vnode.stateNode = instance;
    instance.updater = this;
    this.instance = instance;
    this.vnode = vnode;
    this._pendingCallbacks = [];
    this._pendingStates = [];
    this._jobs = ["resolve"];
    this._mountOrder = mountOrder++;
    this._hookName = "componentDidMount";
    // update总是保存最新的数据,如state, props, context, parentContext, parentVnode
    //  this._hydrating = true 表示组件正在根据虚拟DOM合成真实DOM
    //  this._renderInNextCycle = true 表示组件需要在下一周期重新渲染
    //  this._forceUpdate = true 表示会无视shouldComponentUpdate的结果
    if (instance.__isStateless) {
        this.mergeStates = alwaysNull;
    }
}

Updater.prototype = {
    addJob: function(newJob) {
        var jobs = this._jobs;
        if (jobs[jobs.length - 1] !== newJob) {
            jobs.push(newJob);
        }
    },
    enqueueSetState(state, cb) {
        if (isFn(cb)) {
            this._pendingCallbacks.push(cb);
        }
        if (state === true) {
            //forceUpdate
            this._forceUpdate = true;
        } else {
            //setState
            this._pendingStates.push(state);
        }
        if (options.async) {
            //在事件句柄中执行setState会进行合并
            enqueueUpdater(this);
            return;
        }

        if (this._hookName === "componentDidMount") {
            //组件挂载期
            //componentWillUpdate中的setState/forceUpdate应该被忽略
            if (this._hydrating) {
                //在render方法中调用setState也会被延迟到下一周期更新.这存在两种情况,
                //1. 组件直接调用自己的setState
                //2. 子组件调用父组件的setState,
                this._renderInNextCycle = true;
            }
        } else {
            //组件更新期
            if (this._receiving) {
                //componentWillReceiveProps中的setState/forceUpdate应该被忽略
                return;
            }
            if (this._hydrating) {
                //在componentDidMount方法里面可能执行多次setState方法,来引发update,但我们只需要一次update
                this._renderInNextCycle = true;
                // 在componentDidMount里调用自己的setState,延迟到下一周期更新
                // 在更新过程中, 子组件在componentWillReceiveProps里调用父组件的setState,延迟到下一周期更新
                return;
            }
            this.addJob("patch");
            drainQueue([this]);
        }
    },
    mergeStates() {
        let instance = this.instance,
            pendings = this._pendingStates,
            n = pendings.length,
            state = instance.state;
        if (n === 0) {
            return state;
        }
        let nextState = extend({}, state); //每次都返回新的state
        for (let i = 0; i < n; i++) {
            let pending = pendings[i];
            if (pending && pending.call) {
                pending = pending.call(instance, nextState, this.props);
            }
            extend(nextState, pending);
        }
        pendings.length = 0;
        return nextState;
    },

    exec(updateQueue) {
        var job = this._jobs.shift();
        if (job) {
            this[job](updateQueue);
        }
    },

    patch(updateQueue) {
        let { instance, context, props, vnode } = this;
        if (this._receiving) {
            let [lastVnode, nextVnode, nextContext] = this._receiving;
            nextVnode.stateNode = instance;
            //如果context与props都没有改变,那么就不会触发组件的receive,render,update等一系列钩子
            //但还会继续向下比较
            captureError(instance, "componentWillReceiveProps", [this.props, nextContext]);
            delete this._receiving;
            Refs.detachRef(lastVnode, nextVnode);
        }

        Refs.clearElementRefs();
        let state = this.mergeStates();
        let shouldUpdate = true;
        if (!this._forceUpdate && !captureError(instance, "shouldComponentUpdate", [props, state, context])) {
            shouldUpdate = false;
            if (this.pendingVnode) {
                this.vnode = this.pendingVnode;
                delete this.pendingVnode;
            }
        } else {
            var { props: lastProps, context: lastContext, state: lastState } = instance;
            captureError(instance, "componentWillUpdate", [props, state, context]);
        }
        vnode.stateNode = instance;
        delete this._forceUpdate;
        //既然setState了,无论shouldComponentUpdate结果如何,用户传给的state对象都会作用到组件上
        instance.props = props;
        instance.state = state;
        //vnode.lazyMount();
        instance.context = context;
        this.addJob("resolve");
        if (shouldUpdate) {
            this._hydrating = true;
            this._hookArgs = [lastProps, lastState, lastContext];
            this._hookName = "componentDidUpdate";
            this.render(updateQueue);
        }
        updateQueue.push(this);
    },

    resolve: function(updateQueue) {
        Refs.clearElementRefs();
        let instance = this.instance;
        let vnode = this.vnode;
        var hookName = this._hookName;
        delete this._hookName;
        //执行componentDidMount/Update钩子
        Refs.currentActiveQueue = updateQueue;
        this._ending = true;
        captureError(instance, hookName, this._hookArgs || []);
        this._ending = false;
        delete Refs.currentActiveQueue;
        delete this._hookArgs;
        //执行React Chrome DevTools的钩子
        if (hookName === "componentDidMount") {
            options.afterMount(instance);
        } else {
            options.afterUpdate(instance);
        }

        this._hydrating = false;
        //执行组件虚拟DOM的ref回调
        if (vnode._hasRef) {
            Refs.fireRef(vnode, instance.__isStateless ? null : instance);
        }
        //如果在componentDidMount/Update钩子里执行了setState,那么再次渲染此组件
        if (this._renderInNextCycle) {
            delete this._renderInNextCycle;
            this.addJob("patch");
            updateQueue.push(this);
        }
    },
    render(updateQueue) {
        //vnode为组件虚拟DOM,也只能是组件虚拟DOM
        let { vnode, pendingVnode, instance, parentContext } = this,
            nextChildren,
            rendered,lastChildren;
        let target = pendingVnode || vnode;
        if (this.willReceive === false) {
            rendered = vnode.child;
            delete this.willReceive;
        } else {
            let lastOwn = Refs.currentOwner;
            Refs.currentOwner = instance;
            rendered = captureError(instance, "render", []);
            Refs.currentOwner = lastOwn;
        }
        if (this._hookName !== "componentDidMount") {
            lastChildren = restoreChildren( this.vnode );
            // console.log("新的",this.vnode, !!this.vnode.child, lastChildren.length);
        }else{
            // console.log("旧的");
            lastChildren = [];
        }

        var oldProps = target.props;
        target.props = { children: rendered };
        nextChildren = fiberizeChildren(target);
        target.props = oldProps;
        if (!nextChildren.length) {
            var placeHolder = createVText("#comment");
            placeHolder.index = 0;
            placeHolder.return = target;
            nextChildren.push(placeHolder);
        }

        let childContext = parentContext,
            number = typeNumber(rendered);
        if (number === 7) {
            if (!support16) {
                pushError(instance, "render", new Error("React15 fail to render array"));
            }
        } else {
            if (number < 5) {
                var noSupport = !support16 && errorType[number];
                if (noSupport) {
                    pushError(instance, "render", new Error("React15 fail to render " + noSupport));
                }
            } else {
                childContext = getChildContext(instance, parentContext);
            }
        }
        //child在React16总是表示它是父节点的第一个节点
        var child = nextChildren[0];
        options.diffChildren(lastChildren, nextChildren, target, childContext, updateQueue);
        this.rendered = child; //现在还用于devtools中
        let u = this;
        do {
            if (u.pendingVnode) {
                u.vnode = u.pendingVnode;
                delete u.pendingVnode;
            }
        } while ((u = u.parentUpdater));
    }
};

export function getChildContext(instance, parentContext) {
    if (instance.getChildContext) {
        let context = instance.getChildContext();
        if (context) {
            parentContext = Object.assign({}, parentContext, context);
        }
    }
    return parentContext;
}

export function getContextByTypes(curContext, contextTypes) {
    let context = {};
    if (!contextTypes || !curContext) {
        return context;
    }
    for (let key in contextTypes) {
        if (contextTypes.hasOwnProperty(key)) {
            context[key] = curContext[key];
        }
    }
    return context;
}

vnode.js

import { typeNumber, toArray, REACT_ELEMENT_TYPE } from "./util";
import { removeElement } from "./browser";
import { Refs } from "./Refs";

export function Vnode(type, vtype, props, key, ref, _hasProps) {
    this.type = type;
    this.vtype = vtype;
    this.uuid = Math.random();

    if (vtype) {
        this.props = props;
        this._owner = Refs.currentOwner;

        if (key) {
            this.key = key;
        }

        if (vtype === 1) {
            this._hasProps = _hasProps;
            this.childNodes = [];
        }

        let refType = typeNumber(ref);
        if (refType === 3 || refType === 4 || refType === 5) {
            //number, string, function
            this._hasRef = true;
            this.ref = ref;
        }
    }
    /*
      this.stateNode = null
    */
}

Vnode.prototype = {
    getDOMNode() {
        return this.stateNode || null;
    },

    collectNodes(isChild, ret) {
        ret = ret || [];
        if (isChild && this.vtype < 2) {
            ret.push(this.stateNode);
        } else {
            for (var a = this.child; a; a = a.sibling) {
                a.collectNodes(true, ret);
            }
        }
        return ret;
    },
    batchMount() {
        var parentNode = this.stateNode, childNodes = this.collectNodes();
        childNodes.forEach(function(dom){
            parentNode.appendChild(dom);
        });
    },
    batchUpdate(updateMeta, nextChildren) {
        var parentVnode = updateMeta.parentVElement,
            parentNode = parentVnode.stateNode,
            lastChildren = updateMeta.lastChilds,
            insertPoint = updateMeta.insertPoint,
            newLength = nextChildren.length,
            oldLength = lastChildren.length,
            inserted = [];
        //  console.log(nextChildren, lastChildren, "开始比较");
        for (let i = 0; i < newLength; i++) {
            let child = nextChildren[i];
            let last = lastChildren[i];
            if (last === child) {
                //如果相同
            } else if (last && inserted.indexOf(last) == -1 ) {
                parentNode.replaceChild(child, last);//如果这个位置有DOM,并且它不在新的nextChildren之中
            } else if (insertPoint) {
                parentNode.insertBefore(child, insertPoint.nextSibling);
            } else {
                parentNode.appendChild(child);
            }
            insertPoint = child;
            inserted.push(child);
        }
        if (newLength < oldLength) {
            for (let i = newLength; i < oldLength; i++) {
                removeElement(lastChildren[i]);
            }
        }

        if(parentNode.nodeType === 1){
            parentVnode.childNodes = toArray(parentNode.childNodes);
        }
    },

    $$typeof: REACT_ELEMENT_TYPE
};

scheduler.js

import { options, clearArray } from "./util";
import { Refs } from "./Refs";
import { showError } from "./error";

const dirtyComponents = [];
function mountSorter(u1, u2) {
    //按文档顺序执行
    u1._dirty = false;
    return u1._mountOrder - u2._mountOrder;
}
export function flushUpdaters() {
    if (dirtyComponents.length) {
        var currentQueue = clearArray(dirtyComponents).sort(mountSorter);
        currentQueue.forEach(function(el) {
            delete el._dirty;
        });
        drainQueue(currentQueue);
    }
}

export function enqueueUpdater(updater) {
    if (!updater._dirty) {
        updater.addJob("patch");
        updater._dirty = true;
        dirtyComponents.push(updater);
    }
}

export function drainQueue(queue) {
    options.beforePatch();
    //先执行所有元素虚拟DOMrefs方法(从上到下)
    Refs.clearElementRefs();
    let needSort = [],
        unique = {},
        updater;
    while ((updater = queue.shift())) {
        //queue可能中途加入新元素,  因此不能直接使用queue.forEach(fn)
        if (updater._disposed) {
            continue;
        }
        if (!unique[updater._mountOrder]) {
            unique[updater._mountOrder] = 1;
            needSort.push(updater);
        }
        updater.exec(queue);
    }

    //再执行所有setState/forceUpdate回调,根据从下到上的顺序执行
    needSort.sort(mountSorter).forEach(function(updater) {
        //console.log(updater.name, updater._mountOrder);
        clearArray(updater._pendingCallbacks).forEach(function(fn) {
            fn.call(updater.instance);
        });
    });
    options.afterPatch();
    showError();
}
RubyLouvre commented 6 years ago
export function arrayRemoveOne(array, el) {
    for (var i = array.length; i >= 0; i--) {
        if (el === array[i]) {
            array.splice(i, 1);
            break;
        }
    }
}
export function arrayRemoveSome(array, toRemoved) {
    for (var i = 0, n = toRemoved.length; i < n; i++) {
        arrayRemoveOne(array, toRemoved[i]);
    }
}