🌟

React再入門 ~ Reactの背景と特徴 ~

2025/01/21に公開

はじめに

株式会社 Rehab for JAPAN のまっちゃんです。社内で「初めてReactを触ってるんだよね」という声をしばしば耳にしていたため、少しでもReactが好きになってもらえるように?Reactのことについてまとめていきます。
自分も曖昧な部分があったため改めて学び直すという意図もあります。

今回の記事ではReactの特徴をReactが登場するまでの課題を踏まえてまとめました。このまとめ方をしている理由としては、背景を知ることでReact「何を」解決しようとして「どのような方法」を取り入れたのかを理解することで、より深くReactの設計思想が理解できると考えたためです。

Reactが誕生するまで

その前に

先に断っておきますが、私が初めてReactを触ったのは、React16のリリース後であり、また実装年数も数年で、長いわけではありません。これから述べる背景については、いろいろな記事や書籍を参考にしてまとめております。もし誤りがあれば、ご指摘いただけると幸いです。

AjaxとGoogleMapの登場

2005年にGoogleマップがリリースされ、Web開発において衝撃を与えました。Ajaxは以前から登場していた技術ですが、それまでとは一線を画す使用の仕方をしていました。GoogleMapではAjaxを使用することでページを遷移することなく地図をローディングし表示しました。

従来のページ遷移

Ajaxの技術ができるまでは、サーバーへのアクセスはページ遷移を伴うものでした。そのため、1ページで複数の情報を表示することが難しかったのです。この問題をAjaxが解決しました。Ajaxが登場するまでは、JavaScriptはUIの装飾品程度のものだと考えられていたのが、高度なアプリケーションを作るための言語として注目されるようになりました。

SPAの考え方の登場

Ajaxの登場により、ユーザが操作したある時々に応じた必要なデータのみ取得し描画するということができるようになりました。この考え方を発展させて、従来サーバーサイドで行っていた、ページ遷移やデータの取得をフロントエンドで行うようにすることでよりリッチなUIを提供することができると考えSPA(シングルページアプリケーション)の考え方が生まれました。

jQueryの登場

GoogleMapに衝撃を受けた開発者は、Ajaxを使用してWebアプリケーションを簡単に作成できるライブラリを求めました。そこで、jQueryが登場しました。このときは今ほど、Web技術の標準化が進んでおらず、ブラウザごとに処理を変えないといけないのは当たり前で、クロスブラウザ対応しようとすると、コードが複雑になりなり、バグも多かったようです。
そんな中、jQueryはAjaxの処理はもちろん、ブラウザ間の差分を吸収することができるライブラリでもあったため、広く使われました。

標準化の進展

そうこうしている間に、標準化が進み2009年にはEcmaScript5がリリースされました。このバージョンでは、JavaScriptの標準化が進み、ブラウザ間の違いを吸収する必要がなってきました。

AngularJSの登場

2010年頃には、フロントエンドアプリケーションが持つ範囲が大きくなり、JavaScriptのコード量が増えるに当たり保守性の問題が多く発生するようになりました。そこで、大規模化フロントエンド開発をしやすくするようなアーキテクチャが必要とされるようになりました。
大規模開発をしやすくするために、Googleが開発したのがAngularJSです。MVC(Model-View-Controller)アーキテクチャを採用したフロントエンドフルスタックフレームワークです。

双方データバインディング

AngularJSは双方向データバインディングを採用しています。この仕組みにより、モデルとビューが自動的に同期され、手動のDOM操作が不要になりました。しかし、アプリケーションが複雑化するにつれ、双方向データバインディングがパフォーマンスのボトルネックとなり、デバッグも困難になる課題が浮き彫りになりました。
その結果、これらの問題に対応するために大きく内部アーキテクチャが変更されたAngular(2.0)が2015年にリリースされました。

Reactの登場

AngularJSがフロントエンドの開発の圧倒的なシェアを取っていた時期に、FaceBookでは社内で開発したMVCフレームワークBoltJSを使用して開発を行っていました。しかし、次第に巨大化するフロントエンドの領域において、UI部分でバグが多く発生したようです。その解決策として、提案されたのがReactでした。

Reactの特徴としては、以下のようなものがあります。

  • 宣言型プログラミング
  • コンポーネント指向
  • 単方向データフロー
  • 仮想DOM

これらの技術は今までのフロントエンドの考え方とは全く異なるものでした。最初は社内で受け入れられるにも時間がかかったようです。FaceBookがInstagramを買収し、InstagramやNetflixでのReactの採用から徐々にReactの人気が上昇し、2016年にはGitHubで50,000スターを獲得するに至りました。
Reactの考え方は、今では多くのフレームワークに影響を与え、Reactの思想を取り入れたフレームワークが多く登場しています。

