React源码解析之setState和forceUpdate

32次阅读

共计 3077 个字符,预计需要花费 8 分钟才能阅读完成。

一、enqueueSetState()
非异步方法中,无论调用多少个 setState,它们都会在最后一次setState 后,放入更新队列,然后执行一次统一的更新,详情请参考:
React.setState 之 state 批处理的机制 和 为什么 React.setState 是异步的?

作用:
React 节点的 fiber 对象创建update,并将该更新对象入队

源码:

//classComponent 初始化的时候拿到的 update 对象
const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    //inst 即调用 this.setState 时传进来的 this
    // 也就是 classComponent 实例

    // 通过 this 获取 fiber 对象
    //this._reactInternalFiber
    //this 本身有存储 fiber 对象 的属性,叫 _reactInternalFiber
    const fiber = getInstance(inst);
    // 计算当前时间,之前讲过 不讲了
    const currentTime = requestCurrentTime();
    // 异步加载的设置,暂时不讲
    const suspenseConfig = requestCurrentSuspenseConfig();
    // 计算 fiber 对象的过期时间
    const expirationTime = computeExpirationForFiber(
      currentTime,
      fiber,
      suspenseConfig,
    );
    // 创建 update 对象
    const update = createUpdate(expirationTime, suspenseConfig);
    //setState 传进来的要更新的对象
    update.payload = payload;
    //callback 就是 setState({},()=>{})的回调函数
    if (callback !== undefined && callback !== null) {if (__DEV__) {warnOnInvalidCallback(callback, 'setState');
      }
      update.callback = callback;
    }
    // 暂时不管
    if (revertPassiveEffectsChange) {flushPassiveEffects();
    }
    //update 入队
    enqueueUpdate(fiber, update);
    // 任务调度
    scheduleWork(fiber, expirationTime);
  },
};

解析:
(1)getInstance

//getInstance
export function get(key) {return key._reactInternalFiber;}

就是获取目标对象的 _reactInternalFiber 属性,即 this.setState 中的this

(2)requestCurrentTime ,请见:React 源码解析之 ReactDOM.render()

(3)computeExpirationForFiber ,请见:React 源码解析之 ExpirationTime

(4)createUpdate ,请见:React 源码解析之 Update 和 UpdateQueue

(5)注意下 payloadpayload 就是 setState 传进来的要更新的对象

this.setState({a:1},callback) 中的 {a:1} 即 payload
//====================
update.payload = payload;

(6)enqueueUpdate ,请见:React 源码解析之 Update 和 UpdateQueue

(7)scheduleWork,篇幅较长,会放在下篇讲。

二、enqueueForceUpdate()
作用:
强制让组件重新渲染,也是给 React 节点的 fiber 对象创建 update,并将该更新对象入队

源码:

  enqueueForceUpdate(inst, callback) {const fiber = getInstance(inst);
    const currentTime = requestCurrentTime();
    const suspenseConfig = requestCurrentSuspenseConfig();
    const expirationTime = computeExpirationForFiber(
      currentTime,
      fiber,
      suspenseConfig,
    );

    const update = createUpdate(expirationTime, suspenseConfig);
    // 与 setState 不同的地方
    // 默认是 0 更新,需要改成 2 强制更新
    update.tag = ForceUpdate;

    if (callback !== undefined && callback !== null) {if (__DEV__) {warnOnInvalidCallback(callback, 'forceUpdate');
      }
      update.callback = callback;
    }

    if (revertPassiveEffectsChange) {flushPassiveEffects();
    }
    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },

解析:
enqueueSetState() 方法的流程类似,唯一不同的是多了个手动修改属性 tag 的值:

// 与 setState 不同的地方
// 默认是 0 更新,需要改成 2 强制更新
update.tag = ForceUpdate;

可以看到 createUpdate() 方法中,初始化的 tag 值是UpdateState

// 创建 update 对象
export function createUpdate(
  expirationTime: ExpirationTime,
  suspenseConfig: null | SuspenseConfig,
): Update<*> {
  return {
    // export const UpdateState = 0;
    // export const ReplaceState = 1;
    // export const ForceUpdate = 2;
    // export const CaptureUpdate = 3;

    // 重点提下 CaptureUpdate,在 React16 后有一个 ErrorBoundaries 功能
    // 即在渲染过程中报错了,可以选择新的渲染状态(提示有错误的状态),来更新页面
    // 默认是 0 即更新
    tag: UpdateState, // 0 更新 1 替换 2 强制更新 3 捕获性的更新
  };
}

因此要改成 ForceUpdate,以便React 进行 Update 优先级排序

三、综上
执行 setStateforUpdateReact 进行更新的流程为:
(1)获取 this 上的 fiber 对象
(2)计算currentTime
(3)根据(1)fiber(2)currentTime计算 fiber 对象的 expirationTime
(4)根据(3)expirationTime 创建 update 对象
(5)将setState 中要更新的对象赋值到 (4)update.payload
(6)将setState 中要执行的 callback 赋值到 (4)update.callback
(7)update 入队updateQueue
(8)进行任务调度


(完)

正文完
 0