react的BatchUpdate
Rashomon511 opened this issue · 1 comments
前言
我在业务中经常用到setState,自以为对setState已经非常对了解,直到某一天遇到一个关于state批量更新的面试题,发现自己好像对setState的认识也没有那么全面,所以写这篇文章主要是希望彻底搞清楚setState的更新机制。
setState的更新策略
React中的setState有Batch模式(批量更新模式)和普通模式。
普通模式下,setState能够即时更新state,重新调用 render 方法,然后把render方法所渲染的最新的内容显示到页面上。
Batch模式下,React不会立刻修改state。而是把这个对象放到一个更新队列中,稍后才会从队列中把新的状态提取出来合并到 state中,然后再触发组件更新。
批量更新
react的setState是一个异步方法,React 会将所有的 setState 方法打包成一次进行更新,每次修改 state 都会进行更新。这样的设计主要是为了提高 UI 更新的性能,
我们知道 React 中 state 的改变会导致 UI 的更新。如果想要进行同步操作逻辑有两种方法。
1.通过异步实现
componentDidMount(){
setTimeout(() => {
this.setState({
value: this.state.value + 1
})
console.log('value', this.state.value)
}, 0)
}
2.通过回调函数实现
this.setState({
value: this.state.value + 1
}, () => {
console.log('value', this.state.value)
})
判断批量更新
在更新逻辑的部分,可以看到 React 会通过 batchingStrategy.isBatchingUpdates 判断当前的逻辑状态下是否需要进行批量更新
- 如果不是,那么就直接进行页面的批量更新,将之前累积的所有状态一次更新到组件上。
- 如果是,那么所有的组件状态不进行立即更新,而是将组件状态存放在一个叫 dirtyComponents 的数组中去,等待下次更新时机的到来再进行更新
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setProps, setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
}
为了实现上面的逻辑判断,React 将整个的函数执行过程包裹上了 Transaction,在函数执行前与执行后分别有 initialize 和 close 两个方法。这样的话 React 就有时机在函数执行过程中,涉及到 setState 的执行,都将缓存下来,在 close 的时候进入到 React 的 state 更新逻辑进行更新判断操作,并最终更新到前台的 DOM 上。
initialize、close源码
setState 的源码实现
在 setState 的源码实现中,传递过来的参数就被定义成了 partialState,从参数名以及参数的说明中就可以看到,这只是 state 的一部分。
默认都会调用 this.updater.enqueueSetState(this, partialState) 将 state 放进更新队列中去。
而如果有传递回调函数过来的话,会执行 this.updater.enqueueCallback(this, callback).
ReactComponent.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.'
);
if (__DEV__) {
warning(
partialState != null,
'setState(...): You passed an undefined or null state object; ' +
'instead, use forceUpdate().'
);
}
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback);
}
};
更新队列 ReactUpdateQueue 的定义
在上面的 setState 定义中,我们可以看到有一个 updater 的调用
function ReactComponent(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
ReactComponent.prototype.isReactComponent = {};
enqueueSetState 的定义
enqueueSetState: function(publicInstance, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(
publicInstance,
'setState'
);
if (!internalInstance) {
return;
}
var queue =
internalInstance._pendingStateQueue ||
(internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
},
enqueueUpdate的实现
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
ReactUpdates 中的 enqueueUpdate
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setProps, setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
}
ensureInjected实现:
function ensureInjected() {
invariant(
ReactUpdates.ReactReconcileTransaction && batchingStrategy,
'ReactUpdates: must inject a reconcile transaction class and batching ' +
'strategy'
);
}
注入了两个部分,ReactReconcileTransaction 以及 batchingStrategy。
ReactReconcileTransaction 主要用于在更新 state 时,页面 UI 元素的修正以及在执行生命周期函数时,处理好生命周期函数与其他用户自定义函数之间的执行顺序与逻辑
现在我们可以看一下面试题了
class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null;
}
};
// 0 0 2, 3
搬运自:
请问一下文中react的版本是多少啊?