34 KiB
銀行アプリを作ろう パート1: WebアプリでのHTMLテンプレートとルート設定
1969年、アポロ11号の誘導コンピュータが月への航行を行った際、システム全体を再起動することなく異なるプログラム間を切り替える必要がありました。現代のWebアプリケーションも同様に動作します。すべてを最初からリロードすることなく、表示内容を変更することで、ユーザーが期待するスムーズで応答性の高い体験を提供します。
従来のウェブサイトが各操作ごとにページ全体をリロードするのに対し、現代のWebアプリは必要な部分だけを更新します。このアプローチは、ミッションコントロールが異なるディスプレイを切り替えながら常に通信を維持する方法に似ており、私たちが期待する流れるような体験を生み出します。
以下がその劇的な違いを示しています:
| 従来のマルチページアプリ | 現代のシングルページアプリ |
|---|---|
| ナビゲーション | 各画面でページ全体をリロード |
| パフォーマンス | HTML全体のダウンロードで遅い |
| ユーザー体験 | ページのちらつきが目立つ |
| データ共有 | ページ間での共有が困難 |
| 開発 | 複数のHTMLファイルを管理 |
進化の理解:
- 従来のアプリはナビゲーションごとにサーバーリクエストが必要
- 現代のSPAは一度ロードし、JavaScriptを使って動的にコンテンツを更新
- ユーザーの期待は瞬時でシームレスな操作を好む
- パフォーマンスの利点は帯域幅の削減と応答速度の向上
このレッスンでは、複数の画面がシームレスに流れる銀行アプリを構築します。科学者が異なる実験のために再構成可能なモジュール式の機器を使用するように、必要に応じて表示できる再利用可能なコンポーネントとしてHTMLテンプレートを使用します。
HTMLテンプレート(異なる画面の再利用可能な設計図)、JavaScriptルーティング(画面を切り替えるシステム)、ブラウザの履歴API(戻るボタンが期待通りに動作するようにする)を使用します。これらはReact、Vue、Angularなどのフレームワークで使用される基本的な技術と同じです。
最終的には、プロフェッショナルなシングルページアプリケーションの原則を示す銀行アプリが完成します。
レクチャー前のクイズ
必要なもの
銀行アプリをテストするためにローカルWebサーバーが必要です。心配しないでください、簡単です!まだセットアップしていない場合は、Node.jsをインストールし、プロジェクトフォルダでnpx lite-serverを実行してください。この便利なコマンドでローカルサーバーが起動し、ブラウザでアプリが自動的に開きます。
準備
コンピュータ上でbankという名前のフォルダを作成し、その中にindex.htmlという名前のファイルを作成してください。以下のHTML ボイラープレートから始めます:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bank App</title>
</head>
<body>
<!-- This is where you'll work -->
</body>
</html>
このボイラープレートが提供するもの:
- HTML5ドキュメント構造を適切なDOCTYPE宣言で確立
- 文字エンコーディングをUTF-8に設定し、国際的なテキストをサポート
- レスポンシブデザインをモバイル対応のためのviewportメタタグで有効化
- ブラウザタブに表示される説明的なタイトルを設定
- アプリケーションを構築するためのクリーンなボディセクションを作成
📁 プロジェクト構造のプレビュー
このレッスンの終わりまでに、プロジェクトには以下が含まれます:
bank/ ├── index.html <!-- Main HTML with templates --> ├── app.js <!-- Routing and navigation logic --> └── style.css <!-- (Optional for future lessons) -->ファイルの役割:
- index.html: すべてのテンプレートを含み、アプリの構造を提供
- app.js: ルーティング、ナビゲーション、テンプレート管理を担当
- テンプレート: ログイン、ダッシュボード、その他の画面のUIを定義
HTMLテンプレート
テンプレートはWeb開発における基本的な問題を解決します。1440年代にグーテンベルクが活版印刷を発明した際、ページ全体を彫るのではなく、再利用可能な文字ブロックを作成し、それを必要に応じて配置することに気づきました。HTMLテンプレートも同じ原理で動作します。各画面のために個別のHTMLファイルを作成する代わりに、再利用可能な構造を定義し、必要に応じて表示します。
テンプレートはアプリの異なる部分の設計図のようなものです。建築家が1つの設計図を作成し、同じ部屋を何度も描き直すのではなくそれを何度も使用するように、テンプレートを一度作成し、必要に応じてインスタンス化します。ブラウザはこれらのテンプレートをJavaScriptがアクティブ化するまで非表示にします。
Webページに複数の画面を作成したい場合、1つの解決策は表示したい画面ごとに1つのHTMLファイルを作成することです。しかし、この方法にはいくつかの不便があります:
- 画面を切り替える際にHTML全体をリロードする必要があり、これが遅くなる原因となります。
- 異なる画面間でデータを共有するのが難しいです。
別のアプローチとして、1つのHTMLファイルだけを使用し、複数のHTMLテンプレートを<template>要素を使って定義する方法があります。テンプレートは再利用可能なHTMLブロックで、ブラウザによって表示されず、JavaScriptを使用して実行時にインスタンス化する必要があります。
作ってみよう
ログインページとダッシュボードの2つの主要な画面を持つ銀行アプリを作成します。まず、HTMLボディにプレースホルダー要素を追加します。ここに異なる画面が表示されます:
<div id="app">Loading...</div>
このプレースホルダーの理解:
- **ID "app"**を持つコンテナを作成し、すべての画面を表示
- JavaScriptが最初の画面を初期化するまでローディングメッセージを表示
- 動的コンテンツの単一のマウントポイントを提供
document.getElementById()を使用してJavaScriptから簡単にターゲット可能
💡 プロのヒント: この要素の内容は置き換えられるため、アプリがロード中に表示されるローディングメッセージやインジケーターを入れることができます。
次に、ログインページのHTMLテンプレートを追加します。ここではタイトルとナビゲーション用のリンクを含むセクションを追加します。
<template id="login">
<h1>Bank App</h1>
<section>
<a href="/dashboard">Login</a>
</section>
</template>
このログインテンプレートの分解:
- JavaScriptでターゲットにするためのユニークな識別子"login"を持つテンプレートを定義
- アプリのブランドを確立するメインヘッディングを含む
- 関連するコンテンツをグループ化するセマンティックな
<section>要素を含む - ユーザーをダッシュボードにルートするナビゲーションリンクを提供
次に、ダッシュボードページのHTMLテンプレートを追加します。このページには以下のセクションが含まれます:
- タイトルとログアウトリンクを含むヘッダー
- 銀行口座の現在の残高
- 取引履歴を表示するテーブル
<template id="dashboard">
<header>
<h1>Bank App</h1>
<a href="/login">Logout</a>
</header>
<section>
Balance: 100$
</section>
<section>
<h2>Transactions</h2>
<table>
<thead>
<tr>
<th>Date</th>
<th>Object</th>
<th>Amount</th>
</tr>
</thead>
<tbody></tbody>
</table>
</section>
</template>
ダッシュボードの各部分を理解する:
- セマンティックな
<header>要素でページを構造化し、ナビゲーションを含む - 画面間で一貫してアプリタイトルを表示し、ブランドを確立
- ログアウトリンクを提供し、ログイン画面に戻るルートを設定
- 専用セクションで現在の口座残高を表示
- 適切に構造化されたHTMLテーブルを使用して取引データを整理
- 日付、項目、金額の列のテーブルヘッダーを定義
- テーブルボディを空にして動的コンテンツの挿入を後で可能に
💡 プロのヒント: HTMLテンプレートを作成する際、見た目を確認したい場合は、
<template>と</template>行を<!-- -->で囲んでコメントアウトすることができます。
✅ なぜテンプレートにid属性を使用するのでしょうか?クラスなど他のものを使うことはできるでしょうか?
JavaScriptでテンプレートを動かす
テンプレートを機能させる必要があります。3Dプリンターがデジタル設計図を物理的なオブジェクトに変えるように、JavaScriptは隠されたテンプレートを可視化し、ユーザーが見て操作できるインタラクティブな要素を作成します。
このプロセスは、現代のWeb開発の基礎を形成する3つの一貫したステップに従います。このパターンを理解すれば、多くのフレームワークやライブラリでそれを認識できるようになります。
現在のHTMLファイルをブラウザで試してみると、Loading...の表示で止まってしまいます。これは、HTMLテンプレートをインスタンス化して表示するためのJavaScriptコードを追加する必要があるためです。
テンプレートのインスタンス化は通常以下の3ステップで行われます:
- DOM内のテンプレート要素を取得します。例えば
document.getElementByIdを使用します。 - テンプレート要素を
cloneNodeを使って複製します。 - 可視要素の下にDOMにアタッチします。例えば
appendChildを使用します。
flowchart TD
A[🔍 Step 1: Find Template] --> B[📋 Step 2: Clone Template]
B --> C[🔗 Step 3: Attach to DOM]
A1["document.getElementById('login')"] --> A
B1["template.content.cloneNode(true)"] --> B
C1["app.appendChild(view)"] --> C
C --> D[👁️ Template Visible to User]
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#e8f5e8
style D fill:#fff3e0
プロセスの視覚的な分解:
- ステップ1: DOM構造内の隠されたテンプレートを特定
- ステップ2: 安全に変更可能な作業コピーを作成
- ステップ3: コピーを可視ページエリアに挿入
- 結果: ユーザーが操作可能な機能的な画面が完成
✅ なぜテンプレートをDOMにアタッチする前に複製する必要があるのでしょうか?このステップを省略した場合、何が起こると思いますか?
タスク
プロジェクトフォルダにapp.jsという名前の新しいファイルを作成し、そのファイルをHTMLの<head>セクションにインポートします:
<script src="app.js" defer></script>
このスクリプトインポートの理解:
- JavaScriptファイルをHTMLドキュメントにリンク
defer属性を使用してスクリプトがHTML解析後に実行されるようにする- スクリプト実行前にDOM要素が完全にロードされることを保証
- スクリプトのロードとパフォーマンスに関する最新のベストプラクティスに従う
次にapp.jsで新しい関数updateRouteを作成します:
function updateRoute(templateId) {
const template = document.getElementById(templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
}
ステップごとの説明:
- ユニークなIDを使用してテンプレート要素を特定
cloneNode(true)を使用してテンプレートの内容を深くコピー- コンテンツが表示されるアプリコンテナを特定
- アプリコンテナから既存のコンテンツをクリア
- 複製したテンプレートコンテンツを可視DOMに挿入
次に、この関数をテンプレートの1つで呼び出し、結果を確認してください。
updateRoute('login');
この関数呼び出しが達成すること:
- ログインテンプレートをアクティブ化し、そのIDをパラメータとして渡す
- 異なるアプリ画面間をプログラムで切り替える方法を示す
- "Loading..."メッセージの代わりにログイン画面を表示
✅ このコードapp.innerHTML = '';の目的は何でしょうか?これがないとどうなるでしょうか?
ルートの作成
ルーティングはURLを適切なコンテンツに接続することです。初期の電話交換手がスイッチボードを使って通話を接続していたように、WebルーティングはURLリクエストを受け取り、どのコンテンツを表示するかを決定します。
従来は、Webサーバーが異なるURLに対して異なるHTMLファイルを提供することでこれを処理していました。私たちはシングルページアプリを構築しているため、JavaScriptを使ってこのルーティングを自分で処理する必要があります。このアプローチにより、ユーザー体験とパフォーマンスをより細かく制御できます。
flowchart LR
A["🌐 URL Path<br/>/dashboard"] --> B["🗺️ Routes Object<br/>Lookup"]
B --> C["🎯 Template ID<br/>'dashboard'"]
C --> D["📄 Find Template<br/>getElementById"]
D --> E["👁️ Display Screen<br/>Clone & Append"]
F["📍 /login"] --> G["🎯 'login'"]
H["📍 /unknown"] --> I["❌ Not Found"]
I --> J["🔄 Redirect to /login"]
style B fill:#e3f2fd
style E fill:#e8f5e8
style I fill:#ffebee
style J fill:#fff3e0
ルーティングフローの理解:
- URLの変更がルート設定の検索をトリガー
- 有効なルートが特定のテンプレートIDにマッピングされる
- 無効なルートがフォールバック動作をトリガーし、壊れた状態を防止
- テンプレートのレンダリングは前述の3ステッププロセスに従う
Webアプリについて話す際、ルーティングとはURLを表示すべき特定の画面にマッピングする意図を指します。複数のHTMLファイルを持つウェブサイトでは、ファイルパスがURLに反映されるため、これが自動的に行われます。例えば、プロジェクトフォルダに以下のファイルがある場合:
mywebsite/index.html
mywebsite/login.html
mywebsite/admin/index.html
mywebsiteをルートとしてWebサーバーを作成すると、URLマッピングは以下のようになります:
https://site.com --> mywebsite/index.html
https://site.com/login.html --> mywebsite/login.html
https://site.com/admin/ --> mywebsite/admin/index.html
しかし、私たちのWebアプリではすべての画面を含む単一のHTMLファイルを使用しているため、このデフォルトの動作は役に立ちません。このマッピングを手動で作成し、JavaScriptを使用して表示されるテンプレートを更新する必要があります。
タスク
URLパスとテンプレートをマップするためにシンプルなオブジェクトを使用します。このオブジェクトをapp.jsファイルの冒頭に追加してください。
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard' },
};
このルート設定の理解:
- URLパスとテンプレート識別子の間のマッピングを定義
- キーがURLパスで値がテンプレート情報を含むオブジェクト構文を使用
- 任意のURLに対して表示すべきテンプレートを簡単に検索可能
- 将来のルート追加に対応可能なスケーラブルな構造を提供
次に、updateRoute関数を少し修正します。templateIdを直接引数として渡す代わりに、まず現在のURLを確認し、次にマップを使用して対応するテンプレートID値を取得します。ブラウザのURLからパスセクションだけを取得するにはwindow.location.pathnameを使用できます。
function updateRoute() {
const path = window.location.pathname;
const route = routes[path];
const template = document.getElementById(route.templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
}
**ここで何が ✅ URLに未知のパスを入力するとどうなるでしょうか?どうすれば解決できるでしょうか?
ナビゲーションの追加
ルーティングが設定されたら、ユーザーがアプリ内を移動する方法を提供する必要があります。従来のウェブサイトではリンクをクリックするとページ全体がリロードされますが、私たちはURLとコンテンツをページをリロードせずに更新したいと考えています。これにより、デスクトップアプリケーションが異なるビュー間を切り替えるようなスムーズな体験が得られます。
これを実現するには、ブラウザのURLを更新してユーザーがページをブックマークしたりリンクを共有できるようにすることと、適切なコンテンツを表示することを調整する必要があります。正しく実装すれば、現代のアプリケーションに期待されるシームレスなナビゲーションが実現します。
🏗️ アーキテクチャの洞察: ナビゲーションシステムの構成要素
構築するもの:
- 🔄 URL管理: ページをリロードせずにブラウザのアドレスバーを更新
- 📋 テンプレートシステム: 現在のルートに基づいてコンテンツを動的に切り替え
- 📚 履歴統合: ブラウザの戻る/進むボタンの機能を維持
- 🛡️ エラーハンドリング: 無効または欠落したルートに対する優雅なフォールバック
コンポーネントの連携方法:
- ナビゲーションイベント(クリックや履歴変更)を監視
- History APIを使用してURLを更新
- 新しいルートに適したテンプレートをレンダリング
- シームレスなユーザー体験を維持
次のステップは、URLを手動で変更することなくページ間を移動できるようにすることです。これには以下の2つが含まれます:
- 現在のURLを更新すること
- 新しいURLに基づいて表示されるテンプレートを更新すること
2番目の部分はすでにupdateRoute関数で対応済みなので、現在のURLを更新する方法を考える必要があります。
JavaScript、特にhistory.pushStateを使用する必要があります。このメソッドはHTMLをリロードせずにURLを更新し、ブラウザ履歴に新しいエントリを作成することができます。
⚠️ 重要な注意点: HTMLアンカー要素
<a href>は単独で異なるURLへのハイパーリンクを作成できますが、デフォルトではブラウザがHTMLをリロードしてしまいます。カスタムJavaScriptでルーティングを処理する際には、クリックイベントでpreventDefault()関数を使用してこの動作を防ぐ必要があります。
タスク
アプリ内でナビゲートするための新しい関数を作成しましょう:
function navigate(path) {
window.history.pushState({}, path, path);
updateRoute();
}
このナビゲーション関数の理解:
history.pushStateを使用してブラウザのURLを新しいパスに更新- 戻る/進むボタンの適切なサポートのためにブラウザの履歴スタックに新しいエントリを追加
- 対応するテンプレートを表示するために
updateRoute()関数をトリガー - ページリロードなしでシングルページアプリケーションの体験を維持
このメソッドはまず与えられたパスに基づいて現在のURLを更新し、その後テンプレートを更新します。window.location.originプロパティはURLのルートを返し、与えられたパスから完全なURLを再構築することができます。
この関数ができたので、定義されたルートに一致しないパスがある場合の問題に対処できます。updateRoute関数を修正し、一致するルートが見つからない場合に既存のルートの1つにフォールバックするようにします。
function updateRoute() {
const path = window.location.pathname;
const route = routes[path];
if (!route) {
return navigate('/login');
}
const template = document.getElementById(route.templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
}
覚えておくべき重要なポイント:
- 現在のパスに対してルートが存在するかどうかを確認
- 無効なルートにアクセスした場合にログインページにリダイレクト
- 壊れたナビゲーションを防ぐフォールバックメカニズムを提供
- 不正なURLでもユーザーが常に有効な画面を見ることを保証
ルートが見つからない場合、loginページにリダイレクトするようになります。
次に、リンクがクリックされたときにURLを取得し、ブラウザのデフォルトのリンク動作を防ぐ関数を作成しましょう:
function onLinkClick(event) {
event.preventDefault();
navigate(event.target.href);
}
このクリックハンドラーの分解:
preventDefault()を使用してブラウザのデフォルトのリンク動作を防止- クリックされたリンク要素から目的のURLを抽出
- ページをリロードする代わりにカスタムナビゲート関数を呼び出し
- スムーズなシングルページアプリケーション体験を維持
<a href="/dashboard" onclick="onLinkClick(event)">Login</a>
...
<a href="/login" onclick="onLinkClick(event)">Logout</a>
このonclickバインディングが達成すること:
- 各リンクをカスタムナビゲーションシステムに接続
- クリックイベントを処理するために
onLinkClick関数に渡す - ページリロードなしでスムーズなナビゲーションを実現
- ユーザーがブックマークや共有できる適切なURL構造を維持
onclick属性はclickイベントをJavaScriptコードにバインドします。ここではnavigate()関数の呼び出しに使用されています。
これらのリンクをクリックしてみてください。アプリの異なる画面間をナビゲートできるはずです。
✅ history.pushStateメソッドはHTML5標準の一部であり、すべての最新ブラウザで実装されています。古いブラウザ向けにウェブアプリを構築する場合、このAPIの代わりに使えるトリックがあります。パスの前にハッシュ(#)を使用することで、通常のアンカーナビゲーションで動作し、ページをリロードしないルーティングを実装できます。これは元々ページ内リンクを作成する目的で使用されていました。
戻るボタンと進むボタンを機能させる
戻るボタンと進むボタンはウェブブラウジングにおいて基本的な機能であり、NASAのミッションコントローラーが以前のシステム状態をレビューするようなものです。ユーザーはこれらのボタンが機能することを期待しており、機能しない場合は期待されるブラウジング体験が壊れてしまいます。
私たちのシングルページアプリはこれをサポートするために追加の設定が必要です。ブラウザは履歴スタックを維持しています(history.pushStateで追加してきました)が、ユーザーがこの履歴をナビゲートするとき、アプリはそれに応じて表示されるコンテンツを更新する必要があります。
sequenceDiagram
participant User
participant Browser
participant App
participant Template
User->>Browser: Clicks "Login" link
Browser->>App: onclick event triggered
App->>App: preventDefault() & navigate('/dashboard')
App->>Browser: history.pushState('/dashboard')
Browser->>Browser: URL updates to /dashboard
App->>App: updateRoute() called
App->>Template: Find & clone dashboard template
Template->>App: Return cloned content
App->>Browser: Replace app content with template
Browser->>User: Display dashboard screen
Note over User,Template: User clicks browser back button
User->>Browser: Clicks back button
Browser->>Browser: History moves back to /login
Browser->>App: popstate event fired
App->>App: updateRoute() called automatically
App->>Template: Find & clone login template
Template->>App: Return cloned content
App->>Browser: Replace app content with template
Browser->>User: Display login screen
重要なインタラクションポイント:
- ユーザーの操作がクリックやブラウザボタンを通じてナビゲーションをトリガー
- アプリがリンククリックをインターセプトしてページリロードを防止
- History APIがURL変更とブラウザ履歴スタックを管理
- テンプレートが各画面のコンテンツ構造を提供
- イベントリスナーがすべてのナビゲーションタイプに応答することを保証
history.pushStateを使用するとブラウザのナビゲーション履歴に新しいエントリが作成されます。ブラウザの戻るボタンを押し続けると、次のような履歴が表示されるはずです:
戻るボタンを数回クリックしてみると、現在のURLが変更され履歴が更新されますが、同じテンプレートが表示され続けます。
これは、履歴が変更されるたびにupdateRoute()を呼び出す必要があることをアプリが認識していないためです。history.pushStateのドキュメントを見ると、状態が変更された場合(つまり異なるURLに移動した場合)、popstateイベントがトリガーされることがわかります。このイベントを使用して問題を修正します。
タスク
ブラウザ履歴が変更されたときに表示されるテンプレートが更新されるようにするため、updateRoute()を呼び出す新しい関数をapp.jsファイルの末尾に追加します:
window.onpopstate = () => updateRoute();
updateRoute();
この履歴統合の理解:
- ユーザーがブラウザボタンでナビゲートするときに発生する
popstateイベントを監視 - 簡潔なイベントハンドラー構文のためにアロー関数を使用
- 履歴状態が変更されるたびに自動的に
updateRoute()を呼び出し - ページが最初に読み込まれたときに
updateRoute()を初期化 - ユーザーがどのようにナビゲートしても正しいテンプレートが表示されることを保証
💡 プロのヒント: ここでは簡潔さのためにアロー関数を使用して
popstateイベントハンドラーを宣言しましたが、通常の関数でも同じように動作します。
アロー関数に関するリフレッシュ動画はこちら:
🎥 上の画像をクリックしてアロー関数についての動画をご覧ください。
ブラウザの戻るボタンと進むボタンを使用してみて、表示されるルートが正しく更新されることを確認してください。
GitHub Copilot Agent チャレンジ 🚀
Agentモードを使用して以下のチャレンジを完了してください:
説明: 無効なルートに移動した際のエラーハンドリングと404ページテンプレートを実装し、ユーザー体験を向上させましょう。
プロンプト: スタイリングされたユーザーフレンドリーな404エラーページを表示するHTMLテンプレート(id: "not-found")を作成してください。その後、JavaScriptのルーティングロジックを修正して、ユーザーが無効なURLに移動した際にこのテンプレートを表示し、「ホームに戻る」ボタンを追加してログインページに戻れるようにしてください。
Agentモードについての詳細はこちらをご覧ください。
🚀 チャレンジ
このアプリのクレジットを表示する第3のページ用のテンプレートとルートを追加してください。
チャレンジ目標:
- 適切なコンテンツ構造を持つ新しいHTMLテンプレートを作成
- ルート構成オブジェクトに新しいルートを追加
- クレジットページへのナビゲーションリンクを含める
- ブラウザ履歴を使用してすべてのナビゲーションが正しく動作することをテスト
講義後のクイズ
復習と自己学習
ルーティングはウェブ開発の中で意外と難しい部分の一つであり、ウェブがページリロードの動作からシングルページアプリケーションの動作に移行するにつれてさらに複雑になります。Azure Static Web Appサービスがルーティングをどのように処理しているかについて少し読んでみてください。このドキュメントで説明されているいくつかの決定がなぜ必要なのか説明できますか?
追加の学習リソース:
- React RouterやVue Routerなどの人気フレームワークがクライアントサイドルーティングをどのように実装しているかを探る
- ハッシュベースのルーティングとHistory APIルーティングの違いを調査
- サーバーサイドレンダリング(SSR)がルーティング戦略にどのように影響するかを学ぶ
- プログレッシブウェブアプリ(PWA)がルーティングとナビゲーションをどのように処理しているかを調査
課題
免責事項:
この文書はAI翻訳サービスCo-op Translatorを使用して翻訳されています。正確性を追求しておりますが、自動翻訳には誤りや不正確な部分が含まれる可能性があります。元の言語で記載された文書を正式な情報源としてご参照ください。重要な情報については、専門の人間による翻訳を推奨します。この翻訳の使用に起因する誤解や誤認について、当方は一切の責任を負いません。

