useState惰性初始化函数和更新函数

6,465 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

背景

useState的基本用法

    const [state, setState] = useState(init)

例子

    function Counter() {
      const [count, setCount] = React.useState(() => 0)
      const increment = () => setCount(previousCount => previousCount + 1)
      return <button onClick={increment}>{count}</button>
    }

例子中的useState和更新函数setCount不同于直接使用固定值为初始化和更新后的值,而都是接收了一个函数,且以函数返回值分别作为count的初始值和更新后的值。两种方式在某些特定场景下,会有一定区别。

惰性初始化函数

例子:

    const initialState = Number(window.localStorage.getItem('count'))
    const [count, setCount] = React.useState(initialState)

当函数组件更新re-render时,函数组件内所有代码都会重新执行一遍。此时initialState的初始值是一个相对开销较大的IO操作。每次函数组件re-render时,第一行代码都会被执行一次,引起不必要的性能损耗。

    const initialState = () => Number(window.localStorage.getItem('count'))
    const [count, setCount] = React.useState(initialState)

initialState以函数形式传入时,它只会在函数组件初始化的时候执行一次,函数re-render时不会再被执行。这个函数即惰性初始化函数这个特性,可以在这种场景下规避不必要的性能问题。

更新函数

例子:

    function DelayedCounter() {
      const [count, setCount] = React.useState(0)
      const increment = async () => {
        await doSomethingAsync()
        setCount(count + 1)
      }
      return <button onClick={increment}>{count}</button>
    }

假设doSomethingAsync这个异步函数执行需要500ms,连续快速三次点击按钮,会发现最终count值为1,而不是我们想要的最新值3。而在increment函数内部打印console.log可以发现,函数increment确实执行了三次,但是如果在setCount方法上方console.log(count)打印会发现count值一直是0
目前我理解的原因是函数组件在一次re-render完成之前,我们连续三次点击按钮,调用increment方法时,setCount所访问的count值一直是未更新的值0导致的。

    function DelayedCounter() {
      const [count, setCount] = React.useState(0)
      const increment = async () => {
        await doSomethingAsync()
        setCount(previousCount => previousCount + 1)
      }
      return <button onClick={increment}>{count}</button>
    }

这种场景解决方法也很简单,把setCount参数改为函数形式即可。原因是不同于直接取值count,函数默认参数previousCountcount更新后的最新值,所以可以确保每次点击按钮,increment访问的都是最新的count

总结

  • 惰性初始化函数在某些场景下可以规避性能问题,提升性能
  • 更新函数默认参数可以确保每次访问的值是更新后的值

参考

React-hooks
useState lazy initialization and function updates