this.state 与 useState 的 setTimeout

Dan*_*nny 5 javascript reactjs react-hooks

当我使用类组件时,我有代码:

setTimeout(() => console.log(this.state.count), 5000);
Run Code Online (Sandbox Code Playgroud)

当我使用钩子时:

const [count, setCount] = useState(0);
setTimeout(() => console.log(count), 5000);
Run Code Online (Sandbox Code Playgroud)

如果我触发setTimeout然后count在超时 ( 5000ms)之前将更改为 1 ,类组件将console.log(1)(最新值),并且useStateconsole.log(0)(注册超时时的值)。
为什么会发生这种情况?

Aje*_*hah 15

更新后的版本:

问题:函数和setTimeout组件内部的React State 变量的行为有何不同setInterval

情况 1:函数组件中的状态变量(陈旧闭包):

const [value, setValue] = useState(0)

useEffect(() => {
  const id = setInterval(() => {
    // It will always print 0 even after we have changed the state (value)
    // Reason: setInterval will create a closure with initial value i.e. 0
    console.log(value)
  }, 1000)
  return () => {
    clearInterval(id)
  }
}, [])
Run Code Online (Sandbox Code Playgroud)

情况 2:类组件中的状态变量(没有过时的闭包):

constructor(props) {
  super(props)
  this.state = {
    value: 0,
  }
}

componentDidMount() {
  this.id = setInterval(() => {
    // It will always print current value from state
    // Reason: setInterval will not create closure around "this"
    // as "this" is a special object (refernce to instance)
    console.log(this.state.value)
  }, 1000)
}
Run Code Online (Sandbox Code Playgroud)

案例 3:让我们尝试创建一个陈旧的闭包this

// Attempt 1

componentDidMount() {
  const that = this // create a local variable so that setInterval can create closure
  this.id = setInterval(() => {
    console.log(that.state.value)
    // This, too, always print current value from state
    // Reason: setInterval could not create closure around "that"
    // Conclusion: Oh! that is just a reference to this (attempt failed)
  }, 1000)
}
Run Code Online (Sandbox Code Playgroud)

情况 4:让我们再次尝试在类组件中创建一个过时的闭包

// Attempt 2

componentDidMount() {
  const that = { ...this } // create a local variable so that setInterval can create closure
  this.id = setInterval(() => {
    console.log(that.state.value)
    // Great! This always prints 0 i.e. the initial value from state
    // Reason: setInterval could create closure around "that"
    // Conclusion: It did it because that no longer is a reference to this,
    // it is just a new local variable which setInterval can close around
    // (attempt successful)
  }, 1000)
}
Run Code Online (Sandbox Code Playgroud)

案例 5:让我们再次尝试在类组件中创建一个过时的闭包

// Attempt 3

componentDidMount() {
  const { value } = this.state // create a local variable so that setInterval can create closure
  this.id = setInterval(() => {
    console.log(value)
    // Great! This always prints 0 i.e. the initial value from state
    // Reason: setInterval created closure around value
    // Conclusion: It is easy! value is just a local variable so it will be closed
    // (attempt successful)
  }, 1000)
}
Run Code Online (Sandbox Code Playgroud)

案例 6类获胜(没有额外的努力来避免陈旧的关闭)。但是,如何在函数组件中避免它呢?

// Let's find solution

const value = useRef(0)

useEffect(() => {
  const id = setInterval(() => {
    // It will always print the latest ref value
    // Reason: We used ref which gives us something like an instance field.
    // Conclusion: So, using ref is a solution
    console.log(value.current)
  }, 1000)
  return () => {
    clearInterval(id)
  }
}, [])
Run Code Online (Sandbox Code Playgroud)

源 1源 2

案例6:我们为功能组件寻找另一种解决方案

useEffect(() => {
  const id = setInterval(() => {
    // It will always print the latest state value
    // Reason: We used updater form of setState (which provides us latest state value)
    // Conclusion: So, using updater form of setState is a solution
    setValue((prevValue) => {
      console.log(prevValue)
      return prevValue
    })
  }, 1000)
  return () => {
    clearInterval(id)
  }
}, [])
Run Code Online (Sandbox Code Playgroud)

原始版本:

该问题是由关闭引起的,可以通过使用 来修复refstate但这里有一个解决方法来修复它,即使用“更新程序”形式访问最新值setState

const [value, setValue] = useState(0)

useEffect(() => {
  const id = setInterval(() => {
    // It will always print 0 even after we have changed the state (value)
    // Reason: setInterval will create a closure with initial value i.e. 0
    console.log(value)
  }, 1000)
  return () => {
    clearInterval(id)
  }
}, [])
Run Code Online (Sandbox Code Playgroud)
constructor(props) {
  super(props)
  this.state = {
    value: 0,
  }
}

componentDidMount() {
  this.id = setInterval(() => {
    // It will always print current value from state
    // Reason: setInterval will not create closure around "this"
    // as "this" is a special object (refernce to instance)
    console.log(this.state.value)
  }, 1000)
}
Run Code Online (Sandbox Code Playgroud)


Dan*_*nny 8

对于useState,它会count在第一次使用时创建超时。它count通过 a访问该值closure。当我们通过 设置新值时setCount,组件会重新渲染但不会更改传递给 timeout 的值。
我们可以使用const count = useRef(0)并传递给 timeout count.current。这将始终使用最新的计数值。
查看此链接以获取更多信息。