いまWebアプリは、SPA(Single Page Application)全盛の時代。Angular、Vue.js、React あたりがよく使われます。ただこのSPA、いまいちなにやってるか分からない。
分からないなら作ってみよう。VueやReactなどのフレームワークをいっさい使わず、簡単なSPAを作ってみます。
シンプルなSPAを体感すれば、その本質が分かるでしょう。
いったいなんなの?シングルページアプリケーション
シングルページアプリケーション。シングルなページ?いったいなんなのでしょう?
ページってなに?
まず、ページとページ遷移について、確認しましょう。Webアプリでは以下のことを指します。
- ページ いまブラウザに表示されている画面全体
- ページ遷移 ある画面から別の画面に切り替わること
例えば、お買い物サイトなら、以下のようなイメージ。
- ページ 商品リスト画面や支払い画面の、それぞれの画面のこと
- ページ遷移 商品リスト画面から支払い画面に切り替わること
シングルなページ、とは?
SPAは、シングルページアプリケーション。なら従来のWebアプリは、マルチページアプリケーションですね。
でも、お買い物サイトなら、「商品リスト画面」「支払い画面」は存在します。マルチなページでしょ?シングルとマルチの違いは?
シングルページアプリケーションは、以下の動きになります。
- ページは切り替わらない
- ページ遷移時に、必要な部分だけを更新する
具体例で確かめましょう。
時計アプリを作ってみよう(従来型)
簡単Webアプリで、従来型とSPAの違いを、体感してみましょう。題材は、別記事でも紹介した時計アプリ。
時計アプリ(従来型)コード
まずは、従来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アプリは、単純に「テキストを作り出して表示するソフトウェア」なだけです。(別記事でも紹介したとおり)
だったら、サーバから必要なデータのみを、テキストで送ればいいんじゃない?
サーバで、毎回ページ全体を作るのではなく
- ブラウザが、描画に必要なデータのみをサーバに要求する
- 描画は、もらったデータをもとにブラウザ側で行う
サーバ側から必要なデータのみ取得し、その結果で描画します。これを、クライアントサイドレンダリング(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)の動作をまとめると、以下のようになります。
サーバでコンテンツ生成処理が動く
ブラウザでHTML表示
サーバで時刻生成処理が動く
ブラウザで時刻更新
サーバにリクエストするURLで、返却される内容が変わるのがポイント
- http://localhost:8080/でリクエスト → HTMLが返却される
- http://localhost:8080/clockでリクエスト → 時刻文字列が返却される
つまり
- ページが表示されるのは、最初のHTML取得時の1回のみ
- あとは、同じページ内を、部分的に書き換えていくことで、表示を更新する
最初に表示されたページ1つで、あとはページ内を部分的に書き換えていく。なので「シングルページアプリケーション」と呼ぶわけです。
ページ内の一部分が書き換えられるだけなので、更新のたびに表示がちらつく、ということがなくなるわけです。
シングルページアプリケーション:1つのページの中で、必要な部分のみをサーバから取得し、ページを更新していく
まとめ
ここでは、ごくシンプルなアプリを作って、SPAを体感しました。
ただ実際に開発するときは、SPA用のフレームワークを使って開発します。
- Angular
- Vue.js
- React
- Svelte
SPAフレームワークには様々あります。それぞれにメリットデメリットがあるし、実装の仕方もかなり異なります。
ただどのフレームワークも、その根元にあるのは、ここまでに述べたようなシングルページ更新の思想です。
基本を押さえておけば、各種フレームワークを理解するときの助けになるはず。SPAアプリケーション開発を楽しみましょう。
コメント