【SPAとは?】簡単シングルページアプリケーションで本質を学ぼう

what-is-spa

いまWebアプリは、SPA(Single Page Application)全盛の時代。Angular、Vue.js、React あたりがよく使われます。ただこのSPA、いまいちなにやってるか分からない。

分からないなら作ってみよう。VueやReactなどのフレームワークをいっさい使わず、簡単なSPAを作ってみます。

シンプルなSPAを体感すれば、その本質が分かるでしょう。

目次

いったいなんなの?シングルページアプリケーション

シングルページアプリケーション。シングルなページ?いったいなんなのでしょう?

ページってなに?

まず、ページとページ遷移について、確認しましょう。Webアプリでは以下のことを指します。

Webアプリにおけるページ
  • ページ いまブラウザに表示されている画面全体
  • ページ遷移 ある画面から別の画面に切り替わること

例えば、お買い物サイトなら、以下のようなイメージ。

お買い物サイトの例
  • ページ 商品リスト画面や支払い画面の、それぞれの画面のこと
  • ページ遷移 商品リスト画面から支払い画面に切り替わること

シングルなページ、とは?

SPAは、シングルページアプリケーション。なら従来のWebアプリは、マルチページアプリケーションですね。

でも、お買い物サイトなら、「商品リスト画面」「支払い画面」は存在します。マルチなページでしょ?シングルとマルチの違いは?

シングルページアプリケーションは、以下の動きになります。

シングルページアプリケーション
  • ページは切り替わらない
  • ページ遷移時に、必要な部分だけを更新する

具体例で確かめましょう。

時計アプリを作ってみよう(従来型)

簡単Webアプリで、従来型とSPAの違いを、体感してみましょう。題材は、別記事でも紹介した時計アプリ。

別記事:【Webアプリとは?】実は単純、Pythonで簡単Webアプリを作ってみよう

時計アプリ(従来型)コード

まずは、従来Webアプリ型の、時計アプリです。

from http.server import HTTPServer
from http.server import BaseHTTPRequestHandler
import datetime

# リクエストハンドラ
class CustomHTTPRequestHandler(BaseHTTPRequestHandler):

    # Get
    def do_GET(self):
        # 現在時刻文字列
        dt_now = datetime.datetime.now()
        dt_string = dt_now.strftime('%Y/%m/%d %H:%M:%S')

        # レスポンスコード
        self.send_response(200)
        self.send_header('Content-type', 'text/html; charset=utf-8')
        self.end_headers()

        # コンテンツ
        html_context = '<html lang="ja">' \
                       '  <head>' \
                       '    <meta charset="UTF-8">' \
                       '    <title>時計</title>' \
                       '  </head>' \
                       '  <body>' \
                      f'    <p>時刻:<strong>{dt_string}</strong></p>' \
                       '    <form id="myForm" name="myForm" method="get">' \
                       '      <input id="submitButton" type="submit" value="時刻表示">' \
                       '    </form>' \
                       '    <script>' \
                       '      function submitForm() {' \
                       '        document.myForm.submit();' \
                       '      }' \
                       '      setInterval(submitForm, 1000); ' \
                       '    </script>' \
                       '  </body>' \
                       '</html>'

        self.wfile.write(html_context.encode())

# アドレス
server_address = ('localhost', 8080)

# Webサーバー起動
with HTTPServer(server_address, CustomHTTPRequestHandler) as server:
    server.serve_forever()

時刻を表示するかんたんなWebアプリです。1秒ごとに、以下のようなHTMLをブラウザに返却して描画します。

<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>時計</title>
  </head>
  <body>
    <p>時刻:<strong>2023/6/1 12:34:56</strong></p>
    <form id="myForm" name="myForm" method="get">
      <input id="submitButton" type="submit" value="時刻表示">
    </form>
    <script>
      function submitForm() {
        document.myForm.submit();
      }
      setInterval(submitForm, 1000);
    </script>
  </body>
</html>

従来型は、サーバーサイドレンダリング

このコードでは、ブラウザのJavaScriptでsubmitをすると、サーバにリクエストが送られ、HTMLが返却されます。

submitのタイミングで、サーバ側でページ全体が再作成され、その結果が描画されます。これを、サーバーサイドレンダリング(SSR:Server Side Rendering)といいます。

時計アプリを作ってみよう(SPA)

次に、SPAで時計アプリを作ってみましょう。

従来型の弱点:ページ全体を描画する(してしまう)

前述の従来型時計アプリは、1秒ごとにページ全体を再描画するので、画面がちょっとちらつきます

でもですよ。HTMLをよくみると、毎秒変わるのは、時刻のとこだけ。

    <p>時刻:<strong>2023/6/1 12:34:56</strong></p>

ほかの箇所は、毎回同じ内容です。これ、毎回ぜんぶ送る必要ある?

SPAは、必要なデータのみを取得して、ブラウザ側で描画

SPAでは発想の転換をします。Webアプリは、単純に「テキストを作り出して表示するソフトウェア」なだけです。(別記事でも紹介したとおり)

だったら、サーバから必要なデータのみを、テキストで送ればいいんじゃない?

SPAの考え方

サーバで、毎回ページ全体を作るのではなく

  • ブラウザが、描画に必要なデータのみをサーバに要求する
  • 描画は、もらったデータをもとにブラウザ側で行う

サーバ側から必要なデータのみ取得し、その結果で描画します。これを、クライアントサイドレンダリング(CSR:Client Side Rendering)といいます。

この考えで、時計アプリ(SPA)を作りましょう。

時計アプリ(SPA)コード

