🧠

【考察】React は何故こんなに分かりにくいのか?

に公開3

はじめに

私は今まで仕事や独学でいろいろなプログラミング言語やフレームワークを触ってきました。

だいたいどれも基礎的な本を読んだり、チュートリアルを触れば
後はネットで調べながら、ある程度使えるようになってきました。

ですが React は今までのやり方が通用しませんでした。

いくつか React アプリを開発してみましたが、
既存の処理にちょっとした変更を加えるだけでも
どこに何を書いたらいいか思いつかないことが多々あります。

React は何故こんなに難しいのだろうか?

と疑問に思ったので理由を考察してみました。

これから React を勉強する人や、同じように悩んでいる人にとって
React を理解する糸口となれば幸いです。

理由①:処理レイヤーが複数ある

例えば、次のようなシンプルなコードを考えてみます。

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

return (
  <button onClick={() => setCount(count + 1)}>
    {count}
  </button>
);

ボタンを押すと数字が増える処理です。
ただ、このとき起きている処理の流れは意外と複雑です。

実際には、次のようなステップを踏んでいます。

  1. 開発者が書いた JS/TS の処理が React の API を呼び出す(state を更新する)
  2. React が再レンダリングが必要かどうかを判断する
  3. React が仮想DOMを更新する
  4. React の指示を受けて、ブラウザが画面(実DOM)を更新する

このように、

  • 開発者が書いたJS/TSの処理
  • React の処理
  • ブラウザの処理

と処理レイヤーが複数あるため、どの処理が、どのレイヤーで行われているか把握しづらいことが React のわかりにくさの一因ではないかと思います。

理由②:処理トリガーがわかりにくい

Reactでは、処理が実行されるトリガーが複数あります。

  • ボタンを押したとき(イベント)
  • stateが更新されたとき
  • propsが変わったとき

例えば次のコードです。

useEffect(() => {
  console.log("updated");
}, [count]);

この処理は、

  • 関数を呼び出しているわけでもなく
  • ボタンを直接押しているわけでもない

にもかかわらず、count が変わると実行されます。

このようにどの処理が、いつ・なぜ動くのかコードから直感的に読み取りにくいところが React の難しさだと感じています。

理由③:表示要素とロジックが分かれていない

Reactでは、JSXの中にブラウザ表示要素とロジックが混在します。

return (
  <div>
    {count > 0 && <p>{count}</p>}
  </div>
);

見た目はHTMLに近いですが、

  • 条件分岐
  • 変数の評価
  • ロジックによる表示制御

がすべて含まれています。

このようにどのような要素が、どんな条件で、どのように表示されるのかがコードから読み解きにくいことが、React の複雑さにつながっていると思います。

まとめ

  • 複数の処理レイヤー
  • コードから見えにくいトリガー
  • 表示要素とロジックの曖昧な境界

これらが重なって、
「いつ、どこで、何が起きているのか分からない」 という状態になってしまうのが
React の難しさの要因ではないかと思います。
更に JS/TS の文法や多岐に渡るスタイリング方法も難しさに拍車をかけている気がします。

私自身、まだまだ理解できていないことばかりなので、
まずはこれらの点を意識して React と向き合っていこうと思います。

Discussion

Honey32Honey32

失礼します。

React における処理のトリガーを、以下のように捉えるのは、

  • ボタンを押したとき(イベント)
  • stateが更新されたとき
  • propsが変わったとき

React の本来のメンタルモデル(=設計意図)とは異なっていて、理解をかえって難しくすると思います。(もちろん、誤った理解に基づいて書かれたコードが、実際にたくさんあるのが辛いところだと思いますが…)

React の公式ドキュメントから読み取ると、処理のトリガーは以下の2つしかないことが分かります。

  • イベント
  • レンダー

useEffect(() => {
  console.log(`count: ${count}`);
}, [count]);

例えば、上の記述は「count が変わるたびに、コンソールにメッセージを出力する」という意味ではなく、

『レンダー』のたびに、(その変更を DOM に変更するフェーズの後で)コンソールにメッセージを出力する。 (ただ、無駄な実行を抑制するために、"count が前回と同じなら実行しないで" と指定することができる)」という意味です。

https://scrapbox.io/honey32/Reactは初回レンダリングを特別扱いしない

https://zenn.dev/yumemi_inc/articles/react-effect-simply-explained

https://qiita.com/honey32/items/58e56e407d4d87e294a4

ツイート埋め込み(デカすぎて邪魔なので、たたみました)
けんづけんづ

ロジックは呼び出し元で制御して、描画するコンポーネントでは表示するパラメーター渡す程度やと思いました。

kazukinagatakazukinagata

Reactでは、処理が実行されるトリガーが複数あります。

  • ボタンを押したとき(イベント)
  • stateが更新されたとき
  • propsが変わったとき

そもそも「処理」という主語が広すぎるというのもありますが、再レンダーの話だとしたらこれは間違いで、正しくは以下が条件です。

  • state の更新
  • 親コンポーネントの再レンダリング

もし、再レンダーもイベントも一緒くたに「処理」と理解してるのであれば、そもそも両者を区別して、再レンダーにフォーカスして理解することから出発した方がいいと思います。