参考

https://legacy.reactjs.org/blog/2016/09/28/our-first-50000-stars.html

↓こちらはReact開発経緯をドキュメンタリー形式でまとめた動画です。
従来の概念と異なっていてなかなか浸透しなかったことがよく伝わってきます。

https://youtu.be/8pDqJVdNa44?si=JUy5gYQa4-ndZNg6

Reactの特徴

このような時代背景から作成されたReactは以下のような特徴を持っています。

宣言的UI

Reactは宣言的UIを謳っています。宣言的UIとは、UIの状態を宣言することで、その出力を得るコーディング手法です。Reactでは、UIの状態を宣言するだけで、Reactに状態、描画を委譲することで複雑な状態管理をしなくて良くなります。

命令型プログラミング

宣言型プログラミングと対比されるのが命令型プログラミングです。
命令型プログラミングとは、最終的な出力を得るために時系列順に命令(コード)を書いて、直前の状態に依存しながら命令を書いていく手法です。

// 配列の要素を2倍にする
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = [];

for (let i = 0; i < numbers.length; i++) {
  doubledNumbers[i] = numbers[i] * 2;
}

console.log(doubledNumbers); // [2, 4, 6, 8, 10]
逐次処理

処理が順番に記述されており、forループを使って配列の要素を1つずつ処理しています。
各ステップでどのように処理を行うかが具体的に記述されています。

状態への依存

ループ実行時ごとに、doubledNumbersの状態が変化していきます。ループごとにdoubledNumbersに依存する処理になっています。

宣言型プログラミング

一方、宣言型プログラミングでは、出力の状態を指定(宣言)することで、その出力を得るコーディング手法です。

// 配列の要素を2倍にする
const numbers = [1, 2, 3, 4, 5];

const doubledNumbers = numbers.map(num => num * 2);

console.log(doubledNumbers); // [2, 4, 6, 8, 10]
逐次処理の抽象化

逐次処理を行うためのforループがなく、map関数を使って配列の各要素を2倍にしています。処理が抽象化されており、map関数を使って配列の各要素を2倍にしています。

状態への依存の排除

map関数は、引数として渡された関数を各要素に適用し、新しい配列を返します。このため、doubledNumbersnumbersの状態に依存していません。

Reactの宣言的UI

命令型での課題

上記のような簡易なコードでは、命令型のコードでも問題は発生しづらいです。しかしUIを命令型で扱う場合、その時々のUIの状態を意識しながらコードを書く必要があります。
「ボタンの状態」で考えてみると、押下中の状態、非活性の状態、活性の状態、ボタン押下後のエラーの状態などなどたくさんのUIの状態を扱います。
これらを時系列順に状態を管理する命令型でプログラミングを行うと、複雑になり、UIの状態を把握するのが難しくなります。予測可能性が悪くなり、デバッグやテストが難しくなります。

Reactは宣言型プログラミングの考え方のもとで設計されています。UIの状態を宣言するだけで、Reactに状態、描画を委譲することで複雑な状態管理をしなくて良くなります。

<input type="checkbox" checked={true}/>

宣言型UIのメリット

  • 可読性向上: UIの状態を宣言するだけで「何を」行うかが予測可能なため、コードの可読性が向上する
  • 保守性向上: 状態管理が簡単になり、保守性が向上する

コンポーネント指向

従来のアーキテクチャ

Reactが登場するまでのアーキテクチャでは、MVC(Model-View-Controller)やMVVM(Model-View-ViewModel)が主流でした。これらのアプローチでは、データ(Model)、UI(View)、ロジック(ControllerまたはViewModel)のようにシステム上の役割を関心事として分割しています。

従来のアーキテクチャでの課題

しかし、特にUIのロジックや状態管理が複雑になると、Viewがイベント処理や状態管理の責務を担うようになり、結果として密結合が発生し、再利用性や保守性が低下する問題がありました。

コンポーネント指向での解決策

従来のアーキテクチャでの状態管理の複雑さという問題の解決策として生まれた考え方がコンポーネント指向です。コンポーネント指向では、UIを小さな部品(コンポーネント)に分割し、それぞれを再利用可能な単位として設計します。たとえば「ボタン」「フォーム」「カード」などをコンポーネント化することで、独立したUI要素を効率的に開発・管理できます。

従来のWeb開発でよく用いられていたMVC(Model-View-Controller)モデルでは、UIの見た目やイベント処理、状態が分散して管理されていましたが、コンポーネント指向であるReactは、見た目(View)、イベント処理、状態(Modelの一部)を1つのコンポーネントに統合して管理します。Reactのコンポーネントにおける状態は、従来のMVCモデルのModelのようにアプリケーション全体のデータを管理するのではなく、そのコンポーネントのUIの機能を管理するためのものです。

