2

这节开始说回收机制。在这之前把组件的最后一点内容收尾。buildComponentFromVNode相当于updateComponent, 但里面存在替换操作。在替换过程一堆销毁函数出现了。

  1. recollectNodeTree

  2. unmountComponent

  3. removeChildren

  4. removeNode

export function buildComponentFromVNode(dom, vnode, context, mountAll) {
     //取得附上真实DOM上的组件实例
    let c = dom && dom._component,
        originalComponent = c,
        oldDom = dom,
//判定两个构造器是否相等
        isDirectOwner = c && dom._componentConstructor===vnode.nodeName,
        isOwner = isDirectOwner,
      //添加默认属性
        props = getNodeProps(vnode);
     //寻找与之同类型的组件实例
    while (c && !isOwner && (c=c._parentComponent)) {
        isOwner = c.constructor===vnode.nodeName;
    }
      //如果能找到这个实例,并且不是在ReactDOM.render过程中
    if (c && isOwner && (!mountAll || c._component)) {
        setComponentProps(c, props, ASYNC_RENDER, context, mountAll);
        dom = c.base;
    }
    else { 
    //移除旧的实例,创建新的实例
        if (originalComponent && !isDirectOwner) {
            unmountComponent(originalComponent);
            dom = oldDom = null;
        }

        c = createComponent(vnode.nodeName, props, context);
        if (dom && !c.nextBase) {
            c.nextBase = dom;
            oldDom = null;
        }
        setComponentProps(c, props, SYNC_RENDER, context, mountAll);
        dom = c.base;
         
        if (oldDom && dom!==oldDom) {
            oldDom._component = null;//GC
            recollectNodeTree(oldDom, false);
        }
    }

    return dom;
}

unmountComponent是一个递归处理子组件的过程

export function unmountComponent(component) {
    if (options.beforeUnmount) options.beforeUnmount(component);

    let base = component.base;
     //防止用户在componentWillUnmount里进行setState
    component._disable = true;

    if (component.componentWillUnmount) component.componentWillUnmount();

    component.base = null;

//处理高阶组件
    let inner = component._component;
    if (inner) {
        unmountComponent(inner);
    }
    else if (base) {
    //如果组件最后生成的是元素节点,并且它上面有ref属性
        if (base[ATTR_KEY] && base[ATTR_KEY].ref) base[ATTR_KEY].ref(null);

        component.nextBase = base;

        removeNode(base);
        collectComponent(component);//收集元素节点

        removeChildren(base);
    }
    //处理组件自身的ref,如<Tooltip ref={()=>{  console.log(1)}}>
    if (component.__ref) component.__ref(null);
}

removeNode是将节点从它的父节点中分离出来

export function removeNode(node) {
    let parentNode = node.parentNode;
    if (parentNode) parentNode.removeChild(node);
}

下面是removeChildren与recollectNodeTree,removeChildren其实是个二道贩子,只是负责遍历,真正做的事的是recollectNodeTree。

export function removeChildren(node) {
    node = node.lastChild;
    while (node) {
        let next = node.previousSibling;
        recollectNodeTree(node, true);
        node = next;
    }
}
//recollectNodeTree用于移除组件与执行元素节点的缓存数据
export function recollectNodeTree(node, unmountOnly) {
    let component = node._component;
    if (component) {
        // if node is owned by a Component, unmount that component (ends up recursing back here)
        unmountComponent(component);
    }
    else {
        // If the node's VNode had a ref function, invoke it with null here.
        // (this is part of the React spec, and smart for unsetting references)
        if (node[ATTR_KEY]!=null && node[ATTR_KEY].ref) node[ATTR_KEY].ref(null);

        if (unmountOnly===false || node[ATTR_KEY]==null) {
            removeNode(node);
        }

        removeChildren(node);
    }
}

至此,销毁部分已经讲完了,还剩下diffAttributes及其内部实现。这个没有什么好谈,都是巨简单,里面尽是if else。

总评,preact其实从它这样体量的代码,许多情况是兼顾不及的。唯一称道的是性能。为了达成这性能,它尽量使用异步与_disable来控制组件的更新。为了确保数据不会乱,它是根据页面的真实DOM上来提取现有的虚拟DOM树进行diff。


司徒正美
5.6k 声望3.5k 粉丝

穿梭于二次元与二进制间的魔法师( ̄(工) ̄) 凸ส้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้