这节开始说回收机制。在这之前把组件的最后一点内容收尾。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,mountAll); dom = c.base; } else { //移除旧的实例,创建新的实例 if (originalComponent && !isDirectOwner) { unmountComponent(originalComponent); dom = oldDom = null; } c = createComponent(vnode.nodeName,context); if (dom && !c.nextBase) { c.nextBase = dom; oldDom = null; } setComponentProps(c,SYNC_RENDER,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。