api解析: setState(updater, [callback])
updater: 更新数据 FUNCTION/OBJECT
callback: 更新成功后的回调 FUNCTION
// updater - Function
this.setState((prevState, props) => {
return {counter: prevState.counter + props.step};
});
// update - Object
this.setState({quantity: 2})
setState的特点:
1.异步:react通常会集齐一批需要更新的组件,然后一次性更新来保证渲染的性能
2.浅合并 Objecr.assign()
setState问题与解决
- 在使用setState改变状态之后,立刻通过this.state去拿最新的状态
解决: componentDidUpdate或者setState的回调函数里获取
// setState回调函数
changeTitle: function (event) {
this.setState({ title: event.target.value }, () => this.APICallFunction());
},
APICallFunction: function () {
// Call API with the updated value
}
- 有一个需求,需要在在onClick里累加两次,使用对象的方法更新,则只会加一次
解决: 使用updater function
onClick = () => {
this.setState({ index: this.state.index + 1 });
this.setState({ index: this.state.index + 1 });
}
// 最后解析为,后面的数据会覆盖前面的更改,所以最终只加了一次.
Object.assign(
previousState,
{index: state.index+ 1},
{index: state.index+ 1},
)
//正确写法
onClick = () => {
this.setState((prevState, props) => {
return {quantity: prevState.quantity + 1};
});
this.setState((prevState, props) => {
return {quantity: prevState.quantity + 1};
});
}
注意:
1.不要在render()函数里面写setstate(),除非你自己定制了shouldComponentUpdate方法,要不然会引起无限循环
render() {
//this.setState
return(
//...dom
)
}
2.为什么不能使用第一个方式更新
react为了实现高效render, state其实是一个队列,setState是将数据插入队列中,使用第一种不会触发渲染, react提供了setState的实例方法可以触发render,可以看后面的源码
// 1
this.state.num = 1
// 2
this.setState({
num: this.state.num + 1
})
3.对数组和对象等引用对象操作时,使用返回新对象的方法
array: 不要使用push、pop、shift、unshift、splice可使用concat、slice、filter、扩展语法
object: Object.assgin/扩展语法
setState更新机制
如图: pending queue 和 update queue
setState源码
this.setState()
ReactComponent.prototype.setState = function (partialState, callback) {
// 将setState事务放进队列中
// this.updater就是ReactUpdateQueue, this是组件的实例
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
enqueueSetState()
enqueueSetState: function (publicInstance, partialState) {
// 获取当前组件的instance
// 实例中有两个非常重要的属性:_pendingStateQueue(待更新队列) 与 _pendingCallbacks(更新回调队列)
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
// 初始化待更新队列
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
// 将要更新的state放入一个数组里
queue.push(partialState);
// 将要更新的component instance也放在一个队列里
enqueueUpdate(internalInstance);
}
enqueueUpdate
当前如果正处于创建/更新组件的过程,就不会立刻去更新组件,而是先把当前的组件放在dirtyComponent里,所以不是每一次的setState都会更新组件
function enqueueUpdate(component) {
// 如果没有处于批量创建/更新组件的阶段,则处理update state事务
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 如果正处于批量创建/更新组件的过程,将当前的组件放在dirtyComponents数组中
dirtyComponents.push(component);
}
batchingStrategy
var ReactDefaultBatchingStrategy = {
// 用于标记当前是否出于批量更新
isBatchingUpdates: false,
// 当调用这个方法时,正式开始批量更新
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// 如果当前事务正在更新过程在中,则调用callback,既enqueueUpdate
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
// 否则执行更新事务
return transaction.perform(callback, null, a, b, c, d, e);
}
}
};
transaction
initalize(空函数) -> perform(anyMethos) -> close
// 将isBatchingUpdates置为false
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
// 循环所有dirtyComponent,调用updateComponent来执行所有的生命周期方法,componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate 最后实现组件的更新
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
setState面试
面试官:“react中setState是同步的还是异步?”
我:“异步的,setState不能立马拿到结果。”
面试官:“那什么场景下是异步的,可不可能是同步,什么场景下又是同步的?”
下题结果是:
class App extends React.Component {
state = { val: 0 }
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val);
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
}, 0)
}
render() {
return <div>{this.state.val}</div>
}
}
// 结果就为 0, 0, 2, 3
总结
1.setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
2.setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
3.setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
1、合成事件中的setState
合成事件,react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件
合成事件中也有batchedUpdates方法,是通过同样的事务完成的
class App extends Component {
state = { val: 0 }
increment = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新前的val --> 0
}
render() {
return (
<div onClick={this.increment}>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
2、生命周期函数中的setState
整个生命周期中就是一个事物操作,所以标识位isBatchingUpdates = true,所以流程到了enqueueUpdate()时,实例对象都会加入到dirtyComponents 数组中
class App extends Component {
state = { val: 0 }
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的还是更新前的值 --> 0
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
3、原生事件中的setState
原生事件是指非react合成事件,原生自带的事件监听 addEventListener ,或者也可以用原生js、jq直接 document.querySelector().onclick 这种绑定事件的形式都属于原生事件
原生事件绑定不会通过合成事件的方式处理,自然也不会进入更新事务的处理流程。setTimeout也一样,在setTimeout回调执行时已经完成了原更新组件流程,不会放入dirtyComponent进行异步更新,其结果自然是同步的。
class App extends Component {
state = { val: 0 }
changeValue = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新后的值 --> 1
}
componentDidMount() {
document.body.addEventListener('click', this.changeValue, false)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
4、setTimeout中的setState
基于event loop的模型下, setTimeout 中里去 setState 总能拿到最新的state值。
class App extends Component {
state = { val: 0 }
componentDidMount() {
setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出更新后的值 --> 1
}, 0)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
5、批量更新
在 setState 的时候react内部会创建一个 updateQueue ,通过 firstUpdate 、 lastUpdate 、 lastUpdate.next 去维护一个更新的队列,在最终的 performWork 中,相同的key会被覆盖,只会对最后一次的 setState 进行更新
class App extends Component {
state = { val: 0 }
batchUpdates = () => {
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
}
render() {
return (
<div onClick={this.batchUpdates}>
{`Counter is ${this.state.val}`} // 1
</div>
)
}
}
引发感想来源: