【Web開発】サーバーサイドレンダリング(SSR)→Ajaxの出現とSPA普及→仮想DOM(React)→PWA

この記事は約17分で読めます。

はじめに

ウェブ開発は、よりインタラクティブでレスポンシブなUXを実現するために進化し続けている。サーバーサイドレンダリング(SSR)から始まり、Ajax、シングルページアプリケーション(SPA)、仮想DOMという進化の過程を記載。

サーバーサイドレンダリング(SSR):〜2000年

初期のウェブ開発では、すべてのコンテンツがサーバー側で生成され、クライアント(ブラウザ)に送信されていた(=SSR)。

ユーザーがリンクをクリックすると、新しいページのリクエストがサーバーに送られ、サーバーは新しいHTMLページを生成してクライアントに送り返す。

静的なHTMLファイルが表示されるので、データの変更などが合ってもリロードをしなければ反映されない。

利点: 完全にレンダリングされたページがユーザーに速やかに提供されるため、初期表示の速度が早い。

欠点: ユーザーが異なるページに移動するたびに、サーバーから全てのHTMLを再度ロードしなければならないため、ページの遷移が遅くなる可能性がある。画面全体が一瞬真っ白になり、その間はユーザーは操作できない

Ajaxの出現→SPAが普及:2000~2010年頃

Ajax(Asynchronous JavaScript and XML:非同期JavaScriptとXML)は、2000年代初頭に登場した。AjaxはJavaScriptを使用して非同期にサーバーと通信を行い、ウェブページの一部を動的に更新する技術。これにより、ユーザーはよりスムーズでインタラクティブなウェブ体験を得ることができるようになった。

Google Mapsは、この技術を広く知らしめた一例。地図の表示部分を移動するたびに、画面全体が真っ白になる(SSR)ことがない。

利点: ページの一部のみを更新できる(ページの再読み込みがない)。1つの処理が終わるまで待つ必要はなく、他の操作をする事が出来る。

欠点: 複雑なAjaxアプリケーションは管理が難しくなる可能性がある。

Ajaxの仕組み:XMLHttpRequest / JavaScript / DOM

Ajaxでは、XMLHttpRequest、JavaScript、XML、JSON、DOMなどの技術や概念が合わさっている。

ただし、XMLHttpRequestオブジェクトは、その名が示すように当初はXMLデータの取得を主な目的としていたが、任意の種類のデータを扱うことができ、必ずしもXML形式である必要はない。また、サーバーからのレスポンスの形式でXML以外の形式(JSONなど)も渡せるので、こちらでもXMLである必要はない。

当初、Ajaxは主にXML形式のデータの受信・送信に使用されていたが、現在ではJSONがより広く採用されている。XMLは依然としてオプションとして利用可能だが、Ajax通信で必須ではない。つまり、Ajax(Asynchronous JavaScript and XML)は、その名前にもかかわらず、XMLに限定されないので、現時点ではXMLという表現はミスマッチかもしれない。

出典:https://pikawaka.com/word/ajax

1. イベントのトリガー

ユーザーがウェブページ上でアクション(例えば、ボタンクリック)を行うと、そのイベントがトリガーとなる。

2. サーバーへの非同期リクエスト

イベント発生後、JavaScriptを使用してXMLHttpRequestオブジェクトを作成する。このオブジェクトを使って、サーバーに対して非同期でリクエストを送信する。リクエストには、ユーザーが必要とする情報や期待するレスポンスの形式(JSON、XMLなど)が指定される。

3. サーバーでのリクエスト処理

サーバーはリクエストを受け取り、必要な処理を行う。この間、クライアントサイドでは他の操作が可能で、ユーザーは待機することなくページと対話を続けられる。

4. レスポンスの送信

サーバーでの処理が完了すると、結果はJSONやXMLなどの形式でクライアントに送信される(レスポンス)。

サーバーへの通信がXMLHttpRequestオブジェクトを使用してるので、XMLしかデータを受け取れないように思えるが、XML以外にもデータを受け取る事ができる(JSONなど)。

ちなみに、XMLはExtensible Markup Languageの略でマークアップ言語。HTMLと似ているが、HTMLと異なり、タグの名前を自由に設定できるので、複数の処理をやり取りする際の管理に最適。

5. DOMの更新

