BFE.devAI解答 - React 小测验
29. useEffect() timing IV
请注意,本小测验的代码存在一些问题,具体如下:
'infiniteLoopProtection:false'
是无效的JavaScript代码,应该删除。useLayoutEffect
应该在useEffect
之前定义,因为useEffect
是异步执行的。在
while
循环中修改全局变量window.timestamp
的值会导致浏览器阻塞,应该避免这样做。ReactDOM.createRoot
应该使用ReactDOM.createRoot
替换。
下面是修改后的代码:
import React, { useState, useEffect, useLayoutEffect } from 'react'
import ReactDOM from 'react-dom'
function App() {
const [state, setState] = useState(0)
console.log(1)
// 模拟块级作用域
{
const start = Date.now()
while (Date.now() - start < 50) {
// 移除修改全局变量的行为
}
}
useEffect(() => {
console.log(2)
}, [state])
Promise.resolve().then(() => console.log(3))
setTimeout(() => console.log(4), 0)
useLayoutEffect(() => {
console.log(5)
setState(state => state + 1)
}, [])
return null
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
在上述修改后的代码中,以下是代码的执行顺序及解释:
console.log(1)
: 打印 1。while
循环:由于没有实际的工作,循环执行会很快结束。console.log(2)
: 在初始化时打印 2,以后只在state
改变时打印。Promise.resolve().then(() => console.log(3))
: 将回调函数放入微任务队列中,等待执行。setTimeout(() => console.log(4), 0)
: 将回调函数放入宏任务队列中,等待执行。console.log(5)
: 在组件挂载时打印 5。执行
setState
,触发重新渲染。console.log(1)
: 打印 1。console.log(2)
: 打印 2,因为state
已经改变。console.log(5)
: 打印 5,因为useLayoutEffect
在每次渲染后都会执行。console.log(1)
: 打印 1。console.log(2)
: 打印 2,因为state
已经改变。console.log(3)
: 打印 3,因为 Promise 的回调函数已经在微任务队列中等待执行。console.log(4)
: 打印 4,因为setTimeout
的回调函数已经在宏任务队列中等待执行。
请注意,setTimeout
的回调函数在 useEffect
和 useLayoutEffect
之后调用,因为它在宏任务队列中等待执行,而不是在组件的同步执行阶段执行。