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 的批更新