🤔

【React】フックのエラーからReactの大事なルールについて深堀りしてみた

2023/04/27に公開

エラー内容

Reactの案件でリファクタをしてる際にRendered more hooks than during the previous renderというエラーにぶつかった。
useXxx関数を条件やループに入れたり、早期returnしてしまったりすると起きるみたい。

私の場合だと早期Returnをしてしまっていたためエラーが出ていた。

早期Returnの例:

import React, { useState } from 'react';

function BadExample(props) {
  // フックを呼び出す前に早期リターンが行われている
  if (!props.show) {
    return null;
  }

  const [count, setCount] = useState(0);
}

解決例:

import React, { useState } from 'react';

function GoodExample(props) {
  // リターンよりも前にフックを呼び出してる
  const [count, setCount] = useState(0);

  if (!props.show) {
    return null;
  }
}

これの何がダメなの?

Reactの公式ドキュメントを見てみるとこう書かれていました。

フックをループや条件分岐、あるいはネストされた関数内で呼び出してはいけません。代わりに、あなたの React の関数のトップレベルでのみ、あらゆる早期 return 文よりも前の場所で呼び出してください。これを守ることで、コンポーネントがレンダーされる際に毎回同じ順番で呼び出されるということが保証されます。これが、複数回 useState や useEffect が呼び出された場合でも React がフックの状態を正しく保持するための仕組みです

例:

// ------------
// 1回目のレンダリング
// ------------
useState('hoge')
useEffect(persistForm)
useState('hogehoge')
useEffect(updateTitle)

// ------------
// 2回目のレンダリング
// ------------
useState('hoge')
useEffect(persistForm)
// useState('hogehoge') ←ここが早期Returnの際に呼ばれなかった
useEffect(updateTitle)

// ...

Reactはフックの呼び出しがどのstateに対応しているのかというと、フックが呼ばれる順番に依存しているみたいです。フックの呼び出しの順序が毎回のレンダーごとに同じであるがために、それがずれるとエラーが起きてしまうという結果になってしまいます。

これがフックを呼び出すのがトップレベルのみでなければならない理由のようです。

ここでもう一つの疑問

これを守ることで、コンポーネントがレンダーされる際に毎回同じ順番で呼び出されるということが保証されます。

Reactの公式には上記のような記載があったが、毎回同じ順番で呼び出されることが何に繋がるのかがいまいち理解ができなかった。

「なんで毎回同じにしなきゃいけないの?!?!」
「別々でもいいだろううがよおお!」

そんな疑問をTwitterのコミュニティで投げかけたところ、しまぶーさんが返答してくださりました、、
ありがとうございます;;

https://twitter.com/shimabu_it/status/1651093481438461953

Reactでは冪等性というものが重要視されているみたいです。(ちょくちょく見かけてたけど、漢字難しいなああくらいにしか思ってなかった、、、)

まとめ

同じ順番で呼び出されることで、Reactが正しい状態と関数を呼び出せるっていう「状態の整合性」がとれて、コードの可読性とか、デバッグが簡単になるっていう利点があるのかなと。
予期しない挙動をなくしていきたいっていうReactの概念的なものが詰め込まれた設計になってるんだなって理解した。

こういったルールとかを有耶無耶にしたまま進めてたので、きちんと理解して使うことが大事だなとしみじみ感じたっていうお話でした。

参照

https://ja.legacy.reactjs.org/docs/hooks-rules.html

Discussion