バックエンド開発者としてReactを理解する方法

13 min

過去に、バックエンド開発者としてReactのコードを理解するのがとても難しいと感じました。しかし最近、日々の開発作業からいくつかの洞察を得ることができたので、それを皆さんと共有したいと思います。

Webページを理解する

Webページは3つの部分で構成されています:

  • HTML:構造とコンテンツ
  • CSS:スタイル
  • JavaScript/TypeScript:インタラクション

例えば、フレームワークを使用しないネイティブのカウンターWebページは以下のようになります:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!-- 異なるデバイスでのレイアウトを制御するためのビューポート設定 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- Internet Explorerとの互換性を確保 -->
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <!-- スタイル用の外部CSSファイルをリンク -->
    <link rel="stylesheet" type="text/css" href="../styles.css" />
    <title>Static Template</title>
  </head>
  <body>
    <h1>Native JS DEMO</h1>
    <div id="dom-app">
      <!-- カウンターを表示 -->
      <span id="count">0</span>
      <!-- カウンターを増やすボタン -->
      <button id="addBtn" type="button" onclick="addBtnHandler()">
        +
      </button>
    </div>

    <!-- カウンター用のJavaScript -->
    <script type="text/javascript">
      // カウントを表示する要素を取得
      const $count = document.getElementById("count");

      // 追加ボタンのクリックイベントを処理する関数
      function addBtnHandler() {
        // カウントを増やして表示を更新
        $count.innerText = Number($count.innerText) + 1;
      }
    </script>
  </body>
</html>

重要なポイントは:

  1. ファイル全体がHTML(HyperText Markup Language)— 特殊なマークアップ言語です。 XML(Extensible Markup Language)やMarkdownも他のタイプのマークアップ言語です。同様に、構造を定義するために<html></html>のようなマークアップを持っています。

  2. スタイルは<link rel="stylesheet" type="text/css" href="../styles.css" />で定義され、通常headでインポートされます。

  3. インタラクションは<script></script>で定義され、onclickでトリガーされます。

どのフレームワークを使用しても、最終目標はこのようなHTMLファイルを構成することです。

Reactはどのようにそれを実現するか

同じ機能を実現できるReactコードは以下の通りです:

import React from "react";
import "./styles.css";

export default class App extends React.Component {
  state = {
    count: 0
  };

  handleClick = () => {
    this.setState({
      count: this.state.count + 1
    });
  };

  render() {
    return (
      <div className="app">
        <span>{this.state.count}</span>
        <button type="button" onClick={this.handleClick}>
          +
        </button>
      </div>
    );
  }
}

このコードで以前理解できなかったことは:

  1. React.Componentとは何か?なぜextendsで、なぜexportなのか?
  2. stateとは何か(同様に、useEffectやhooksとは何か)?
  3. render()とは何か、なぜ{}が続き、HTMLの一部を返すのか?
  4. 返されたHTMLは誰が使い、どのようにHTMLに構成されるのか?

バックエンド開発者として、私はデータに焦点を当て、Springのようなフレームワークは依存関係管理、データベース接続、アプリケーションのビルドと実行など、データに直接関係しない多くのことを処理していました。データの流れは完全に私のコントロール下にありました。

そのためReactは私を不快にさせました。ロジックは「私の最終目標はHTMLファイルを構築することなので、HTMLファイルに関連するすべてのことは私のコントロール下にあるべきで、Reactが処理すべきではない」というものでした。しかし明らかに、Reactは私が予想した以上のことを処理し、最終的に私にとって「ブラックボックス」になりました。

これら4つの質問に答えるために、ゼロから始めましょう。なぜフレームワークが必要なのか、Reactがどのような具体的な問題を解決したのかを考えてみましょう。

ネイティブJSですべてのページを書いたらどうなるか?

DOMレンダリング

最初の問題はDOM(Document Object Model)レンダリングです。簡単に言えば、HTMLはファイルで、DOMはメモリ内のそのファイルのインスタンスです。ツリー構造で保存されています。

このDOMはgetElementByIdやremoveChildのようなクエリと更新のためのAPIを提供します。Webのコンテンツを変更したい場合、このようなメソッドを使用してツリーを簡単にトラバースできます。

しかし、簡単は速いを意味しません。

投稿を2行から1000行に更新したケースを想像してください。この場合、以下のことが起こります:

  1. レイアウト再計算(リフロー):投稿2が投稿1の下にある場合、投稿2は投稿1の更新に対応するために下に移動することは容易にわかります。このステップで、DOMはCSSのデータを使用して再計算を行う必要があります。

  2. 再描画:再計算後、ブラウザは影響を受けたピクセル部分を再読み込みする必要があります。

更新は簡単で効率的かもしれませんが、その後の処理は時間がかかる可能性があります。 直感的な質問は、更新された部分だけを更新できないか?他の部分はそのままで、リフローと再描画は不要ということです。

保守性の問題

もう一つは保守性についてです。現代のソフトウェア開発の重要な側面はオブジェクト指向プログラミングです。例えば、プラスとマイナスの2つのボタンがある場合、明らかにほとんどの部分を再利用できます。しかし現在のケースでは、別々に書く必要があります。