クライアントサイドのJavaScriptは、受信したレスポンスを使ってDOM(Document Object Model)を更新する。このステップでは、ページの一部だけが新しいデータで書き換えられる。このため、ページ全体をリロードする必要がなく、ユーザーにとっては滑らかでシームレスな体験が提供される。

DOMは、HTMLやXMLをオブジェクトの木構造モデルで表現することで、ドキュメントをプログラム(JavaScriptなど)から操作・利用することを可能にする仕組み。

DOMに関しては、こちらの説明が分かりやすい。

DOM ?? とは?? #1|OCH リゾラボ
はじめに こんにちは! 最近、頭の中の情報がごちゃごちゃしてきて泣きそうな樺山です。 Python に始まり、Django/Vue/JavaScript、NoCode や Webサイト構築系その他もろもろ。 それぞれ中途半端な学習になってる...

下記コードでは、ユーザーが「データをロード」ボタンをクリックすると、script.jsdata.txtからデータを非同期にロードし、そのデータをdataContainerというIDを持つdivタグ内に挿入する。

このプロセスはページ全体の再読み込みを行わずに実行されるため、ユーザーはページの他の部分を使用し続けることができる。また、更新が必要なのはdataContainer内のコンテンツのみであり、ページ全体ではない。

HTML
<!DOCTYPE html>
<html>
<head>
    <title>Ajax Example</title>
</head>
<body>
    <button id="loadButton">データをロード</button>
    <div id="dataContainer">ここにデータが表示されます</div>

    <script src="script.js"></script>
</body>
</html>
JavaScript
// script.js
document.getElementById('loadButton').addEventListener('click', function() {
    var xhr = new XMLHttpRequest(); // XMLHttpRequest オブジェクトを作成
    xhr.open('GET', 'data.txt'); // data.txtからデータを取得するGETリクエストを設定
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
            document.getElementById('dataContainer').innerHTML = xhr.responseText; // レスポンスをdataContainerに挿入。DOMが使われている
        }
    };
    xhr.send(); // リクエストを送信
});

Ajaxの仕組みに関して、詳しくはこちら。

Ajaxとは?初心者向けに豊富な画像で仕組みを解説
Ajaxとは、JavaScriptやDOMやXMLなど既存の機能を組み合わせた手法のことです。Ajaxを実装すると、画面遷移なしで、ページの一部だけ更新する事が出来ます。Ajaxの最大の特徴でもある非同期通信など初心者の方に少し難しい部分は...

シングルページアプリケーション(SPA)

Ajaxの発展により、ウェブアプリケーションはさらに進化し、シングルページアプリケーション(SPA)の概念が登場した。

SPAでは名前の通り、単一のページでコンテンツの切り替えを行うWebアプリケーションのこと。

最初に画面(HTML)を取得した後は、JavaScriptによって画面の書き換えを行うことで、単一ページでありながらアプリケーションを構築することができる。

ページの初回読み込み

  • SPAでは、初回アクセス時にアプリケーションの全てのコアアセット(HTML、JavaScript、CSS)が一度にクライアントにロードされる。具体的には、JavaScriptやCSSのバンドラーが、複数のJavaScript, CSSファイルをビルド時に1つのファイルに結合する。
  • 従来のマルチページアプリケーション(MPA)のアプローチと異なり、ページ間での遷移があるたびに必要なリソースをサーバーからロードする必要がなくなる。

ページ間の遷移

  • JavaScript(Ajaxを含む非同期通信)を用いて必要なデータのみをフェッチし、クライアントサイドでビューを動的に更新する。
  • 単一ページ=単一URLということではなく、URLはJavaScriptで操作可能なので(ReactやVue.jsなどのフロントエンドフレームワークの場合はReact Router, Vue Routerなどを使う)、URLが変更されてもページのリロードは発生せず単一のページのままであることが可能。ユーザ視点では、MPAと同じようにページ遷移しているように見えるが、実際には単一ページ。

利点: ページ遷移がスムーズ
欠点: 初期ロードに時間がかかる場合がある。SEOにも影響を与える可能性がある。

仮想DOMの導入:2013年

Ajaxが非同期通信を可能にし、ページ全体の再読み込みなしにサーバーからデータを取得し、特定のDOM要素を更新することができるようになった。

