Skip to content

Latest commit

 

History

History
217 lines (167 loc) · 6.97 KB

how_setstate_works.md

File metadata and controls

217 lines (167 loc) · 6.97 KB

setState 主流程及源码

本篇文章介绍以下知识点:

  • setState 主流程及源码
  • 类组件和函数组件 fiber.memoizedState 的区别
  • 类组件编译后也是一个函数,React 是如何区分函数组件和类组件的

demo

准备工作

  • constructor 中第一行添加 debugger,类组件 Counter 初始化时会调用构造函数
  • handleClick 中第一行添加 debugger,当我们点击按钮时,setState 主流程便从这里开始
import React, { Component } from "react";
import ReactDOM from "react-dom";
class Counter extends Component {
  constructor(props) {
    debugger;
    super(props);
    this.state = {
      number: 0,
    };
  }
  handleClick = (event) => {
    debugger;
    this.setState({ number: 1 });
    this.setState({ number: 2 });
  };

  render() {
    console.log("render===", this.state);
    return <button onClick={this.handleClick}>{this.state.number}</button>;
  }
}

ReactDOM.render(<Counter />, document.getElementById("root"));

React.Component

我们知道类组件一定要继承于 React.Component 或者 React.PureComponent,这两个类位于 packages/react/src/ReactBaseClasses.js 文件中,React.Component 做的事情很简单。下面一步一步 debug 一下

刷新页面,首先进入我们的构造函数断点处

image

注意这个函数调用栈的顺序,可以在每个函数都打一个断点,多看几次类组件初始化的过程

点击下一步,进入 super(props) 函数,实际上就是我们的 React.Component

image

这里有三个需要注意的地方:

  • this.updater = updater || ReactNoopUpdateQueue;。当我们调用 this.setState 时,使用的就是 this.updater.enqueueSetState。这里只是简单的将 this.updater 初始化为空的 ReactNoopUpdateQueue。实际上真正的 this.updaterreact-dom 中初始化。react-domreact-native 等对于 this.updater 的实现都不尽相同。
  • Component.prototype.isReactComponent = {}; isReactComponent 用于后续在创建 fiber 节点时判断是不是类组件。如果函数原型存在 isReactComponent 则说明是类组件
  • Component.prototype.setState 这是我们调用 this.setState 时的逻辑

image

在创建 fiber 节点时需要判断当前组件是类组件还是函数组件 image

shouldConstruct 实现如下: image

React.Component 的简单实现如下:

const ReactNoopUpdateQueue = {
  isMounted: function (publicInstance) {
    return false;
  },
  enqueueForceUpdate: function (publicInstance, callback, callerName) {},
  enqueueReplaceState: function (
    publicInstance,
    completeState,
    callback,
    callerName
  ) {},
  enqueueSetState: function (
    publicInstance,
    partialState,
    callback,
    callerName
  ) {},
};
class Component {
  constructor(props, context, updater) {
    this.props = props;
    this.updater = updater || ReactNoopUpdateQueue;
    this.isReactComponent = {};
  }
  setState(partialState, callback) {
    this.updater.enqueueSetState(this, partialState);
  }
  forceUpdate(callback) {
    this.updater.enqueueForceUpdate(this, callback, "forceUpdate");
  }
}

实际上真正的 this.updater 的初始化在 adoptClassInstance 方法中:

image

类组件和函数组件的 fiber.memoizedState 的区别

我们在React.useReducer 原理及源码主流程章节中已经知道,函数组件对应的 fiber.memoizedState 是用来保存 hook 链表的。

在类组件中,其对应的 fiber.memoizedState 保存的是上一次更新的 this.state 的值

image

类组件的更新队列

React.useReducer 原理及源码主流程中我们知道如果多次调用 setCount,更新的队列会保存在 hook.queue 链表中

在类组件中,如果我们连续调用多次

this.setState({ number: 1 });
this.setState({ number: 2 });
this.setState({ number: 3 });

实际上更新队列保存在 fiber.updateQueue中,fiber.updateQueue.shared.pending 指向最后一个 this.setState() 生成的更新对象

image

fiber.updateQueue.sharedhook.queue 一样也是环状链表

类组件初始化流程

image

setState 主流程及源码

调用 this.setState 时,实际上调用的是 this.updater.enqueueSetState

function get(key) {
  return key._reactInternals;
}
function createUpdate(eventTime, lane) {
  var update = {
    eventTime: eventTime,
    lane: lane,
    tag: UpdateState,
    payload: null,
    callback: null,
    next: null,
  };
  return update;
}

// enqueueUpdate构造环状列表
function enqueueUpdate(fiber, update) {
  var updateQueue = fiber.updateQueue;

  if (updateQueue === null) {
    return;
  }

  var sharedQueue = updateQueue.shared;
  var pending = sharedQueue.pending;

  if (pending === null) {
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }

  sharedQueue.pending = update;
}
function requestEventTime() {
  // 任务是有优先级的,优先级高的会打断优先级低的
  // 如果低优先级任务超时了,则优先级高的不能再打断优先级低的任务
  return performance.now(); // 程序从启动到现在的时间,是用来计算任务的过期时间的
}
var classComponentUpdater = {
  isMounted: isMounted,
  enqueueSetState: function (inst, payload, callback) {
    var fiber = get(inst);
    var eventTime = requestEventTime();
    var lane = requestUpdateLane(fiber);
    var update = createUpdate(eventTime, lane);
    update.payload = payload;

    if (callback !== undefined && callback !== null) {
      update.callback = callback;
    }
    enqueueUpdate(fiber, update);
    scheduleUpdateOnFiber(fiber, lane, eventTime);
  },
};

class Component {
  constructor() {
    // 这里为了简化流程,直接初始化this.updater为classComponentUpdater
    this.updater = classComponentUpdater;
  }
  setState(partialState, callback) {
    this.updater.enqueueSetState(this, partialState);
  }
}

主流程图:

image

fiber.updateQueue 会在下一个微任务中在 processUpdateQueue 函数中处理