直感的な質問は、**HTMLを抽象化することは可能か?**ということです。

Reactはこれらの問題をどのように解決するか?

仮想DOM

最初の問題に対して、Reactは仮想DOMの概念を導入しました。これは実際のDOMの抽象化です。軽量なコピーで、Reactは実際のDOM更新を行う前にすべての操作をここで行います。

Reactは2つの重要なフェーズで仮想DOMに対して操作を実行します—バッチ処理と差分比較—これによりDOM更新が最適化されます:

  • バッチ処理:Reactは仮想DOMへの複数の更新をバッチ処理します。各状態変更が発生するたびにDOMに適用するのではなく、これらの変更をメモリに保持し、一度に仮想DOMを更新します。

  • 差分比較:Reactが仮想DOMを更新すると、これらの変更を反映するために実際のDOMを更新する必要があります。これは協調と呼ばれるプロセスで行われます。

これは私を不快にさせ、Reactがブラックボックスだと感じさせた最も重要なことです。

私は実際のHTMLオブジェクトで作業しているのではなく、Reactが提供する抽象化で作業しています。

これと比較して、バックエンド開発では、インスタンスを扱うとき、実際のオブジェクトを扱っており、アドレスとポインタを使用してデータのライフサイクルを追跡することさえできます。 フロントエンドでは、状況がまったく異なります。実際のDOMオブジェクトで作業していません。 仮想DOMはReactが作成したものではありませんが、Reactはそれをより簡単にしてオープンソース化しました。

コンポーネント

Reactが更新された場所を見つける方法は、ブロックでWebページを構築することです。このようなブロックはコンポーネントと呼ばれます。各コンポーネントは独自の状態を管理し、時間の経過とともに変化に対応できる独自のライフサイクルメソッドを持っています。

JSX(JavaScript XML)

JSXはHTMLに似たJavaScriptの構文拡張で、ReactでUIがどのように見えるべきかを記述するために使用されます。同じファイルにHTML構造とJavaScriptコードを書くことができ、コードの理解と開発が容易になります。

Reactコンポーネント

Reactコンポーネントは、React要素を返すクラスまたは関数として定義できます。UIの一部の動作とレンダリングロジックをカプセル化します。

  • クラスコンポーネントReact.Componentを継承するクラスベースのコンポーネント。ローカル状態やライフサイクルメソッドなどの機能を提供します。
  • 状態:コンポーネントに属するプロパティ値を格納するオブジェクト。状態が変更されると、コンポーネントは再レンダリングされます。
  • イベント処理:ボタンがクリックされたときに状態を更新し、コンポーネントの再レンダリングをトリガーします。
  • renderメソッド:Reactクラスコンポーネントのライフサイクルメソッド。DOMにレンダリングされるべきJSXを返します。

宣言的UI

宣言的UIパラダイムでは、何をレンダリングしたいかを記述し、どのようにレンダリングするかは記述しません。Reactは宣言的UIライブラリで、UIの最終状態を指定するコードを書き、Reactが実際のレンダリングプロセスとDOM更新を処理します。

単方向データフロー

単方向データフローは、アプリケーション内のデータが単一のパスに従うことを意味します。通常、これは親コンポーネントから子コンポーネントへpropsを通じて行われます。状態は通常、より高いレベルのコンポーネントで管理され、データやデータを操作する関数がpropsとして子コンポーネントに渡されます。これによりデータフローが予測可能になり、デバッグが容易になります。

まとめ

これで4つの質問に答えることができます:

  1. React.Componentとは何か?なぜextendsで、なぜexportなのか?
  • React.Component:Reactライブラリの基底クラスで、コンポーネントはこれを継承します。
  • extends:クラスを別のクラスの子として作成するために使用されます。
  • export:コンポーネントを他のファイルでインポート可能にします。
  1. stateとは何か(同様に、useEffectやhooksとは何か)?
  • 状態:アプリケーションで追跡する必要があるデータ。状態変更はコンポーネントの再レンダリングをトリガーできます。
  • Hooks:React 16.8で導入され、クラスを書かずに状態や他のReact機能を使用できます。
  • useEffect:関数コンポーネントで副作用を管理するhook。
  1. render()とは何か、なぜ{}が続き、HTMLの一部を返すのか?
  • render():画面に表示されるべきものを指定するライフサイクルメソッド。
  • {}:JSX内でJavaScript式を埋め込むために使用。
  • HTMLを返す:技術的には、render()はHTMLではなくJSXを返します。
  1. 返されたHTMLは誰が使い、どのようにHTMLに構成されるのか?
  • 誰が使うか?render()から返されたJSXはReactが使用します。ReactはこのJSXを実際のDOM要素に変換し、ブラウザのDOMツリーにマウントします。
  • HTMLへの構成:内部的に、ReactはJSXをReact.createElement()呼び出しに変換します。これらの呼び出しはReactがツリー構造を追跡し、最終的にDOMを効率的に構築・更新するために使用するオブジェクトを返します。