しかし、このプロセスは往々にしてJavaScriptコードの複雑さを増大させ、大規模なアプリケーションでは特に、DOMとデータの整合性を保つことが困難になった

コードの複雑さの課題を解消するため、FacebookがReactというライブラリを2013年に公開した。ReactはDOMと取得データを同期させる手段として、仮想DOMというアプローチを取った。

コンポーネントの状態が変更されると、Reactは仮想DOMを再構築し、以前の仮想DOMとの差分を計算する(Reconciliation:再調整という)。その差分を実際のDOMに適用することで、必要な部分のみを更新する。

仮想DOM

  • メモリ空間などで、擬似的にDOMを再現した構造体。変更すると、メモリ空間上にあるデータが書き換わる。
  • 名前にDOMと付いているが、直接ブラウザに表示されているオブジェクトではない。普通のJavaScriptオブジェクト。
  • 仮想DOMの一部の更新を検知し、DOMと同期させることで、その差分のみを検知することができる。このプロセスは自動的に行われ、開発者はどのDOM要素を更新すべきかを個別に指定する必要はない。
React DOM Components – React
The library for web and native user interfaces

リアルDOMと仮想DOMの違い

例)カウンター

リアルDOM

HTML
<!DOCTYPE html>
<html>
<head>
    <title>Counter Example</title>
</head>
<body>
    <div id="app">
        <p id="counter">0</p>
        <button type="button" id="increment">+1</button>    
    </div>

    <script>
        let count = 0;
        const btn = document.getElementById('increment');
        const counterElement = document.getElementById('counter');

        btn.addEventListener('click', () => {
            count++;
            counterElement.innerText = count;
        });
    </script>
</body>
</html>

下記デメリットがある

  • UIとロジックの混在: JavaScriptで直接DOM要素を参照し、操作しているため、UI(HTML)とロジック(JavaScript)が混在。
  • 状態の二重管理: カウンターの状態がJavaScriptの変数(count)とDOM(<p id="counter">0</p>内のテキスト)のの中で二重に管理されている。カウントが増加するたびに、この2つの状態を手動で同期する必要がある。
  • イベントリスナーの設定: ボタンに対するイベントリスナーを手動で設定する必要あり。
  • DOM操作のコスト: カウンターを更新するたびにDOMを直接操作する必要がある。特にDOM要素が多い大規模なアプリケーションでは、パフォーマンスに影響を与える可能性がある。

上記をreactで書いた場合

JSX
import React, { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0); // 状態をReactで管理

  const increment = () => {
    setCount(prevCount => prevCount + 1);
  };

	// UIは状態に基づいて自動的に更新される
  return (
    <div className="App">
      <p>{count}</p>
      <button type="button" onClick={increment}>+1</button>    
    </div>
  );
}
  • 状態管理の単純化: カウンターの状態はuseStateフックを使用して管理されていて、UIとロジックが明確に分離されている。countはReactの状態として管理され、UI(<p>{count}</p>)は常にこの状態を反映する。
  • UIの宣言的更新: メッセージの状態が変わると、仮想DOMがこの変更を検出し、必要なUI部分のみが効率的に更新される。
  • イベントハンドラの簡素化: ボタンのonClickプロパティを通じて、イベントハンドラが直接指定されている。これにより、DOM要素を直接参照する必要がなくなり、コードがシンプル。

Reactでは、どの DOM 要素を更新するか ということを開発者が直接指定するのではなく、「状態がこう変わったとき UI はこうあるべき」という「ルール」を定義しており、具体的な DOM 操作は React の内部メカニズムが担当している。

JSX
<p>{count}</p>

従来の JavaScript を使った DOM 操作では、開発者が具体的に「このボタンがクリックされたら、この <p> タグの innerText を更新せよ」といった命令を直接指定する。

JavaScript
document.getElementById('counter').innerText = count;

このコンポーネントに対応する仮想DOMツリーは次のような構造を持っていると考えることができる。

JavaScript
div
 ├── p
 │    └── "countの現在値"
 └── button
      └── "+1"

ここで、"countの現在値"は、countステートに依存するテキストノード。ユーザーがボタンをクリックすると、setCount関数が呼び出され、countステートが更新される。

Reactはこの変更を検知し、新しいcount値を持つ新しい仮想DOMツリーを生成する。この内部的な仮想DOMツリーは直接触れることはできず、Reactの抽象化によって管理される。そして、新旧の仮想DOMツリーを比較し、変更があった部分(この場合は<p>タグ内のテキスト)だけを実DOMに効率的に反映させる。

