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