更新版本:
问题:在函数组件和类组件中,在 setTimeout
/ setInterval
内使用 React State 变量的行为有何区别?
情况1: 函数组件中的 State 变量(陈旧闭包):
const [value, setValue] = useState(0)
useEffect(() => {
const id = setInterval(() => {
console.log(value)
}, 1000)
return () => {
clearInterval(id)
}
}, [])
案例2:类组件中的状态变量(没有过时闭包):
constructor(props) {
super(props)
this.state = {
value: 0,
}
}
componentDidMount() {
this.id = setInterval(() => {
console.log(this.state.value)
}, 1000)
}
案例3:让我们尝试创建一个固化的闭包来包含this
。
componentDidMount() {
const that = this
this.id = setInterval(() => {
console.log(that.state.value)
}, 1000)
}
案例 4: 让我们再次尝试在类组件中创建一个陈旧的闭包。
componentDidMount() {
const that = { ...this }
this.id = setInterval(() => {
console.log(that.state.value)
}, 1000)
}
案例5:让我们再次尝试在类组件中创建一个过期的闭包。
componentDidMount() {
const { value } = this.state
this.id = setInterval(() => {
console.log(value)
}, 1000)
}
案例 6: 类已经获胜(没有额外的努力来避免陈旧的闭包)。但是,如何在函数组件中避免呢?
const value = useRef(0)
useEffect(() => {
const id = setInterval(() => {
console.log(value.current)
}, 1000)
return () => {
clearInterval(id)
}
}, [])
案例6: 让我们为函数组件寻找另一个解决方案
source-1, source-2
useEffect(() => {
const id = setInterval(() => {
setValue((prevValue) => {
console.log(prevValue)
return prevValue
})
}, 1000)
return () => {
clearInterval(id)
}
}, [])
问题是由于闭包引起的,可以通过使用
ref
来解决。但这里有一个解决方法,即使用
setState
的"更新器"形式访问最新的
state
值:
function App() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
setTimeout(() => console.log('count after 5 secs: ', count, 'Wrong'), 5000)
}, [])
React.useEffect(() => {
setTimeout(() => {
let count
setCount(p => {
console.log('p: ', p)
count = p
return p
})
console.log('count after 5 secs: ', count, 'Correct')
}, 5000);
}, [])
return (<div>
<button onClick={() => setCount(p => p+1)}>Click me before 5 secs</button>
<div>Latest count: {count}</div>
</div>)
}
ReactDOM.render(<App />, document.getElementById('mydiv'))
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<body>
<div id="mydiv"></div>
</body>
setState(prevValue => prevValue)
不会导致重新渲染,因为返回的是相同的值。React 会执行:应该渲染:Object.is(oldValue, newValue)
=>false
。因此,如果我们执行setCount(p => { // do_something; return p})
,就不会重新渲染。 - Ajeet ShahsetTimeout
时不必担心无限重新渲染。 - Ajeet ShahuseState
的"setter"函数时,它有特殊处理。 - sherrellbc