React 中 State 的那点事

想第一时间获取我的最新文章,请关注公众号: 技术特工队

技术特工队

setState 为什么是异步的

首先我们先来看一个例子:

1
2
3
for ( let i = 0; i < 100; i++ ) {
this.setState( { num: this.state.num + 1 } );
}

如果每次都立马执行的,再短短的时间里,会有100次的渲染,这显然对于React来说是很大的一个渲染性能问题,所以针对setState做了一些特别的优化:React会将多个setState的调用合并成一个来执行,这意味着当调用setState时,state并不会立即更新

具体来说是在 setState之后并不会立马执行,而是放到一个队列中,在合适的时机,批量更新state,队列机制可以批量的合并更新State。
最终被react合并后效果如下:

1
2
3
4
5
6
7
Object.assign(
previousState,
{ num: this.state.num + 1 },
{ num: this.state.num + 1 },
{ num: this.state.num + 1 },
...
)

再看这个例子:

1
2
const b = { name: 'brian', name: 'peter' , name: 'Dave'};
// b的结果为 {name: 'Dave'}

这个问题也就解释了,上面的循环逻辑执行,每次拿到的 this.state.num 始终是0,最值在最后合并后,num的值再变为1 ,所以对于react内部渲染最终只会执行一次最终的渲染,因为这部分React将其进行了合并处理。

https://juejin.im/post/5bf55878e51d452b19649eae

https://medium.com/@brianwu291/learn-basic-react-setstate-function-2aec5018a38a

js单线程

那我们再来看一次例子:

1
2
3
4
this.setState( { num: this.state.num + 1 } );
setTimeout(() => {
this.setState({ num: this.state.num + 1 });
}, 0)

在这个例子中,执行了两遍setState,第二句是放在了setTimeout中进行执行,但是时间间隔是0,这样的情况下,我们得到的输出结果就是正常,符合预期的,这是为什么呢?

这里需要了解js的单线程的逻辑,单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。但是对于多核cpu,就有些浪费了,所有就将执行的任务分为同步任务和异步任务,同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”被主线程通知,某个异步任务可以执行了,该任务才会进入主线程执行。

同步任务就在主线程上执行,形成一个执行任务栈,只有当任务栈中的任务执行完后,才会让任务队列中第一位进入主线程,而这里一般也会检测一个执行时间,可能任务是延时任务,只有等到规定时间才会回到主线程中执行。

例如:

1
2
3
4
5
6
console.log(1);
setTimeout(function(){console.log(2);},0);
console.log(3);
console.log(4);

// 输出结果: 1 -> 3 -> 4 -> 2

原因为外层的console输出都是同步任务,直接在主线程任务栈中进行执行,只有这部分执行完后,才会检测位于 任务队列的任务即(setTimeout console ) 方法,所以这个2一定是最后输出的。

如果非要每次都执行有没有办法呢?

React 提供了更新的方法,可以拿到前一个state的状态,然后进行更新,例子如下:

1
2
3
this.setState((preState, props) => {
return {counter: preState.counter + 1};
});

上面的方法react会保证preState能够每次都拿到最新的值,所以在特殊场景下,可以这样更新state,那么就可以每次都能拿到最新的值。

http://www.ruanyifeng.com/blog/2014/10/event-loop.html

题外话:

setTimeout 和 setInterval 等的区别

  • setTimeout 在指定时间后,仅执行一次
  • setInterval 每隔指定时间后,就执行一次
WangXin wechat
欢迎订阅我的微信公众号,第一时间获取最新文章!
坚持原创技术分享,您的支持将鼓励我继续创作!