React Hooks、useStateの更新関数引数には関数を


最近 Hooks を触って思ったことを綴ります。よく見る useState の increment 例です。

const [count, setCount] = useState(0)
const handleClick = () => {
  setCount(count + 1)
}
return (
  <div className={props.className}>
   <p>count: {count}</p>
   <button onClick={handleClick}>+1</button>
  </div>
)

これはアンチパターンで、handleClick は render毎に再定義されます。この再定義を skip するため、useCallback による関数memoizeを行います。

アンチパターン集

// memoize input array の指定がないため、上記と差がない
const handleClick = useCallback(() => {
  setCount(count + 1)
})
// memoize されているが、状態変化とともに再定義される
const handleClick = useCallback(() => {
  setCount(count + 1)
},[count])
// 2回目以降のレンダリング時再定義が skip されるが
// 初期状態を参照しているため「1」にしかならない。
const handleClick = useCallback(() => {
  setCount(count + 1)
},[])

現状良さそうな方法

状態更新関数の引数に与えるものは、プリミティブに限りません。関数を与えることで、prev state を参照出来ます。これにより、初期レンダリング時のみに handleClick関数定義を抑止し、正しく動かすことが出来ます。

// 2回目以降のレンダリング時の再定義が skip され、正しく動く
const handleClick = useCallback(() => {
  setCount(prev => prev + 1)
},[])

useState で生成された関数は、同時に生成された状態を参照しない方が良さそう、という話でした。