SPA版時計アプリは、PythonのWebサーバと、返却するHTMLの2つを作ります。

server.py

from http.server import HTTPServer
from http.server import SimpleHTTPRequestHandler
from urllib.parse import urlparse
import json
import datetime

# リクエストハンドラ
class CustomHTTPRequestHandler(SimpleHTTPRequestHandler):

    # Get
    def do_GET(self):

        parsed_path = urlparse(self.path)
        print(f'URL : {parsed_path.path}')

        # 時刻取得なら、時刻返却
        if parsed_path.path == '/clock':
            self.make_clock()
            return
        
        # index.html返却
        super().do_GET()

    # 時刻返却
    def make_clock(self):

        # 現在時刻文字列
        dt_now = datetime.datetime.now()
        dt_string = dt_now.strftime('%Y/%m/%d %H:%M:%S')

        # レスポンスコード
        self.send_response(200)
        self.send_header('Content-type', 'application/json; charset=utf-8')
        self.end_headers()

        # 時刻
        clock_data = {'clock': dt_string}
        clock_string = json.dumps(clock_data)
        self.wfile.write(clock_string.encode())


# アドレス
server_address = ('localhost', 8080)

# Webサーバー起動
with HTTPServer(server_address, CustomHTTPRequestHandler) as server:
    server.serve_forever()

index.html

<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>時計</title>
  </head>
  <body>
    <p>時刻:<strong><div id="clock"></div></strong></p>
    <script>
      const handleInterval = () => {
        fetch('/clock')
          .then((response) => response.json())
          .then((data) => {
            let element = document.getElementById('clock');
            element.innerHTML = data.clock;
            console.log(data.clock);
          });
      }
      setInterval(handleInterval, 1000);
    </script>
  </body>
</html>

なにをしているのか

ブラウザからリクエストを出します。

http://localhocst:8080/

サーバで、index.html返却のほうに分岐します。

        # index.html返却
        super().do_GET()

ブラウザに、index.htmlの内容が返却されます。

ブラウザにHTMLが返却されると、ブラウザでJavaScriptが動きます。

      const handleInterval = () => {
        fetch('/clock')
          .then((response) => response.json())
          .then((data) => {
            let element = document.getElementById('clock');
            element.innerHTML = data.clock;
            console.log(data.clock);
          });
      }

この、fetch がポイント。ここでは

http://localhost:8080/clock

というリクエストをサーバーに出しています。先ほどのリクエストと異なることに注意(/clockがついている)

サーバーでは、make_clockに分岐し、時刻文字列を返します。

        # 時刻取得なら、時刻返却
        if parsed_path.path == '/clock':
            self.make_clock()
            return
        
        # index.html返却
        super().do_GET()

    # 時刻返却
    def make_clock(self):

        # 現在時刻文字列
        dt_now = datetime.datetime.now()
        dt_string = dt_now.strftime('%Y/%m/%d %H:%M:%S')

        # レスポンスコード
        self.send_response(200)
        self.send_header('Content-type', 'application/json; charset=utf-8')
        self.end_headers()

        # 時刻
        clock_data = {'clock': dt_string}
        clock_string = json.dumps(clock_data)
        self.wfile.write(clock_string.encode())

返ってくる文字列は、以下のような形式

{"clock": "2023/06/01 01:23:45"}

ブラウザでは、返ってきた時刻文字列を表示します。

            let element = document.getElementById('clock');
            element.innerHTML = data.clock;
  • このような、サーバからデータを取ってくる仕様を、Web APIと呼ぶことがあります。
  • {“clock”: “2023/06/01 01:23:45”} のようなフォーマットを、JSON形式といいます。

SPAは、1つのページの中を更新していくアプリケーション

時計アプリ(SPA)の動作をまとめると、以下のようになります。

STEP
ブラウザからサーバに、http://localhost:8080リクエスト

サーバでコンテンツ生成処理が動く

STEP
サーバからブラウザに、HTML返却

ブラウザでHTML表示

STEP
ブラウザでJavaScriptが動作し、サーバにhttp://localhost:8080/clockをリクエスト

サーバで時刻生成処理が動く

STEP
サーバからブラウザに、時刻文字列返却

ブラウザで時刻更新

サーバにリクエストするURLで、返却される内容が変わるのがポイント

  • http://localhost:8080/でリクエスト → HTMLが返却される
  • http://localhost:8080/clockでリクエスト → 時刻文字列が返却される

つまり

時計アプリ(SPA)のページ表示
  • ページが表示されるのは、最初のHTML取得時の1回のみ
  • あとは、同じページ内を、部分的に書き換えていくことで、表示を更新する

最初に表示されたページ1つで、あとはページ内を部分的に書き換えていく。なので「シングルページアプリケーション」と呼ぶわけです。

ページ内の一部分が書き換えられるだけなので、更新のたびに表示がちらつく、ということがなくなるわけです。

シングルページアプリケーション:1つのページの中で、必要な部分のみをサーバから取得し、ページを更新していく

まとめ

ここでは、ごくシンプルなアプリを作って、SPAを体感しました。

ただ実際に開発するときは、SPA用のフレームワークを使って開発します。

主なSPA用フレームワーク
  • Angular
  • Vue.js
  • React
  • Svelte

SPAフレームワークには様々あります。それぞれにメリットデメリットがあるし、実装の仕方もかなり異なります。

ただどのフレームワークも、その根元にあるのは、ここまでに述べたようなシングルページ更新の思想です

基本を押さえておけば、各種フレームワークを理解するときの助けになるはず。SPAアプリケーション開発を楽しみましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次