帮你读懂preact源码(三)

对回收的处理

在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。

参考

  1. https://calendar.perfplanet.c...
  2. https://reactjs.org/docs/impl...
  3. https://segmentfault.com/a/11...
  4. https://www.w3ctech.com/topic...
  5. https://zhuanlan.zhihu.com/p/...
  6. https://github.com/acdlite/re...

相关推荐