getInitialState(){
return {data:[]};
},
componentDidMount(){
var data = [ { author: "Pete Hunt", text: "This is one comment" },
{ author: "Jordan Walke", text: "This is *another* comment" } ];
(function(data){
this.setState({data:data});
console.log(this.state.data);
}).call(this,data);
},
为什么log里打出来的data是[]呢?
这就涉及到了state batch更新的问题。
先看看下面这个demo
var Example = React.createClass({
getInitialState: function() {
return {
value: 0,
};
},
handleClick: function(){
this.setState({value: this.state.value + 1});
console.log(this.state.value);
this.setState({value: this.state.value + 1});
console.log(this.state.value);
},
render: function(){
console.log(this.state.value);
return (
<div className="parent">
<button onClick={this.handleClick}>click me</button>
</div>
);
}});
React.render(, document.body);
考虑这两个问题,当点击 button 时:1、 render 方法被调用了几次?2、 点击button后,handleClick 中2 次 setState 时 this.state.value 值是多少?render中的this.state.value又是多少?
知道结果后,你能解释为什么 render 方法只被调用了 1 次,且两次 setState 时 this.state.value 都是 0 么?
这一切,都要归功于 React 的 batchUpdate 设计,但是 batchUpdate 究竟是怎么实现的呢?让我们深入到 React 的源码中一探究竟。
我们从 DOM 事件触发开发对整个链路进行一下梳理:
当一次 DOM 事件触发后,ReactEventListener.dispatchEvent 方法会被调用,该方法监听页面时间并派发。而这个方法并不是急着去调用我们在 JSX 中指定的各种回调,而是调用了
ReactUpdates.batchedUpdates
这个方法就是 React 整个 batchUpdate 思想的核心。该方法会执行一个 transaction,那么这个transaction究竟干了什么?
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
assign(
ReactDefaultBatchingStrategyTransaction.prototype,
Transaction.Mixin,
{
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
}
}
);
注意它被 assgin 的 getTransactionWrappers 方法,返回了一个常量 TRANSACTION_WRAPPERS。
var NESTED_UPDATES = {
initialize: function() {
this.dirtyComponentsLength = dirtyComponents.length;
},
close: function() {
if (this.dirtyComponentsLength !== dirtyComponents.length) {
// Additional updates were enqueued by componentDidUpdate handlers or
// similar; before our own UPDATE_QUEUEING wrapper closes, we want to run
// these new updates so that if A's componentDidUpdate calls setState on
// B, B will update before the callback A's updater provided when calling
// setState.
dirtyComponents.splice(0, this.dirtyComponentsLength);
flushBatchedUpdates();
} else {
dirtyComponents.length = 0;
}
}
};
var UPDATE_QUEUEING = {
initialize: function() {
this.callbackQueue.reset();
},
close: function() {
this.callbackQueue.notifyAll();
}
};
var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];
transaction的perform执行的的内容就是找到事件对应的所有节点并依次对这些节点触发事件。而
我们需要关注的就是 NESTED_WRAPPER 中的 close 方法,也就是这个方法指明了当所有的事件触发响应结束后,flushBatchUpdates()。
下面给大家看一个简单的流程图。
这是当我们点击button后的整个流程。
现在我们理解了,当点击button后,首先会调用batchUpdate,而batchUpdate 功能都是通过执行一个transaction 实现的,该transaction中的method中包含了各个组件的onClick回调,若回调中包含了this.setState,则通过ReactUpdates.enqueueUpdate进行更新,大概的流程如下
this.setState // ReactComponent.js
...
this.updater.enqueueSetState // ReactUpdateQueue.js
...
获取当前组件的 pendingStateQueue,并将新的 state push 进去 // ReactUpdateQueue.js
...
enqueueUpdate // ReactUpdates.js
...
if(当前不在一次 batchUpdate 的过程中) // ReactUpates.js
执行 batchingStreategy.batchUpdates 方法
else
将当前 component 存入 dirtyComponents 数组中
...
if (setState 方法存在 callback) // ReactComponent.js
调用 this.updater.enqueueCallback 将 callback 存入队列中
...
综上所述,this.setState 调用后,新的 state 并没有马上生效,而是通过 ReactUpdates.batchedUpdate 方法存入临时队列中。当一个 transaction 完成后,才通过 ReactUpdates.flushBatchedUpdates 方法将所有的临时 state merge 并计算出最新的 props 及 state。
善用官方文档:
void setState(
function|object nextState,
[function callback]
)
The second (optional) parameter is a callback function that will be executed once setState is completed and the component is re-rendered.
所以正确做法是
this.setState(
Object.assign({}, { data }),
() => console.log(this.state)
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。