React 面试题详细答案 - 第 42 题
42. setState 是同步还是异步?为什么?(批处理 batching、优先级)
setState 的执行机制
React 17 及之前:批处理机制
function Component() {
const [count, setCount] = useState(0)
const handleClick = () => {
console.log('点击前:', count)
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
console.log('点击后:', count)
}
return <button onClick={handleClick}>Count: {count}</button>
}
React 18:自动批处理
function Component() {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
}
const handleAsync = () => {
setTimeout(() => {
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
}, 1000)
}
const handlePromise = () => {
fetch('/api/data').then(() => {
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
})
}
return (
<div>
<button onClick={handleClick}>同步批处理</button>
<button onClick={handleAsync}>异步批处理</button>
<button onClick={handlePromise}>Promise 批处理</button>
<p>Count: {count}</p>
</div>
)
}
批处理的原理
批处理机制
class ReactBatching {
constructor() {
this.isBatching = false
this.pendingUpdates = []
}
startBatching() {
this.isBatching = true
}
endBatching() {
this.isBatching = false
this.flushUpdates()
}
addUpdate(update) {
if (this.isBatching) {
this.pendingUpdates.push(update)
} else {
this.flushUpdates([update])
}
}
flushUpdates(updates = this.pendingUpdates) {
this.pendingUpdates = []
updates.forEach((update) => update())
}
}
const batching = new ReactBatching()
function setState(newState) {
batching.addUpdate(() => {
applyStateUpdate(newState)
})
}
function handleClick() {
batching.startBatching()
setState({ count: 1 })
setState({ count: 2 })
setState({ count: 3 })
batching.endBatching()
}
优先级机制
const PriorityLevels = {
Immediate: 1,
UserBlocking: 2,
Normal: 3,
Low: 4,
Idle: 5,
}
function Component() {
const [count, setCount] = useState(0)
const [urgent, setUrgent] = useState(false)
const handleClick = () => {
setCount(count + 1)
setUrgent(true)
startTransition(() => {
setCount(count + 1000)
})
}
return (
<div>
<button onClick={handleClick}>更新</button>
<p>Count: {count}</p>
<p>Urgent: {urgent ? 'Yes' : 'No'}</p>
</div>
)
}
实际应用示例
避免状态更新问题
function Counter() {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
}
return <button onClick={handleClick}>Count: {count}</button>
}
function Counter() {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount((prev) => prev + 1)
setCount((prev) => prev + 1)
setCount((prev) => prev + 1)
}
return <button onClick={handleClick}>Count: {count}</button>
}
强制同步更新
import { flushSync } from 'react-dom'
function Component() {
const [count, setCount] = useState(0)
const [flag, setFlag] = useState(false)
const handleClick = () => {
flushSync(() => {
setCount(count + 1)
})
console.log('Count after flushSync:', count)
setFlag(!flag)
}
return (
<div>
<button onClick={handleClick}>更新</button>
<p>Count: {count}</p>
<p>Flag: {flag ? 'Yes' : 'No'}</p>
</div>
)
}