preact系列之setState

499 阅读2分钟

阅读须知

  • 文章参考的preact版本是10.5.13
  • 文章会省略大部分逻辑,比如hydrating,context,isSvg等。所以如果有大佬点进来需谨慎。
  • 简略版本代码

setState

  • setState注意点,默认setState是批量更新,setState(updater, callback),updater如果是函数,参数可以拿到最新的state,callback也可以拿到最新的state。
    • 拿到组件本身最新的state
    • 如果update是函数,执行update拿到修改后的state,合并到旧的state中。如果update是对象,合并到旧的state中。
    • 保存callback函数到组件本身的_renderCallbacks,
  Component.prototype.setState = function(update, callback) {
   let s;
   
   // _nextState是最新的state, 不存在就拿state
   if (this._nextState != null && this._nextState !== this.state) {
       s = this._nextState;
   } else {
       s = this._nextState = assign({}, this.state);
   }
   
   // 函数的第一个参数拿到最新的state,调用
   if (typeof update == 'function') {
       update = update(assign({}, s), this.props);
   }
   
   // setState采用对象写法的对象,   
   // setState函数写法,调用函数拿到函数的返回值
   if (update) {
       assign(s, update);
   }

   if (update == null) return;
   
   // 回调函数放入组件的_renderCallbacks
   if (this._vnode) {
       if (callback) this._renderCallbacks.push(callback);
       enqueueRender(this);
   }
};

enqueueRender

  • c._dirty=true 表示组件是否处于更新状态。
    • 在首次渲染初始化时:c._dirty = true。render之后:c._dirty = false。
    • setState会检查组件是否处于更新状态,不是进入更新队列延迟更新,标记组件处于更新状态。
  • preact异步更新:将更新函数放置在promiose.then 执行
  • rerenderQueue基于_depth升序排序,更新从子组件开始更新,由下向上的更新。
    • 比如子组件setState之后将点击函数传递给父组件,父组件同样执行setState。
// 存储需要更新的组件
let rerenderQueue = [];

// 异步更新
const defer =
    typeof Promise == 'function'
        ? Promise.prototype.then.bind(Promise.resolve())
        : setTimeout;

export function enqueueRender(c) {
    if (
        (!c._dirty &&
        (c._dirty = true) &&
        rerenderQueue.push(c) &&
        !process._rerenderCount++)
    ) {
        defer(process);
    }
}

// 循环更新
function process() {
    let queue;
    while ((process._rerenderCount = rerenderQueue.length)) {
        queue = rerenderQueue.sort((a, b) => a._vnode._depth - b._vnode._depth);
        rerenderQueue = [];
        queue.some(c => {
            if (c._dirty) renderComponent(c);
        });
    }
}

// 记录当前更新队列的个数
process._rerenderCount = 0;

renderComponent

  • 调用组件的diff去更新。之后commitRoot执行保存在commitQueue的生命周期
function renderComponent(component) {
    let vnode = component._vnode,
        oldDom = vnode._dom,
        parentDom = component._parentDom;

    if (parentDom) {
        let commitQueue = [];
        const oldVNode = assign({}, vnode);
        oldVNode._original = vnode._original + 1;

        diff(
            parentDom,
            vnode,
            oldVNode,,
            commitQueue,
            oldDom == null ? getDomSibling(vnode) : oldDom,
        );
        commitRoot(commitQueue, vnode);
    
        if (vnode._dom != oldDom) {
            updateParentDomPointers(vnode);
        }
    }
}

forceUpdate

Component.prototype.forceUpdate = function(callback) {
    if (this._vnode) {
        this._force = true;
        if (callback) this._renderCallbacks.push(callback);
        enqueueRender(this);
    }
};