一.几个开发中经常会遇到的问题
以下几个问题是我们在实际开发中经常会遇到的场景,下面用几个简单的示例代码来还原一下。
1.setState是同步还是异步的,为什么有的时候不能立即拿到更新结果而有的时候可以?
1.1 钩子函数和React合成事件中的
componentDidMount() {
console.log('parent componentDidMount');
}
render() {
return (
<div>
<SetState2></SetState2>
<SetState></SetState>
</div>
);
}
组件内部放入同样的代码,并在 componentDidMount
中放入一段同步延时代码,打印延时时间:
setState
不会立即更新
2.所有组件使用的是同一套更新机制,当所有组件 didmount
,然后执行更新
3.更新时会把每个组件的更新合并,每个组件只会触发一次更新的生命周期。
componentDidMount() {
console.log('parent componentDidMount');
}
render() {
return (
<div>
<SetState2></SetState2>
<SetState></SetState>
</div>
);
}
组件内部放入同样的代码,并在 componentDidMount
中放入一段同步延时代码,打印延时时间:
setState
2.所有组件使用的是同一套更新机制,当所有组件 didmount
,然后执行更新
3.更新时会把每个组件的更新合并,每个组件只会触发一次更新的生命周期。
1.2 异步函数和原生事件中的 setTimeout
中调用
componentDidMount() {
setTimeout(() => {
console.log('调用setState');
this.setState({
index: this.state.index + 1
})
console.log('state', this.state.index);
console.log('调用setState');
this.setState({
index: this.state.index + 1
})
console.log('state', this.state.index);
}, 0);
}
执行结果:
说明:
1.在父组件 setState
同步更新
2.为什么有时连续两次
componentDidMount() {
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
}
1
1
setstate
会被合并成一次
2.使用函数传递 github
上 partialState
: _pendingStateQueue
:当前组件等待执行更新的 isBatchingUpdates
:react用于标识当前是否处于批量更新状态,所有组件公用
transcation
:react的事务机制,在被事务调用的方法外包装n个 waper.init
、被调用方法、 FLUSH_BATCHED_UPDATES
:用于执行更新的 close
方法
2.执行过程
对照上面流程图的文字说明,大概可分为以下几步:
1.将setState传入的 waper
方法,遍历待更新组件队列依次执行更新。
5.执行生命周期 state
进行合并,获得最终要更新的state对象,并将队列置为空。
7.执行生命周期 componentWillUpdate
。
9.执行真正的更新, componentDidUpdate
。
三.总结
1.钩子函数和合成事件中:
在 react
仍然处于他的更新机制中,这时 setState
,都会不会执行更新,而是将要更新的 _pendingStateQueue
,将要更新的组件存入 didmount
后会将 setState
。
2.异步函数和原生事件中
由执行机制看, setState
时,如果 isBranchUpdate
被设置为false,根据上面的流程,这时再调用 partialState
合并机制
componentDidMount() {
setTimeout(() => {
console.log('调用setState');
this.setState({
index: this.state.index + 1
})
console.log('state', this.state.index);
console.log('调用setState');
this.setState({
index: this.state.index + 1
})
console.log('state', this.state.index);
}, 0);
}
执行结果:
说明:
1.在父组件
setState
同步更新
2.为什么有时连续两次
componentDidMount() {
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
}
1
1
setstate
会被合并成一次
2.使用函数传递 github
上 partialState
: _pendingStateQueue
:当前组件等待执行更新的 isBatchingUpdates
:react用于标识当前是否处于批量更新状态,所有组件公用
transcation
:react的事务机制,在被事务调用的方法外包装n个 waper.init
、被调用方法、 FLUSH_BATCHED_UPDATES
:用于执行更新的 close
方法
componentDidMount() {
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
}
1
1
setstate
2.使用函数传递 github
上 partialState
: _pendingStateQueue
:当前组件等待执行更新的 isBatchingUpdates
:react用于标识当前是否处于批量更新状态,所有组件公用
transcation
:react的事务机制,在被事务调用的方法外包装n个 waper.init
、被调用方法、 FLUSH_BATCHED_UPDATES
:用于执行更新的 close
方法
2.执行过程
对照上面流程图的文字说明,大概可分为以下几步:
1.将setState传入的
waper
方法,遍历待更新组件队列依次执行更新。5.执行生命周期
state
进行合并,获得最终要更新的state对象,并将队列置为空。7.执行生命周期
componentWillUpdate
。9.执行真正的更新,
componentDidUpdate
。
三.总结
1.钩子函数和合成事件中:
在 react
仍然处于他的更新机制中,这时 setState
,都会不会执行更新,而是将要更新的 _pendingStateQueue
,将要更新的组件存入 didmount
后会将 setState
。
2.异步函数和原生事件中
由执行机制看, setState
时,如果 isBranchUpdate
被设置为false,根据上面的流程,这时再调用 partialState
合并机制
我们看下流程中
state
暂存队列的,最后返回一个合并后的
_processPendingState: function (props, context) {
var inst = this._instance;
var queue = this._pendingStateQueue;
var replace = this._pendingReplaceState;
this._pendingReplaceState = false;
this._pendingStateQueue = null;
if (!queue) {
return inst.state;
}
if (replace && queue.length === 1) {
return queue[0];
}
var nextState = _assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
}
return nextState;
},
我们只需要关注下面这段代码:
Object.assign(
nextState,
{index: state.index+ 1},
{index: state.index+ 1}
)
如果传入的是函数,函数的参数preState是前一次合并后的结果,所以计算结果是准确的。
4. setstate
4. setstate
在componentDidMount()中,你 可以立即调用setState()。它将会触发一次额外的渲染,但是它将在浏览器刷新屏幕之前发生。这保证了在此情况下即使render()将会调用两次,用户也不会看到中间状态。谨慎使用这一模式,因为它常导致性能问题。在大多数情况下,你可以 在constructor()中使用赋值初始状态来代替。然而,有些情况下必须这样,比如像模态框和工具提示框。这时,你需要先测量这些DOM节点,才能渲染依赖尺寸或者位置的某些东西。
以上是官方文档的说明,不推荐直接在
setState
,由上面的分析: setState
,就会在未来再进行一次 componentDidMount
我们可以调用接口,再回调中去修改 componentDidMount
中 componentWillUpdate
setState
。
由上面的流程图很容易发现,在它们里面调用
setState
时使用函数传递 state
。