小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
背景
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
,函数默认参数previousCount
为count
更新后的最新值,所以可以确保每次点击按钮,increment
访问的都是最新的count
值
总结
- 惰性初始化函数在某些场景下可以规避性能问题,提升性能
- 更新函数默认参数可以确保每次访问的值是更新后的值
参考
React-hooks
useState lazy initialization and function updates