对回收的处理
在preact中,回收调用了两个方法,dom节点的回收一般会调用recollectNodeTree,组件的回收会调用unmountComponent。
preact复用dom的秘密在于当要卸载一个组件的时候,只有组件的根节点会从父节点上卸载掉,组件完整的dom仍然存在,被卸载的组件会保存在components对象中。
在创建组件的时候又通过nodeName拿到对应的dom节点树,挂载在组件实例的inst.nextBase上,在renderComponent的时候,再diff nextBase与新的虚拟dom树rendered。
相关主要代码如下:
function createComponent(Ctor, props, context) {
let list = components[Ctor.name],
inst;
if (Ctor.prototype && Ctor.prototype.render) {
inst = new Ctor(props, context);
Component.call(inst, props, context);
} else { // 对应函数组件
inst = new Component(props, context);
inst.constructor = Ctor;
inst.render = doRender;
}
if (list) {
for (let i = list.length; i--;) {
if (list[i].constructor === Ctor) {
inst.nextBase = list[i].nextBase;
list.splice(i, 1);
break;
}
}
}
return inst;
}
setState的处理
更改组件上的state,然后将要渲染的组件放在一个数组中,在下一次event loop的时候渲染:
setState: function setState(state, callback) {
let s = this.state;
if (!this.prevState) this.prevState = extend({}, s);
extend(s, typeof state === 'function' ? state(s, this.props) : state);
if (callback)(this._renderCallbacks = this._renderCallbacks || []).push(callback);
enqueueRender(this);
},
function enqueueRender(component) {
// component._dirty为false且items原本为空数组就能渲染
if (!component._dirty && (component._dirty = true) && items.push(component) == 1) {
(options.debounceRendering || defer)(rerender); //异步的执行render,要执行render方法的component中的_dirty设为true
}
},
function rerender() {
let p,
list = items;
items = [];
while (p = list.pop()) {
if (p._dirty) renderComponent(p);
}
}
preact对事件的处理
preact为了减少增减事件对性能和内存的影响,当为dom做事件监听时,添加的是一个代理函数。
function setAccessor(node, name, old, value, isSvg) {
// ...
if (name[0] == 'o' && name[1] == 'n') {
let useCapture = name !== (name = name.replace(/Capture$/, ''));
name = name.toLowerCase().substring(2);
if (value) {
if (!old) node.addEventListener(name, eventProxy, useCapture);
} else {
node.removeEventListener(name, eventProxy, useCapture);
}
(node._listeners || (node._listeners = {}))[name] = value;
}
// ...
}
function eventProxy(e) {
return this._listeners[e.type](options.event && options.event(e) || e);
}
fiber(个人理解)
从以上源码阅读中我们可以看到,react最大的性能问题在于递归的diff,react中的shouldCompnentUpdate与PureComponent也是为了缓解这个问题。但是当应用比较大的时候一个高级别组件的diff还是很容易使得动画掉帧。
fiber的出现就是为了解决这个问题,react fiber将计算工作分成了多个小片,这使得整个计算工作可以暂停,中止,或重新开始。为不同类型的更新分配优先级。当动画或用户交互触发时,就可以先暂停低优先级的更新工作,保证动画的流畅性,等所有的渲染计算工作完成,对dom更新进行一次commit。