仮想DOMの課題と発展:Incremental・Fiber・Svelte

仮想DOMも完璧ではなく、下記のような課題がある。

追加のメモリ使用

  • 仮想DOMは実際のDOMのメモリ内表現であり、アプリケーションのUI構造の全ての部分について仮想ノードを保持するため、メモリ使用量が多くなる可能性がある。

初回レンダリングの遅延

  • 仮想DOMは実際のDOMとの比較が必要なため、初回のレンダリングは実際のDOMと同期するまでに時間がかかる場合がある。

パフォーマンスのオーバーヘッド

  • 仮想DOMは、実際のDOMとの比較や差分計算などの追加の処理を必要とする。これにより、一部の場合においてパフォーマンスのオーバーヘッドが発生する可能性がある。

上記課題を解決する、仮想DOMの発展として、いくつかの手法が挙げられている。

Incremental DOM(2016年?)

  • Incremental DOMは変更が必要な部分のみを更新することに焦点を当てている。
  • Googleが開発し、Angularで採用されている。
  • 利点:メモリ使用量減る。DOMの変更が行われるときにその場で変更を適用するため、全体のDOMツリーのコピーを保持する必要がないので。
  • 欠点:実際のDOMに対して変更を直接適用するため、変更が多い場合や大規模なアプリケーションでは、その都度DOMの更新を行う必要があるため、処理時間がかかる可能性がある。

仮想DOMとIncremental DOMの違いの解説こちら

Incremental vs Virtual DOM
Will Incremental DOM Replace Virtual DOM in the Near Future

Fiber(2017年)

  • React 16で導入された。
  • コンポーネントを「fiber」という新しい作業単位に分割する。
  • Fiberはコンポーネントごとに更新作業をより細かく分割し、Reactが必要に応じてこれらの作業を中断し再開することを可能にすることで、アプリケーションの応答性を向上させる。

詳細はこちら

【React】仮想DOM~StackとFiberを理解する~

Svelte(2016年)

  • コンパイル時に変更をトラックし、直接最適なDOM操作を生成する。仮想DOMを介さずに変更を反映するため、実行時のオーバーヘッドが少なくなり高速なアプリケーションが実現できる。
出典:https://qiita.com/asameshiCode/items/0ef348efa4066c68778f

PWA(Progressive Web Apps):2015年

モバイルの普及と、ユーザーのオフライン利用(地下鉄など)や高速なアプリ体験のニーズが高まり、Googleが2015年くらいからPWAという仕組みを提唱し始めた。

PWAは、Webサイトをネイティブアプリのように利用できるようにする仕組みのこと。

  • PWAを実装したWebサイトは、オフラインで動作したり、プッシュ通知を送ったりと、ネイティブアプリと同じような機能を使うことができる。ネイティブアプリと違い、アプリストアを経由する必要がなくインストールも不要。
  • 導入例:Twitter, Youtube, Spotify, Pinterest, Uber, Suumo, 日経など
  • オフライン機能: Service Workerという技術を使用して、オフライン時でもアクセス可能にする。Service Workerはブラウザとネットワーク間のプロキシとして機能し、データをキャッシュできる。ブラウザのキャッシュ機能をさらに発展させたもので、Service Workerを介したキャッシュ管理では、開発者がどのリソースをキャッシュし、それらをいつどのように更新するかを細かく指定できる。なので、アプリはオフライン時でも機能するようになり、ユーザーに対してよりコントロールされた体験を提供できるようになる。

まとめ

  • サーバーサイドレンダリング(SSR): 2000年以前、すべてのコンテンツがサーバーで生成され、クライアントに送信されていた。
  • Ajaxの出現とSPAの普及: 2000年代初頭から2010年頃、Ajaxを用いた非同期通信によってページの一部だけを更新する技術が普及し、SPA(シングルページアプリケーション)が登場。
  • 仮想DOMの導入: 2013年、FacebookがReactを公開し、仮想DOMを用いた効率的なUI更新手法が導入された。
  • PWA:2015年、GoogleがPWAを推進し、Webサイトをネイティブアプリのように利用できるようにする仕組みを提供した。

コメント

タイトルとURLをコピーしました