const ButtonComponent = () => {
  // 状態(Model)を管理
  const [count, setCount] = useState(0);

  // 状態を直接UI(View)にバインドし、イベント処理を統合
  return (
    <div>
        <button onClick={() => setCount(count + 1)}>
          Click me
        </button>
      <p>Count: {count}</p>
    </div>
  );
};

コンポーネント化のメリット

  • 再利用性: 同じコンポーネントをアプリケーションの様々な場所で使い回せるため、開発効率が向上する。
  • 保守性: コンポーネントが独立しているため、修正や変更の影響範囲を局所化できる。
  • テストの容易性: コンポーネント単位でテストができるため、テストが書きやすくなる。
  • カプセル化: コンポーネントの内部実装を隠蔽し、外部からの影響を受けにくくできる。

単方向データフロー

双方向データバインディング

Reactが登場する前のフロントエンドフレームワークでは、双方向データバインディングが採用されているものが主流でした。双方向データバインディングとは、データ(Model)の変更がUI(View)に反映され、UI(View)の変更がデータ(Model)に反映される仕組みです。

双方向データバインディングの課題

大規模アプリケーションを開発する際に、双方向データバインディングは複雑さを増す要因となってきました。データの変更が自動でUIに変更され、逆も自動反映されることは便利だったのですが、アプリケーションが大規模化するにつれ、データの変更がどこで行われているのか、UIの変更がどこで行われているのかがわかりにくくなり、デバッグやテストが困難になる問題がありました。

単方向データフローでの解決策

状態の変化が一方的に成約することで、予測可能なデータの流れになるという解決アプローチを取りました。
単方向データフローとは、データが一方向のみに流れることを示しています。Reactでは、親コンポーネントから子コンポーネントへデータが渡される一方通行のみのデータの流れになっています。これはフォームも同様で、UIから状態を変更する際も、UI→イベント→状態のように、一方向へのデータの流れになっています。

単方向データフローのメリット

  • 予測可能性: データの流れが一方向のみであるため、データの変更がどこで行われているのかが明確になり、予測可能性が向上する。
  • 保守性の向上: データの流れが一方向のみであるため、データの変更がどこで行われているかが明確になるため影響範囲が特定しやすく、コードの修正がしやすくなります。

仮想DOM

従来の画面更新方法

従来のJavaScriptフレームワークはDOMを直接操作しUIを更新していました。

// 元のDOM
const element = document.getElementById('app');
element.innerHTML = '<h1>Hello World!</h1>';

上記のような単純なDOM操作であれば問題ありませんが、複雑なUIを扱いDOM操作が多くなるとパフォーマンスの問題が発生しました。DOM操作は高コストであり、UIの更新が頻繁に行われる場合、パフォーマンスが低下する問題がありました。

仮想DOMでの解決策

Reactは仮想DOMを採用することで、DOM操作のコストを削減しパフォーマンスを向上させています。仮想DOMとは、実際のDOMの構造をメモリ上にJavaScriptオブジェクトとして表現したものです。この仮想DOMと実際のDOMを比較し差分を検出し必要な部分のみを更新することで、不要な更新処理を最小限化し、パフォーマンスを向上させています。

ReactのUIの更新方法は大きく3つのプロセスがあります。

  1. コンポーネントの状態が変更される
  2. 仮想DOMが再構築される
  3. 実際のDOMと仮想DOMを比較し差分を検出
  4. 差分のみを更新

このプロセスにより、不要なDOM操作を最小限に抑えパフォーマンスを向上させています。

まとめ

今回の記事では、Reactの設計思想の特徴である「宣言的UI」「コンポーネント指向」「単方向データフロー」「仮想DOM」について、Reactが登場するまでの歴史的背景を踏まえながらまとめました。

これらの特徴は、従来のWeb開発における課題を解決するために生まれました。

  • 宣言的UIは、UIの状態を宣言的に記述することで、コードの可読性と保守性を向上させます。
  • コンポーネント指向は、UIを再利用可能な部品に分割することで、開発効率と保守性を高めます。
  • 単方向データフローは、データの流れを一方向に制限することで、アプリケーションの状態を予測可能にし、デバッグやテストを容易にします。
  • 仮想DOMは、DOM操作のコストを最小限に抑え、パフォーマンスを向上させます。

これらの特徴が組み合わさることで、Reactは大規模で複雑なアプリケーションでも効率的に開発・保守することを可能にしています。

参考

https://ja.react.dev/learn/reacting-to-input-with-state#how-declarative-ui-compares-to-imperative
https://book.impress.co.jp/books/1115101137
https://booth.pm/ja/items/2368019

Rehab Tech Blog

Discussion