React 源码解读之 setState 和 forceUpdate

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」。

react 版本:v17.0.3

React 中,class组件创建更新的方式有三种,分别是 ReactDOM.render、setState、forceUpdate。在《React 源码解读之 ReactDOM.render》已对 ReactDOM.render 做了介绍,本文将介绍setState和forceUpdate这两种创建更新的方式。

setState

当我们在class组件中发起一个更新时,通常会调用 setState 方法。setState 是触发组件更新的主要方式,它会将组件 state 的更改加入到更新队列中,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。

setState 的定义在React.Component 中:

// react/src/ReactBaseClasses.js

// 在原型上添加 setState 方法,使用 setState 来更新组件
// partialState 要更新的 state,可以是 Object/Function
// callback 组件更新完成后要执行的回调函数 setState({}, callback)
Component.prototype.setState = function(partialState, callback) {
// 判断 partialState 是否符合条件,如果不符合则抛出 Error
  if (
    typeof partialState !== 'object' &&
    typeof partialState !== 'function' &&
    partialState != null
  ) {
    throw new Error(
      'setState(...): takes an object of state variables to update or a ' +
        'function which returns an object of state variables.',
    );
  }
  // state 的更新机制,在 react-dom 中实现
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
复制代码

可以看到,setState 的第一个参数必须是对象或者是函数,并且不能为空,否则就会报错。然后调用 enqueueSetState 初始化 setState 。

enqueueSetState

enqueueSetState 的作用,就是在调用 setState 时,给fiber对象创建一个 update,然后将该update对象添加到updateQueue中,并进入任务调度流程。

// react-reconciler/src/ReactFiberClassComponent.new.js

// inst 即调用 this.setState 时传递进来的this,也就是 class组件实例

// payload 即调用 this.setState 时传递进来的初始state,

// 可以是一个Object对象,也可以是一个function,该function返回一个Object对象
// callback 即调用 this.setState 时传递进来的 回调函数,该回调函数是可选的
// 如果我们想在调用 setState 后立即读取 this.state,就可以使用在 callback 中获取
enqueueSetState(inst, payload, callback) {
    // 通过 class组件实例获取 fiber 对象
    // this._reactInternals 在 this 上通过 _reactInternals 属性存储fiber对象
    const fiber = getInstance(inst);
    // 获取当前时间,通过 performance.now() 或 Date.now() 获取的秒数
    const eventTime = requestEventTime();
    // 创建一个优先级变量(lane模型,通常称为车道模型)
    const lane = requestUpdateLane(fiber);
    // 创建一个 update 对象
    const update = createUpdate(eventTime, lane);
    // 将 stateState 传进来的要更新的对象添加到 update 上
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
        if (__DEV__) {
            warnOnInvalidCallback(callback, 'setState');
        }
        // 将 setState 的第二个参数 callback 添加到 update 对象上
        update.callback = callback;
    }
    // 将新建的 update 添加到 update链表中
    enqueueUpdate(fiber, update, lane);
    // 进入任务调度流程
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
    
    // ...
},
复制代码
  • 在 enqueueSetState 中,首先通过 getInstance 方法获取存储在ClassComponent实例上的fiber对象。

    // 通过 class组件实例获取 fiber 对象
    // this._reactInternals 在 this 上通过 _reactInternals 属性存储fiber对象
    const fiber = getInstance(inst);
    复制代码

getInstance 方法源码如下:

// shared/ReactInstanceMap.js

export function get(key) {
  return key._reactInternals;
}
复制代码

下面是打印出来的ClassComponent的 this,可以看到,在 this 上通过 _reactInternals 属性存储着fiber对象。

  • 然后计算当前时间和更新的优先级,根据当前时间和更新优先级创建一个新的update对象,并将setState传递进来的要更新的state对象和callback添加到该update对象上:

    // 获取当前时间,通过 performance.now() 或 Date.now() 获取的秒数
    const eventTime = requestEventTime();
    
    // 创建一个优先级变量(lane模型,通常称为车道模型)
    const lane = requestUpdateLane(fiber);
    
    // 创建一个 update 对象
    const update = createUpdate(eventTime, lane);
    
    // 将 stateState 传进来的要更新的state对象添加到 update 上
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
        
      	// ...	
      
        // 将 setState 的第二个参数 callback 添加到 update 对象上
        update.callback = callback;
    }
    复制代码
  • 接下来将update对象添加到updateQueue中,updateQueue是一个环形链表:

    // 将新建的 update 添加到 update链表中
    enqueueUpdate(fiber, update, lane);
    复制代码
  • 最后调用scheduleUpdateOnFiber方法,进入任务调度流程

    // 进入任务调度流程
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
    复制代码

forceUpdate

forceUpdate 将会强制让组件重新渲染。当有些变量不在 state 上,但是又想要这个变量更新;或者 state 里的某个变量层次太深,更新的时候没有自动触发render。这些时候我们可以手动调用forceUpdate强制触发render。

forceUpdate 的定义在React.Component 中:

// react/src/ReactBaseClasses.js

// 在Component的深层次改变但未调用setState时,使用该方法,强制Component更新一次,无论 props/state 是否更新
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
复制代码

可以看到,forceUpdate 调用的是 enqueueForceUpdate 来触发render。

enqueueForceUpdate

enqueueForceUpdate 的作用,就是在调用 forceUpdate 时,给fiber对象创建一个 update,然后将该update对象添加到updateQueue中,并进入任务调度流程。

// react-reconciler/src/ReactFiberClassComponent.new.js

enqueueForceUpdate(inst, callback) {
    // 通过 class组件实例获取 fiber 对象
    // this._reactInternals 在 this 上通过 _reactInternals 属性存储fiber对象
    const fiber = getInstance(inst);
    // 获取当前时间,通过 performance.now() 或 Date.now() 获取的秒数
    const eventTime = requestEventTime();
    // 创建一个优先级变量(lane模型,通常称为车道模型)
    const lane = requestUpdateLane(fiber);
    // 创建一个 update 对象
    const update = createUpdate(eventTime, lane);

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

    if (callback !== undefined && callback !== null) {
        if (__DEV__) {
            warnOnInvalidCallback(callback, 'forceUpdate');
        }
        // 将 setState 的第二个参数 callback 添加到 update 对象上
        update.callback = callback;
    }
    // 将新建的 update 添加到 update链表中
    enqueueUpdate(fiber, update, lane);
    // 进入任务调度流程
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);

    // ...
},
复制代码

enqueueForceUpdate 方法的流程与enqueueSetState类似,唯一的不同是修改了属性 tag 的值:

// 与 setState 不同的地方
// update.tag 默认为 0,即更新,将其改成 2 ,即需要强制更新
update.tag = ForceUpdate;
复制代码

在创建update对象时,tag属性的值默认是 0 ,即更新,在执行forceUpdate时,需要将tag属性的值改为 2 ,即需要强制更新。

在 createUpdate 方法中可以看到,tag属性的初始值是 UpdateState:

//react-reconciler/src/ReactUpdateQueue.new.js

export function createUpdate(eventTime: number, lane: Lane): Update<*> {
  const update: Update<*> = {
    eventTime,
    lane,
  
    // export const UpdateState = 0;   // 更新
    // export const ReplaceState = 1;  // 替换
    // export const ForceUpdate = 2;   // 强制更新
    // export const CaptureUpdate = 3; // 捕获性更新

    tag: UpdateState, // tag属性的初始值是 UpdateState
    payload: null,
    callback: null,

    next: null,
  };
  return update;
}
复制代码

由于是forceUpdate触发的更新,因此需要将tag改成ForceUpdate,以便 React 进行Update 的优先级排序。

总结

setState 是执行组件更新的主要方式,forceUpdate 是强制让组件执行更新,两者的更新流程都相似:

  1. 获取ClassComponent的this对象上的fiber对象

  2. 计算当前时间和更新优先级

  3. 根据当前时间和更新优先级创建update对象

  4. 将setState中要更新的state对象和要执行的callback添加到update对象上

  5. 将当前update对象加入到updateQueue队列中

  6. 进入任务调度流程

两者唯一的不同是执行forceUpdate时 update对象的tag属性值更改成了 ForceUpdate。update对象的tag属性值默认是UpdateState,执行forceUpdate时改成ForceUpdate,便于React进行Update优先级的排序。

Supongo que te gusta

Origin juejin.im/post/7066046776863621127
Recomendado
Clasificación