react调度前的组件状态更新

1,006 阅读3分钟

react有多种状态更新的触发方式,譬如类组件中的setState,函数组件中的useState,那么在调度前,react在状态更新时究竟做了什么呢 ?

一、触发状态更新的方式

  • ReactDOM.render —— HostRoot
  • this.setState —— ClassComponent
  • this.forceUpdate —— ClassComponent
  • useState —— FunctionComponent

从上面可以看到react有三种触发更新的方式,三种方式的不同体现在保存状态的数据结构上,但它们的状态更新机制和流程是大体一样的。

二、状态更新的数据结构

每次状态更新会创建一个保存状态相关内容的update对象,不同更新方式创建的update对象有所不同,总共分为两类:

  • ClassComponent与HostRoot使用一种update结构
  • FunctionComponent单独使用一种update结构

ClassComponent与HostRoot的update结构

const update: Update<*> = {
  eventTime,
  lane,
  suspenseConfig,
  tag: UpdateState,
  payload: null,
  callback: null,

  next: null,
};
  • eventTime:任务时间,通过performance.now()获取的毫秒数。由于该字段在未来会重构,当前我们不需要理解他。
  • lane:优先级相关字段。当前还不需要掌握他,只需要知道不同Update优先级可能是不同的。

你可以将lane类比心智模型中需求的紧急程度。

  • suspenseConfig:Suspense相关,暂不关注。
  • tag:更新的类型,包括UpdateState | ReplaceState | ForceUpdate | CaptureUpdate
  • payload:更新挂载的数据,不同类型组件挂载的数据不同。对于ClassComponentpayloadthis.setState的第一个传参。对于HostRootpayloadReactDOM.render的第一个传参。
  • callback:更新的回调函数,在commit阶段的layout子阶段执行。
  • next:与其他Update连接形成链表。

三、状态的批量更新

当一次任务中触发多次状态更新时,react会将多个更新合并为一次渲染,这时候会有多个update,不同的update之间如何关联呢?

答案是:将每次更新的update加入到fiber.updateQueue对象中,update之间使用单向环状链表连接。

三种updateQueue分别是:

  • HostComponent
  • ClassComponent与HostRoot
  • FunctionComponent

HostComponent中props的更新

HostComponent的更新方式为在completeWork阶段对比新旧fiber上的props,被处理完的props会被赋值给workInProgress.updateQueue

const updatePayload = prepareUpdate(
      instance,
      type,
      oldProps,
      newProps,
      rootContainerInstance,
      currentHostContext,
  );
//updatePayload === updateQueue 
workInProgress.updateQueue = (updatePayload: any);

其中updatePayload在存在props差异时返回的结果为数组形式,他的偶数索引的值为变化的prop key,奇数索引的值为变化的prop value,并且updatePayload等于updateQueue

//获取updatePayload的例子
export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      style: null
    };
    this.onChange = this.onChange.bind(this);
  }
  onChange() {
    this.setState({
      style: {
        backgroundColor: "red"
      }
    });
  }
  render() {
    return (
      <div style={this.state.style} onClick={this.onChange}>
        HELLO
      </div>
    );
  }
}

上面代码中updateQueue的返回值:['style',null,'style',{backgroundColor:'red'}]

ClassComponent与HostRoot使用的UpdateQueue结构

const queue: UpdateQueue<State> = {
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
      pending: null,
    },
    effects: null,
  };
  • baseState:本次更新前该Fiber节点stateUpdate基于该state计算更新后的state

你可以将baseState类比心智模型中的master分支。

  • firstBaseUpdatelastBaseUpdate:本次更新前该Fiber节点已保存的Update。以链表形式存在,链表头为firstBaseUpdate,链表尾为lastBaseUpdate。之所以在更新产生前该Fiber节点内就存在Update,是由于某些Update优先级较低所以在上次render阶段Update计算state时被跳过。

你可以将baseUpdate类比心智模型中执行git rebase基于的commit(节点D)。

  • shared.pending:触发更新时,产生的Update会保存在shared.pending中形成单向环状链表。当由Update计算state时这个环会被剪开并连接在lastBaseUpdate后面。

你可以将shared.pending类比心智模型中本次需要提交的commit(节点ABC)。

  • effects:数组。保存update.callback !== nullUpdate

四、类组件的状态更新流程

this.setState内会调用this.updater.enqueueSetState方法。

Component.prototype.setState = function (partialState, callback) {
	......
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

enqueueSetState方法创建update,并且将update插入updateQueue,开始调度。

enqueueSetState(inst, payload, callback) {
  // 通过组件实例获取对应fiber
  const fiber = getInstance(inst);

  const eventTime = requestEventTime();
  const suspenseConfig = requestCurrentSuspenseConfig();

  // 获取优先级
  const lane = requestUpdateLane(fiber, suspenseConfig);

  // 创建update
  const update = createUpdate(eventTime, lane, suspenseConfig);

  update.payload = payload;

  // 赋值回调函数
  if (callback !== undefined && callback !== null) {
    update.callback = callback;
  }

  // 将update插入updateQueue
  enqueueUpdate(fiber, update);
  // 调度update
  scheduleUpdateOnFiber(fiber, lane, eventTime);
}

参考资料