29
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NIJIBOXAdvent Calendar 2023

Day 15

フロントエンドの変遷とReact:なぜ React が主流になったのか

Last updated at Posted at 2023-12-14

僕がサーバーサイドからフロントエンドへの転向を試みていた頃、世は React vs Vue で盛り上がっていました。あらゆる記事の結論だけを掻い摘んで、「なんとなく React 優勢じゃね?」と悟った僕はそのまま React の学習を開始しました。

これまで片手間で jQuery を使用する程度だった僕のフロントエンド領域は、コンポーネントを組み合わせてビューを構築するモダンな設計スタイルに感銘を受け、これをやる為に生まれてきたのだと豪語するようになりました。

そこから「React をやらせてくれ。実務経験は無い」だけを発信する無謀な転職活動を行い、まんまと React 案件に携わることが出来ました。

しかし、実務で使用する React の奥深さに打ちのめされ、すぐに存在意義を見失いました。

React がここまで主流になった理由と、何を解決するために生まれたのかを理解しないまま React と向き合うのは困難だと悟りました。

この記事では、フロントエンド開発の変遷と React の特徴について見ていきます。

フロントエンドの変遷

JavaScript の登場とブラウザ戦争 (1990年代中頃)

90年代中頃に Netscape が JavaScript を導入し、これまでの HTML&CSS では実現出来なかった動的な要素やインタラクティブな機能が追加できるようになります。

しかし、当初はブラウザ間の互換性に課題があり、様々なブラウザが異なる JavaScript エンジンを搭載していました。その為、同じ JavaScript コードが異なるブラウザで動作しないといった問題が頻発することになります。

この時代は異なるウェブブラウザメーカーが市場でシェアを競い合った時期であり、ユーザーも多様なブラウザを利用していたため、ブラウザ互換性のない JavaScript を広く活用することはとても困難でした。

Flash 全盛時代と Ajax (2000年代初頭)

Flash は Adobe によって提供されたプラグイン技術で、アニメーション、音声、ビデオなどのマルチメディア体験を提供できました。
また、クロスプラットフォーム対応であり、異なるOSやブラウザで一貫して動作するという利点がありました。これによって、様々なブラウザ上でインタラクティブなユーザー体験を提供することができ、Flash は大流行します。

それとほぼ同時期に登場したのが Ajax です。Ajax はサーバーと非同期でデータをやり取りすることができ、ウェブページの一部を再読み込みせずに更新することが可能になりました。これによって Ajax は、Flash とは別のアプローチでインタラクティブなユーザー体験を提供することが出来ました。

Ajax は Google Maps で採用されたのをきっかけに浸透していき、しばしば Flash vs Ajax などの議論が繰り広げられるようになります。
最終的に Flash は衰退しますが、Ajax はその後も活躍し続けることになります。

jQuery による革命 (2000年代中頃)

2006年にリリースされた jQuery は、これまでの JavaScript が抱えていた致命的な問題の多くを解消しました。
jQuery はブラウザの差異を吸収し、統一された API を提供することで、クロスブラウザの課題を解決することができました。さらに、当時の JavaScript では複雑化していた DOM 操作をシンプルかつ直感的な方法で実装することができ、イベント処理も簡略化され、Ajax との相性も良好でした。アニメーションやエフェクトの追加にも適しており、JavaScript 開発の効率性が飛躍的に向上してフロントエンド界隈が盛り上がりを見せます。

フロントエンドエンジニアという言葉が一般的に認知され始めたのもこの頃です。

クライアントサイドMVC と jQuery の課題 (2010年代初頭)

jQuery が広く活用され、大規模なアプリケーションにも採用されるようになってくると、徐々に万能ではないという事に気づき始めます。
jQuery は HTML に後付けするイベントハンドラがとても命令的でした。

・DOM を参照し
・イベント処理を行い(○○が○○ならXXをして〜)
・DOM を更新する

この一連の流れがイベントハンドラ毎に行われます。これはイベントハンドラが増加する程に冗長になるというだけではなく、 DOM の更新処理も各地に散らばる事を意味しています。

アプリケーション規模が大きいと DOM 構成が複雑化し、イベントハンドラが更新した DOM をさらにイベントハンドラが更新するという依存関係が生まれます。あるイベントハンドラが動作する前提となる DOM 構造は、どのイベントハンドラによって構築され、あるいは破壊されるのか、これらを把握しきれなくなり、デバッグがとても困難になっていきます。

それだけではなく、当時主流だった MVC フレームワークとの組み合わせにも課題がありました。サーバーサイドで作成した View が HTML としてレスポンスされますが、この HTML はマスタリソースではなく、マスタは View のテンプレートです。しかし、jQuery などが行うクライアントサイドの処理対象はテンプレートではなく、テンプレートから生成された HTML です。

