React 官方文档给的三点描述:
- 不要直接修改state
- state的更新可能是异步的
- state的更新会被合并
一、不要直接修改state
文档并没有解释原因,只说明构造函数中是唯一可以给 state 直接赋值的地方。
可能的原因:
- 收集状态改变 数据驱动型的框架模式: 状态改变 ---> 视图自动改变。
改变状态的一般操作是直接赋值 ;另一种就是函数setxxx。
Vue 就是利用Object.defineProperty、proxy监听对象,实现状态收集。
react中并不存在依赖收集,因此,直接赋值并不能让React监听到state的变化。
- 更好的维护数据不可变性(immutable)。 setState内部实际会返回一个新的state。如果直接修改state,会带来很多棘手的副作用(可读性变差、不可预期bug)。
// 最常见可变 就是引用赋值
const person = {player: {name: '456'}};
const person1 = person;
person.player.name = 123
// var ==》 let const 也是在强调不可变。减少var 带来的各种副作用
只有纯的没有副作用的函数,才是合格的函数。
- 统一去更新state,利于维护和控制; 固定的setState的函数,会让它的整个渲染/更新过程变得更加可控,包括实施各类性能优化(diff、持久化数据结构)。
二、state的更新可能是异步的
异步的?那算宏任务、微任务范畴?
案例:
handleClick() {
const base = 5
setTimeout(() => {
console.log('宏任务触发')
}, 0)
Promise.resolve().then(()=>{
console.log('微任务触发')
})
this.setState({
count: this.state.count + base
}, () => {
console.log('更新后的值:', this.state.count)
})
console.log('methods end');
}
// 执行结果, 说明setState 先于微任务
handleClick2() {
setTimeout(()=>{
const base = 5
setTimeout(() => {
console.log('宏任务触发2')
}, 0)
Promise.resolve().then(()=>{
console.log('微任务触发2')
})
this.setState({
count: this.state.count + base
}, () => {
console.log('更新后的值2:', this.state.count)
})
console.log('methods end2');
}, 10)
}
// 执行结构 setState 同步
"更新后的值2:" 5
"methods end2"
"微任务触发2 "
"宏任务触发2"
setState本身并不是异步,只是因为react的性能优化机制处理后,体现为异步
小结论:
- 在组件生命周期或 React 合成事件中,setState 是异步; 在 React 的生命周期以及绑定的事件流中,所有的 setState 操作会先缓存到一个队列中,在事件结束后,才会取出队列,触发state 更新。
- 在 setTimeout 或者原生 dom 事件中,setState 是同步;
三、state的更新会被合并
State 变更意味着组件会重新render。为了优化性能,React会收集state的变更,多次调用的情况下,React只会执行一次render。
案例:
// 初识count 为0
mergeState() {
for ( let i = 0; i < 10; i++ ) {
this.setState( { count: this.state.count + 1 } , () => {
console.log(this.state.count)
});
}
// count 为1
for ( let i = 0; i < 10; i++ ) {
this.setState( prevState => {
// prevSate 上一个状态
console.log( prevState.count );
return {
count: prevState.count + 1
}
} );
}
// count 为10
}
// 说明:
// 1.直接传递对象的setstate会被合并成一次
// 2.使用函数传递state不会被合并
最后一个demo:
// 初始val number 0
demo(){
this.setState({val: this.state.val + 1});
console.log('==第一次输出==', this.state.val); // 第 1 次输出
this.setState({val: this.state.val + 1});
console.log('==第二次输出==', this.state.val); // 第 2 次输出
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log('==第三次输出==', this.state.val); // 第 3 次输出
this.setState({val: this.state.val + 1});
console.log('==第四次输出==', this.state.val); // 第 4 次输出
}, 0);
}
// 0 0 2 3
《深入 React 技术栈》 图 3-15