react setState源码解析

setState特点:
setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。
setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在updateComponent更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。

setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。

简易版实现:


//更新策略
let batchingStrategy = {
  // 默认是非批量更新模式
  isBatchingUpdates: false,
  // 脏组件 组件的状态改变了
  dirtyComponents: [],
  // 批量更新的方法
  batchedUpdates() {},
};

//事务,react根据各种事务执行操作,点击事件是个事务,在事务执行的时候,会先执行所有的initialize方法标志批处理开启,然后调用传递的回调,最后调用所有的close方法进行更新
class Transaction {
  constructor(wraps) {
    this.wraps = wraps || [];
  }

  perform(fn) {
    this.wraps.forEach((wrap) => wrap.initialize());
    fn.call();
    this.wraps.forEach((wrap) => wrap.close());
  }
}
//创建事务实例
let transaction = new Transaction([
  {
    initialize() {
      // 开启批量更新模式
      batchingStrategy.isBatchingUpdates = true;
    },
    close() {
      // 关闭批量更新模式
      batchingStrategy.isBatchingUpdates = false;
      // 将脏组件全部更新
      batchingStrategy.dirtyComponents.forEach((component) =>
        component.updateComponent()
      );
    },
  },
]);

//模拟合成事件
window.trigger = function (event, method) {
  let component = event.target.component;
  //合成事件传递的回调,本文即是点击事件
  transaction.perform(component[method].bind(component));
};

//和更新相关的类
class Updater {
  constructor(component) {
    this.component = component;
    //批量存储setState
    this.pendingStates = [];
  }

  addState(partcialState) {
    this.pendingStates.push(partcialState);
    // 判读是否批量更新,是批量更新,就先把组件缓存起来
    // 不是批量更新,直接更新
    batchingStrategy.isBatchingUpdates
      ? batchingStrategy.dirtyComponents.push(this.component)
      : this.component.updateComponent();
  }
}

class Component {
  constructor(props) {
    this.props = props;
    this.$updater = new Updater(this);
  }
  
  //渲染相关
  renderElement() {
  	//虚拟dom转换的真实dom
    let htmlStr = this.render();
    let div = document.createElement("div");
    div.innerHTML = htmlStr;
    this.domElement = div.children[0];
    // 将组件实例挂到dom元素上
    this.domElement.component = this;
    return this.domElement;
  }

  mount(container) {
    container.appendChild(this.renderElement());
  }
  
  //setState时根据是否开启批处理选择是否更新组件
  setState(partcialState) {
    this.$updater.addState(partcialState);
  }

  updateComponent() {
  	//进行多个setState合并成一个
    this.$updater.pendingStates.forEach((partcialState) =>
      Object.assign(this.state, partcialState)
    );
    this.$updater.pendingStates.length = 0;
    let oldElement = this.domElement;
    let newElement = this.renderElement();
    //更新dom
    oldElement.parentElement.replaceChild(newElement, oldElement);
  }
}

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0,
    };
  }

  add() {
    this.setState({
      number: this.state.number + 1,
    });
    console.log(this.state); //0
    this.setState({
      number: this.state.number + 1,
    });
    console.log(this.state); //0
	
	//因为是宏任务,此时批处理已经结束,batchingStrategy.isBatchingUpdates = false,setState调用addState(partcialState)时,同步更新
    setTimeout((_) => {
      this.setState({
        number: this.state.number + 1,
      });
      console.log(this.state); //2

      this.setState({
        number: this.state.number + 1,
      });
      console.log(this.state); //3
    });
  }

  render() {
    return `<button οnclick="trigger(event,'add')" >${this.props.name}:${this.state.number}</button>`;
  }
}

源码:

ReactComponent.prototype.setState = function(partialState, callback) {
  //setState核心
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

enqueueSetState

  enqueueSetState: function(publicInstance, partialState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setState',
    );
	
	//internalInstance保存setState传入的state
    var queue =
      internalInstance._pendingStateQueue ||
      (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

    enqueueUpdate(internalInstance);
  },

enqueueUpdate

function enqueueUpdate(internalInstance) {
  ReactUpdates.enqueueUpdate(internalInstance);
}

enqueueUpdate

function enqueueUpdate(component) {
  ensureInjected();
   
   //如果不在批量更新中,则开始更新组件 
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  //在批量更新中,则通过dirtyComponents保存需要更新的组件
  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}

batchingStrategy=>ReactDefaultBatchingStrategy

  • 用事务的方式批量的进行component的更新
  • 事务在执行 perform 之前,先执行所有 wrapper 中的 initialize 方法;perform 完成之后(即 method 执行后)再执行所有的 close 方法。一组 initialize 及 close 方法称为一个 wrapper,从上面的示例图中可以看出 Transaction 支持多个 wrapper 叠加
  • 具体到实现上,React 中的 Transaction 提供了一个 Mixin 方便其它模块实现自己需要的事务。而要使用 Transaction 的模块,除了需要把 Transaction 的 Mixin 混入自己的事务实现中外,还需要额外实现一个抽象的 getTransactionWrappers 接口。这个接口是 Transaction 用来获取所有需要封装的前置方法(initialize)和收尾方法(close)的,因此它需要返回一个数组的对象,每个对象分别有 key 为 initialize 和 close 的方法。
var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,

  batchedUpdates: function(callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      //通过事务的方式进行批量更新
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  },
};

module.exports = ReactDefaultBatchingStrategy;

// 定义复位 wrapper
var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function () {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  }
};

// 定义批更新 wrapper
var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

function ReactDefaultBatchingStrategyTransaction() {
  this.reinitializeTransaction();
}

_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
  getTransactionWrappers: function () {
    return TRANSACTION_WRAPPERS;
  }
});

var transaction = new ReactDefaultBatchingStrategyTransaction();

通过原型合并,事务的close 方法,先把 isBatchingUpdates 复位,再发起一个 DOM 的批更新

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值