つまり、クライアントサイドで必要な HTML の修正はテンプレート側で行う必要があります。フロントエンドとサーバーサイドの開発サイクルは異なるので、軽微な HTML の変更であっても、想定以上の手間と時間がかかることを意味しています。

これらの問題を解決するべく、クライアントサイド MVC/MVVM という設計スタイルが誕生します。
サーバーサイドで利用されていた MVC(Model、View、Controller)フレームワークのようなアーキテクチャをクライアントサイド開発にも導入しよう、という考えです。

Angular.js、Backbone.js などのフレームワークが開発され、jQuery が苦手とするアプリケーション全体を構造化する枠組みが提供されるようになります。Model、View、Controller に役割を分離し、ロジックが UI に依存しない状態にすることで、UI の変更がロジックに影響を与えなくなります。

Frame 1 (1).png

そして、Controller が URL ハンドリングを行うことで SPA(Single Page Application) を実現できるようになります。これまでは該当ページの HTML がサーバーからレスポンスされることで画面を表示していました。新たなフレームワークでは、サーバーは空の HTML をレスポンスし、各ページの DOM を JavaScript が更新して表示を切り替え、Controller で URL をルーティングする訳です。

Model で値を保持し、イベントは Controller、画面描画と DOM 更新は View が行うことで依存関係を除外し、全てを同時に行なっていたことで構造が複雑化した jQuery の問題を解決します。

また、クライアントサイド MVC の登場によって、サーバーサイドの MVC フレームワークにおける View の必要性が減少します。クライアントサイドの View が部分的に更新可能なテンプレートの役割を果たすようなるからです。非同期で取得した値を Model が View に伝搬して動的にコンテンツを生成することが出来るようになります。

クライアントサイドMVC における View がマスタリソースとなり、サーバーサイドで完全な HTML を生成する必要がなくなります。これによって、jQuery 時代に発生していたフロントエンドとサーバーサイドにおける HTML の連携問題も解消されていきます。

React の誕生と役割

これまでのフロントエンドの変遷から、ようやく React が誕生します。

React は クライアントサイド MVC ではなく View に特化したライブラリなので、誕生当初は他のクライアントサイド MVC と組み合わせて利用されていました。その為、View レイヤ以外の状態管理やルーティングをどうするか? は別途考慮しなければなりませんでした。

React は誕生当初からコンポーネント内で状態(state、props)を管理する仕組みを提供していましたが、コンポーネントツリーが深くなると状態の共有と管理が複雑になりました。
これらの回答が Redux や Context API だったりします。

また、その後のアップデートで React Router を提供してルーティングも行えるようになります。しかし、より直感的で SSR(Server Side Rendering)にも対応している Next.js が開発者に好んで採用されていくようになります。

このように、View 層に焦点を当てて誕生した React は、徐々に Model や Controller の役割をその後のアップデートとコミュニティサポートによって補填されていきます。
クライアントサイドMVC は、React の成長と共に少しづつ役目を終えていくことになります。

ここからは React の特徴を解説していきます。

宣言的UI とコンポーネント志向

宣言的UI は、最終的な UI がどのようになるのかを先に記述するアプローチです。
望まれる結果を指定して、状態に対して一意に定まる UI を定義します。

宣言的という言葉の対義語が命令的です。UI の外観を達成するための手順を明示的に指示していくアプローチがそれです。 JavaScript や jQuery で行なっていた処理などが該当します。

// 命令的UI は、具体的な手順や画面の更新処理などを直接記述します。
var p = document.createElement('ul');
var c = document.createElement('li');
c.textContent = 'Item 1';
p.appendChild(c);
document.body.appendChild(p);
// 宣言的UIは、あるべき状態を記述して UI を構築します。
const MyComponent = () => {
    return (
      <ul>
        <li>Item 1</li>
      </ul>
    );
}

そして、React はコンポーネント志向です。UI を再利用可能な部品(コンポーネント)に分割し、それらのコンポーネントを組み合わせて最終的な UI を構築します。各コンポーネントは独自に状態やイベントを保持し、外部から渡されたプロパティ(props)に基づいて表示を決定します。

Frame 2.png

コンポーネントは状態を引数にとって DOM をリターンする関数のようなもので、状態が変わる毎にコンポーネントを実行して新しい DOM を構築します。状態が変わる度にコンポーネントをレンダリングして、DOM をゼロから構築するので、更新について考慮する必要はありません。これが宣言的UI の本質です。

状態の変化によって DOM をゼロから構築する、という手法は実際の DOM で行うと描画コストが重く、画面のチラつきやイベントリセットなどの課題があり実現が困難でした。

この問題を解決したのが仮想DOM です。

革新的な仮想DOM

