We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
本文已收录在 Github: https://github.com/beichensky/Blog 中,欢迎 Star,欢迎 Follow!
在 react 18 版本之前,在面试中经常会出现这个问题,那么答案又是什么样的呢?
react 18
在 React 合成事件中是异步的
React
在 hooks 中是异步的
hooks
其他情况皆是同步的,例如:原生事件、setTimeout、Promise 等
setTimeout
Promise
看看下面这段代码的执行结果,就知道所言非虚了
class App extends React.Component { state = { count: 0 } componentDidMount() { this.setState({count: this.state.count + 1}) console.log(this.state.count); this.setState({count: this.state.count + 1}) console.log(this.state.count); setTimeout(() => { this.setState({count: this.state.count + 1}) console.log(this.state.count); this.setState({count: this.state.count + 1}) console.log(this.state.count); }); } render() { return <h1>Count: {this.state.count}</h1> } }
有经验的同学肯定都知道,最终的结果是: 0 0 2 3。
0 0 2 3
原因就是因为 componentDidMount 中的 setState 是批量更新,在整体逻辑没走完之前,不会进行更新。所以前两次打印结果都是 0,并且将两次更新合并成了一次。
componentDidMount
setState
而在 setTimeout 中,脱离了 React 的掌控,变成了同步更新,因为下方的 log 可以实时打印出即时的状态。
log
此时 React 的内部的处理逻辑我们可以写一段代码简单模拟一下:
先声明三个变量,用来记录数据
isBatchUpdate: 判断是否批量更新的标志
isBatchUpdate
count: 状态
count
queue: 存储状态的数组
queue
声明一个 handleClick 方法,来模拟 React 合成事件
handleClick
声明一个 setState 方法,来模拟 React 的 setState
// 判断是否批量更新的标志 let isBatchUpdate = false; // 状态 let count = 0; // 存储最新状态的数组 let queue = []; const setState = (state) => { // 批量更新,则将状态暂存,否则直接更新 if (isBatchUpdate) { queue.push(state); } else { count = state; } } const handleClick = () => { // 进入事件,先将 isBatchUpdate 设置为 true isBatchUpdate = true setState(count + 1) console.log(count); setState(count + 1) console.log(count); setTimeout(() => { setState(count + 1) console.log(count); setState(count + 1) console.log(count); }) // 事件结束,将 isBatchUpdate 置为 false isBatchUpdate = false; } handleClick(); count = queue.pop(); // 更新完成,重置状态数组 queue queue = [];
可以看到,上面这段代码的打印结果也是 0 0 2 3。
上面提到,在原生事件以及 setTimeout 等情况下,setState 是同步的,那如果我们仍然希望这种情况下可以同步更新,该怎么办呢?
React 也提供了一种解决方案:从 react-dom 包中暴露了一个 API: unstable_batchedUpdates
react-dom
API
unstable_batchedUpdates
那我们简单用一下看看效果:
class App extends React.Component { state = { count: 0 } componentDidMount() { this.setState({count: this.state.count + 1}) console.log(this.state.count); this.setState({count: this.state.count + 1}) console.log(this.state.count) setTimeout(() => { ReactDOM.unstable_batchedUpdates(() => { this.setState({count: this.state.count + 1}) console.log(this.state.count) this.setState({count: this.state.count + 1}) console.log(this.state.count) }) }) } render() { return <h1>Count: {this.state.count}</h1> } }
可以看到此时的打印结果为 0 0 1 1。
0 0 1 1
Ok,React 18 之前 setState 的更新方式就说到这里,那 React 18 里做了什么改动呢?
上面提到了默认批量更新以及手动批量更新,那有些同学不满足了呀,觉得手动的还是不够智能,在很多情况下还得手动去调用 unstable_batchedUpdates 这个函数,用起来不爽。
别急,React 18 新版本就可以解决这些同学的痛点了!
Ok,直接上代码,看看 React 18 到底怎么用的
class App extends React.Component { state = { count: 0 } componentDidMount() { this.setState({count: this.state.count + 1}) console.log(this.state.count); this.setState({count: this.state.count + 1}) console.log(this.state.count) setTimeout(() => { this.setState({count: this.state.count + 1}) console.log(this.state.count) this.setState({count: this.state.count + 1}) console.log(this.state.count) }) } render() { return <h1>Count: {this.state.count}</h1> } } // 使用 react 18 新的并发模式写法进行 dom render ReactDOM.createRoot(document.getElementById('#root')!).render(<App />)
组件代码保持和第一版的一致,没有使用 unstable_batchedUpdates。
可以看到,此时的打印结果也是: 0 0 1 1
仅仅是使用了新的 API: ReactDOM.createRoot(root).render(jsx)。React 就能实现自动的批量更新了。感觉有点神奇。
ReactDOM.createRoot(root).render(jsx)
我们依然写一段代码来模拟一下这个过程:
此时不需要 isBatchUpdate 来判断是否批量更新了,而是通过更新的优先级来进行判断
每次更新会进行优先级的判定,相同优先级的任务会被合并。
事件执行完毕,进行任务的执行和更新
// 状态 let count = 0; // 存储状态的数组 let queue = []; const setState = (state) => { const newState = {payload: state, priority: 0 } // 判断当前优先级的任务集合是否存在,不存在则初始化,存在则存到对应由县级的任务集合中 if (queue[newState.priority]) { queue[newState.priority].push(newState.payload) } else { queue[newState.priority] = [newState.payload] } } const handleClick = () => { setState(count + 1) console.log(count); setState(count + 1) console.log(count); setTimeout(() => { setState(count + 1); console.log(count); setState(count + 1) console.log(count); }) } handleClick(); count = queue.pop().pop(); setTimeout(() => { count = queue.pop().pop(); })
可以看到,上面这段代码的执行结果也是 0 0 1 1
上述模拟代码仅为了展示优先级批量更新,不代表任何 React 源码的逻辑和思想
好了,自动批量更新的新特性就说到这里了。这里引入了三个问题:
Q: React 18 之后提供了 ReactDOM.createRoot(root).render(jsx) 的 API,那之前 ReactDOM.render 的 API 还支持吗?
ReactDOM.createRoot
ReactDOM.render
A: 支持的,并且行为和之前版本是一致的。只有使用了 ReactDOM.createRoot 这种方式,才会启用新的并发模式。
Q: React 全自动更新后,那如果我就是想拿到更新之后的数据怎么办呢? A: 类组件中可以使用 setState(state, callback) 的方式,在 callback 中取到最新的值,函数组件可以使用 useEffect,将 state 作为依赖。即可以拿到最新的值。
setState(state, callback)
callback
useEffect
state
Q: 文章中说到的优先级的概念是怎么回事呢? A: 这个涉及到 React 最新的调度以及更新的机制,优先级的概念以及其他优先级的任务如何创建,我们之后会一一展开来说。
目前的话,可以理解为 React 的更新机制进行了变化,不再依赖于批量更新的标志。而是根据任务优先级来进行更新:高优先级的任务先执行,低优先级的任务后执行。
代码量很少,主要是修改了 ReactDOM 的渲染方式,可以亲自尝试一下,有疑惑的地方可以说出来一起进行讨论。
ReactDOM
如果有写的不对或不严谨的地方,欢迎大家能提出宝贵的意见,十分感谢。
如果喜欢或者有所帮助,欢迎 Star,对作者也是一种鼓励和支持。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
本文已收录在 Github: https://github.com/beichensky/Blog 中,欢迎 Star,欢迎 Follow!
18 版本之前
经典面试题:setState 是同步还是异步
在
react 18
版本之前,在面试中经常会出现这个问题,那么答案又是什么样的呢?在
React
合成事件中是异步的在
hooks
中是异步的其他情况皆是同步的,例如:原生事件、
setTimeout
、Promise
等看看下面这段代码的执行结果,就知道所言非虚了
有经验的同学肯定都知道,最终的结果是:
0 0 2 3
。原因就是因为
componentDidMount
中的setState
是批量更新,在整体逻辑没走完之前,不会进行更新。所以前两次打印结果都是 0,并且将两次更新合并成了一次。而在
setTimeout
中,脱离了React
的掌控,变成了同步更新,因为下方的log
可以实时打印出即时的状态。此时
React
的内部的处理逻辑我们可以写一段代码简单模拟一下:先声明三个变量,用来记录数据
isBatchUpdate
: 判断是否批量更新的标志count
: 状态queue
: 存储状态的数组声明一个
handleClick
方法,来模拟React
合成事件声明一个
setState
方法,来模拟React
的setState
可以看到,上面这段代码的打印结果也是
0 0 2 3
。手动批量更新
上面提到,在原生事件以及
setTimeout
等情况下,setState
是同步的,那如果我们仍然希望这种情况下可以同步更新,该怎么办呢?React
也提供了一种解决方案:从react-dom
包中暴露了一个API
:unstable_batchedUpdates
那我们简单用一下看看效果:
可以看到此时的打印结果为
0 0 1 1
。React 18 版本之后
上面提到了默认批量更新以及手动批量更新,那有些同学不满足了呀,觉得手动的还是不够智能,在很多情况下还得手动去调用
unstable_batchedUpdates
这个函数,用起来不爽。别急,React 18 新版本就可以解决这些同学的痛点了!
Ok,直接上代码,看看 React 18 到底怎么用的
组件代码保持和第一版的一致,没有使用
unstable_batchedUpdates
。可以看到,此时的打印结果也是:
0 0 1 1
仅仅是使用了新的
API
:ReactDOM.createRoot(root).render(jsx)
。React 就能实现自动的批量更新了。感觉有点神奇。我们依然写一段代码来模拟一下这个过程:
此时不需要
isBatchUpdate
来判断是否批量更新了,而是通过更新的优先级来进行判断每次更新会进行优先级的判定,相同优先级的任务会被合并。
事件执行完毕,进行任务的执行和更新
可以看到,上面这段代码的执行结果也是
0 0 1 1
上述模拟代码仅为了展示优先级批量更新,不代表任何 React 源码的逻辑和思想
好了,自动批量更新的新特性就说到这里了。这里引入了三个问题:
Q: React 18 之后提供了
ReactDOM.createRoot
(root).render(jsx) 的 API,那之前ReactDOM.render
的 API 还支持吗?A: 支持的,并且行为和之前版本是一致的。只有使用了
ReactDOM.createRoot
这种方式,才会启用新的并发模式。Q: React 全自动更新后,那如果我就是想拿到更新之后的数据怎么办呢?
A: 类组件中可以使用
setState(state, callback)
的方式,在callback
中取到最新的值,函数组件可以使用useEffect
,将state
作为依赖。即可以拿到最新的值。Q: 文章中说到的优先级的概念是怎么回事呢?
A: 这个涉及到 React 最新的调度以及更新的机制,优先级的概念以及其他优先级的任务如何创建,我们之后会一一展开来说。
目前的话,可以理解为 React 的更新机制进行了变化,不再依赖于批量更新的标志。而是根据任务优先级来进行更新:高优先级的任务先执行,低优先级的任务后执行。
写在后面
代码量很少,主要是修改了
ReactDOM
的渲染方式,可以亲自尝试一下,有疑惑的地方可以说出来一起进行讨论。如果有写的不对或不严谨的地方,欢迎大家能提出宝贵的意见,十分感谢。
如果喜欢或者有所帮助,欢迎 Star,对作者也是一种鼓励和支持。
The text was updated successfully, but these errors were encountered: