react-hooksのuseStateでfunctionを管理させたい場合のTips

react-hooksのuseStateでfunctionを管理させたい場合のTips:

react-hooksのuseStateはこれまでのstateのように値を保持してくれる重要な関数だ。

例えば単純なカウンターならこんな具合になるだろう

const useCounter = () => { 
  const [count, setCounter] = useState(0) 
  return { count, setCounter  
} 
export const MyApp = () => { 
  const { count, setCounter } = useCounter() 
  return ( 
    <div> 
      <div>{count}</div> 
      <button onClick={() => setCounter(count + 1)}>+</button> 
    </div> 
  ) 
} 
このuseStateに関数を保持させたい場合、ちょっと注意が必要になる。

例えばこんな風に書くと、意図しない挙動になるだろう。

const initialHelloFn = () => { 
  console.log("initial") 
} 
const useCounter = () => { 
  const [count, setCounter] = useState(0) 
  const [helloFn, setHelloFn] = useState(initialHelloFn) 
  // このnewFnが何度も呼び出される 
  const newFn = useCallback(() => { 
    console.log("hello!", count) 
  }, [count]) 
  useEffect(() => { 
    setHelloFn(newFn) 
  }, [newFn, setHelloFn]) 
  console.log(helloFn) 
  return { 
    count, 
    setCounter, 
    helloFn 
  } 
} 
 
export const MyApp = () => { 
  const { count, setCounter, helloFn } = useCounter() 
  return ( 
    <div> 
      <div>{count}</div> 
      <button onClick={() => setCounter(count + 1)}>+</button> 
      <button onClick={() => helloFn()}>hello</button> 
    </div> 
  ) 
} 
おそらくこのようにすると、hello! 0のような出力が大量に出てしまうだろう


正しく動かす場合はどうするか?

{fn: 管理したい関数} のようにobjectで管理する。

const useCounter = () => { 
  const [count, setCounter] = useState(0) 
  // {fn: Function}などobjectの形にラップする 
  const [helloFn, setHelloFn] = useState({ fn: initialHelloFn }) 
  const newFn = useCallback(() => { 
    console.log("hello!", count) 
  }, [count]) 
 
  useEffect(() => { 
    setHelloFn({ fn: newFn }) 
  }, [newFn, setHelloFn]) 
 
  return { 
    count, 
    setCounter, 
    helloFn: helloFn.fn 
  } 
} 


なぜこうする必要があるか?

結論から言えばuseStateから返ってくるsetFooのようなハンドラーはFunctional Updateに対応しているため、単純に関数を渡してしまうとFunctional Updateとして処理されてしまうからになる。

https://reactjs.org/docs/hooks-reference.html#functional-updates

Functional Update自体は便利で、下記のように、現在の値を引き継がずに関数だけで利用することができる

<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> 
これはhooksで新しく入ったわけではなく、React.Component.setStateにも同様の機能が存在していたものだ
https://reactjs.org/docs/react-component.html#setstate

ただComponentの場合はstate自体がobjectなのでほとんどこのような引っかかり方をすることは無かった。useStateが単純な値を格納するものとして利用できてる分、この点は気をつけるべき部分だろう

コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)