React HooksのuseEffectパターン集


はじめに

React Hooks使っていますか?
機能をコンポーネント単位で管理することができ、メンテナンスしやすいコードが書けるので大変便利ですよね。

ただClass Componentのときと比べて一部の機能、特にuseEffectの書き方に迷ってしまうシーンが多いため比較的使うであろうパターンについてまとめました。

useEffectパターン

コンポーネント内の特定の値が変化したら更新

第二引数に変更を監視する値を置き、値が変化したら関数が実行されます。

useEffect(() => {
  console.log(stateA)
}, [stateA])

コンポーネント内の何かしらの値が変化したら更新

第二引数を省略します。
いずれかのstate等が変化すると実行されます。

useEffect(() => {
  console.log('hello')
})

アップデート前の値を取得する

return内の処理ではupdateが行われる前の値を取得できます。

useEffect(() => {
  console.log(stateA)
  return () => {
    console.log(stateA)
  }
}, [stateA])

componentDidMount (コンポーネントのマウント時)

useEffectの第2引数を空にするとcomponentDidMountと同様の処理を行うことができます。1

useEffect(() => {
  // 処理
  console.log('Mounted!')
}, [])

componentDidUpdate (コンポーネントのアップデート時)

useEffectではコンポーネントのレンダーがトリガーになり、mountとupdate両方の処理が行われてしまうのでそのままではupdateのみを行うことはできません。
updateのみを実現するためにはuseRefを使い、マウント時の処理を防ぐことによって表現できます。2

const mounted = useRef(false)
useEffect(() => {
  if(mounted.current) {
    // 処理
    console.log('Updated!')
  } else {
    mounted.current = true
  }
}, [stateA])

async componentDidMount (マウント時に順次処理)

useStateの第一引数はasyncにすることはできないのでasyncで囲った関数を呼び出すようにします。
当然ながら第一引数の関数内では非同期処理が適用されず複数の処理を行う場合は注意が必要です。3

useEffect(() => {
  asyncFunc()
}, [])

const asyncFunc = async () => {
  await new Promise(resolve => {
    setTimeout(() => resolve(console.log('hello')), 1000);
  });
  console.log('world');
}

async componentDidUpdate (アップデート時に順次処理)

考え方は基本的にasync componentDidUpdateと同じです。
非同期処理を行う関数内で実行してあげましょう。

const mounted = useRef(false)

useEffect(() => {
  if(mounted.current) {
    asyncFunc()
  } else {
    mounted.current = true
  }
}, [stateA])

const asyncFunc = async () => {
  // 処理
  await new Promise(resolve => {
    setTimeout(() => resolve(console.log('hello')), 1000);
  });
  console.log('world');
}

componentWillUnmount

第二引数を空にした状態でreturnを行うと同様の結果を得られます。

useEffect(() => {
  return () => {
    // 処理
    console.log('Unmounting!')
  }
}, [])

componentDidMount + componentDidUpdate

マウントとアップデートそれぞれ別の処理を行いたい場合、上記の書き方を利用し両方に記述すると表現できます。

const mounted = useRef(false)

useEffect(() => {
  if(mounted.current) {
    // Update時の処理
    console.log('Updated!')
  } else {
    // Mount時の処理
    console.log('Mounted!')
    mounted.current = true
  }
}, [stateA])

最後に

useEffectは使いこなせると便利ですがそれまでが大変です。
今回はClass Component目線での書き方にしましたが、Functional ComponentでのuseEffectは完全に同じものではないのでご留意ください。正確な違いについてはこちらをご確認ください。
場合によってはClass Componentの方が最適である可能性もあるのでそちらも検討してみてください。

楽しいReactライフを!

参考