DOM(Document Object Model)は HTML や XML文書をプログラムから操作可能にする API です。

仮想DOM は実際の DOM と同じ構造を持つ、軽量な JavaScript オブジェクトです。DOM はツリー構造となっており、対となる仮想DOM も同様のため容易に比較を行うことが出来ます。

仮想DOM は変更前後で2種類保持されます。UI の状態が変更されると、現在の仮想DOMツリーが変更を検知して構築されます。そして、現在の仮想DOMツリーと前回の仮想DOMツリーを比較して差分箇所を特定し、実際の DOM に差分のみを適用します。

Frame 3 (2).png

React 以前、仮想DOM の概念が登場するまでは、DOM は巨大なグローバル変数でした。DOM の直接的な操作がグローバルなスコープで行われ、全てのスクリプトやコンポーネントが同じ DOM にアクセスしていました。これはデバッグが困難になるだけでなく、パフォーマンスにも影響がありました。

また、直接的に DOM を操作することで状態と DOM が密接に結びつくケースもありました。
通常、JavaScript の変数やオブジェクトなどにアプリケーションの状態が保持され、それに基づいて DOM が動的に生成・変更されます。

一方で、一部のアプローチでは DOM そのものに状態が反映されることがあります。HTML の要素にカスタム属性を使ってデータを埋め込んだり、特定の要素の属性値を変更することで状態を表現していたりします。これによって状態が JavaScript にあるのか、DOM にあるのかが曖昧になり、状態管理が複雑になっていきます。

React は各コンポーネントが独自の状態を保持するように設計されています。DOM の直接的な操作を避け、各コンポーネントが独立して動作し、状態管理が容易になっています。

そして、コンポーネントが状態の変化に応じて構築する DOM を仮想DOM に向けることで、DOM の更新に関するパフォーマンスの課題を改善し、宣言的UI の実現を可能としています。

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

React は単方向データバインディングです。データの伝播が親コンポーネントから子コンポーネントへ一方通行であり、子から親にデータの受け渡しはできません。子が親のデータを更新する場合は、親から子へコールバック関数を渡す必要があります。

Angular.js や Vue.js などで採用されていたのが、これと対になる双方向データバインディングです。この頃に登場したフレームワークは MVVM(Model View ViewModel)などと呼ばれていたりします。

双方向データバインディングは、View の変更と Model の変更を ViewModel によって共通化します。View を更新すると Model の値が、Model が更新されると View の値が連動して反映されます。

Frame 5.png

これによって手動で値を更新する必要がなくなり、直接 DOM を操作せずに View を更新することができるようになります。
フロントエンド界隈はこの技術に熱狂し、様々なフレームワークが登場します。この時代に生まれたフレームワークは MVVM や MVW(Model View Whatever)、 MV* フレームワークなどとも呼ばれるようになります。

双方向データバインディングの主な課題は、データのフローが複雑で予測できなくなることです。Model と View が双方向にイベントを交換し、Model と View の間で状態変化のトランザクションが発生します。これによって、アプリケーションの状態を理解し、デバッグするのが困難になってしまいます。

単方向データバインディングは、データがどのように更新されるのか予測しやすくなっています。データの変更は子コンポーネントへ一方向に流れます。これにより、アプリケーションの動作を追跡しやすく、デバッグが容易になります。

双方向データバインディングでは View と Model の間でデータが相互に影響し合うため、常にゼロから状態を構築することは難しくなります。MVVM フレームワークでは双方向にデータが流れ、状態が各地に散らばり、各所で DOM を更新していました。

一方で、単方向データバインディングでは状態をコンポーネントが管理しています。コンポーネントは状態変化に応じて再レンダリングされ、新しいビューを生成し、状態とビューを同期します。常に状態とビューを構築し直す、という手法も、仮想DOM によって実現可能となりました。

React は仮想DOM を使用して効率的に新しいビューを実際の DOM に反映し、常に最新の状態でビューを生成しています。

まとめ

僕は実際に全てを経験した訳ではありませんが、時代は常に何かを改善する為に進化していて、React のアプローチはとても革新的に思えました。

近年では、React 以前のフロントエンドを知らない世代も多いと思います。宣言的 UI やコンポーネント志向などの設計スタイルは、それ以前を知っていなければ、その革新性に震えることはできません。この革新さを実感することが学びのモチベーションに繋がったりもするので、How だけではなく Why も紐解いていくのが良いと僕は思います。

React は現在も進化し続けており、最近では React Server Components によって SSR 機能に発展の兆しが見えたことや、Concurrent Mode によるレンダリングプロセスの改善などが話題になっています。

いつか React に置き換わる新たな革命がやってくるのかもしれません。
その時はリアルタイムでそれに熱狂したいなと思いました。

29
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?