You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/hk/7-bank-project/1-template-route/README.md

322 lines
13 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!--
CO_OP_TRANSLATOR_METADATA:
{
"original_hash": "8a07db14e75ac62f013b7de5df05981d",
"translation_date": "2025-08-29T14:59:01+00:00",
"source_file": "7-bank-project/1-template-route/README.md",
"language_code": "hk"
}
-->
# 建立銀行應用程式 第1部分網頁應用中的HTML模板與路由
## 課前測驗
[課前測驗](https://ff-quizzes.netlify.app/web/quiz/41)
### 簡介
自從JavaScript在瀏覽器中出現以來網站變得比以往更加互動且複雜。網頁技術現在常被用來創建直接在瀏覽器中運行的完整應用程式我們稱之為[網頁應用程式](https://en.wikipedia.org/wiki/Web_application)。由於網頁應用程式高度互動化使用者不希望每次執行操作時都需要重新載入整個頁面。因此JavaScript被用來直接使用DOM更新HTML以提供更流暢的使用者體驗。
在這節課中我們將奠定基礎使用HTML模板來創建銀行網頁應用實現多個畫面顯示與更新而無需重新載入整個HTML頁面。
### 先決條件
你需要一個本地網頁伺服器來測試我們在這節課中構建的網頁應用。如果你還沒有,可以安裝[Node.js](https://nodejs.org),並在你的專案資料夾中使用指令 `npx lite-server`。這將創建一個本地網頁伺服器並在瀏覽器中打開你的應用。
### 準備工作
在你的電腦上,建立一個名為`bank`的資料夾,並在其中建立一個名為`index.html`的檔案。我們將從這個HTML[樣板代碼](https://en.wikipedia.org/wiki/Boilerplate_code)開始:
```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>
```
---
## HTML模板
如果你想為一個網頁創建多個畫面一種解決方案是為每個要顯示的畫面創建一個HTML檔案。然而這種解決方案有一些不便之處
- 切換畫面時需要重新載入整個HTML這可能會很慢。
- 在不同畫面之間共享數據會變得困難。
另一種方法是只使用一個HTML檔案並使用`<template>`元素定義多個[HTML模板](https://developer.mozilla.org/docs/Web/HTML/Element/template)。模板是一個可重複使用的HTML區塊瀏覽器不會直接顯示它需要在運行時使用JavaScript實例化。
### 任務
我們將創建一個具有兩個畫面的銀行應用登入頁面和儀表板。首先在HTML的`body`中添加一個佔位元素,我們將用它來實例化應用的不同畫面:
```html
<div id="app">Loading...</div>
```
我們給它一個`id`以便稍後用JavaScript更容易找到它。
> 提示:由於這個元素的內容會被替換,我們可以在其中放置一個加載消息或指示器,當應用加載時會顯示它。
接下來在下面添加登入頁面的HTML模板。目前我們只在其中放置一個標題和一個包含導航連結的區塊。
```html
<template id="login">
<h1>Bank App</h1>
<section>
<a href="/dashboard">Login</a>
</section>
</template>
```
然後我們再添加一個儀表板頁面的HTML模板。這個頁面將包含不同的區塊
- 一個包含標題和登出連結的標頭
- 銀行帳戶的當前餘額
- 一個以表格形式顯示的交易記錄
```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>
```
> 提示在創建HTML模板時如果你想查看它的外觀可以將`<template>`和`</template>`行用`<!-- -->`註解掉。
✅ 你認為我們為什麼要在模板上使用`id`屬性?我們是否可以使用其他東西,比如類別?
## 使用JavaScript顯示模板
如果你在瀏覽器中嘗試當前的HTML檔案你會看到它卡在顯示`Loading...`。這是因為我們需要添加一些JavaScript代碼來實例化並顯示HTML模板。
實例化模板通常分為三個步驟:
1. 在DOM中檢索模板元素例如使用[`document.getElementById`](https://developer.mozilla.org/docs/Web/API/Document/getElementById)。
2. 使用[`cloneNode`](https://developer.mozilla.org/docs/Web/API/Node/cloneNode)克隆模板元素。
3. 將其附加到可見元素下的DOM中例如使用[`appendChild`](https://developer.mozilla.org/docs/Web/API/Node/appendChild)。
✅ 為什麼我們需要在附加到DOM之前克隆模板如果跳過這一步你認為會發生什麼
### 任務
在你的專案資料夾中創建一個名為`app.js`的新檔案並在HTML的`<head>`部分中導入該檔案:
```html
<script src="app.js" defer></script>
```
現在在`app.js`中,我們將創建一個新函數`updateRoute`
```js
function updateRoute(templateId) {
const template = document.getElementById(templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
}
```
這裡我們執行的正是上述的三個步驟。我們實例化具有`templateId`的模板,並將其克隆的內容放入應用的佔位元素中。注意,我們需要使用`cloneNode(true)`來複製模板的整個子樹。
現在用其中一個模板調用這個函數,並查看結果。
```js
updateRoute('login');
```
✅ 這段代碼`app.innerHTML = '';`的目的是什麼?如果沒有它會發生什麼?
## 創建路由
在談到網頁應用時,我們稱*路由*為將**URL**映射到應顯示的特定畫面的意圖。在具有多個HTML檔案的網站中這是自動完成的因為檔案路徑會反映在URL中。例如在你的專案資料夾中有以下檔案
```
mywebsite/index.html
mywebsite/login.html
mywebsite/admin/index.html
```
如果你以`mywebsite`作為根目錄創建一個網頁伺服器URL映射將是
```
https://site.com --> mywebsite/index.html
https://site.com/login.html --> mywebsite/login.html
https://site.com/admin/ --> mywebsite/admin/index.html
```
然而對於我們的網頁應用我們使用的是包含所有畫面的單一HTML檔案因此這種默認行為對我們沒有幫助。我們必須手動創建這個映射並使用JavaScript更新顯示的模板。
### 任務
我們將使用一個簡單的物件來實現一個[映射](https://en.wikipedia.org/wiki/Associative_array)將URL路徑與我們的模板對應起來。在`app.js`檔案的頂部添加這個物件。
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard' },
};
```
現在讓我們稍微修改一下`updateRoute`函數。我們不再直接將`templateId`作為參數傳遞而是希望通過首先查看當前URL然後使用我們的映射來獲取對應的模板ID值。我們可以使用[`window.location.pathname`](https://developer.mozilla.org/docs/Web/API/Location/pathname)來僅獲取URL中的路徑部分。
```js
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。這涉及兩件事
1. 更新當前URL
2. 根據新URL更新顯示的模板
第二部分我們已經通過`updateRoute`函數處理了所以我們需要弄清楚如何更新當前URL。
我們需要使用JavaScript具體來說是[`history.pushState`](https://developer.mozilla.org/docs/Web/API/History/pushState)它允許在不重新載入HTML的情況下更新URL並在瀏覽歷史中創建新條目。
> 注意雖然HTML的錨點元素[`<a href>`](https://developer.mozilla.org/docs/Web/HTML/Element/a)本身可以用來創建指向不同URL的超連結但它默認會使瀏覽器重新載入HTML。在處理自定義JavaScript路由時需要使用`preventDefault()`函數來防止這種行為。
### 任務
讓我們創建一個新函數,用於在應用中導航:
```js
function navigate(path) {
window.history.pushState({}, path, path);
updateRoute();
}
```
這個方法首先根據給定的路徑更新當前URL然後更新模板。屬性`window.location.origin`返回URL的根目錄允許我們從給定路徑重建完整URL。
現在我們有了這個函數,我們可以解決當路徑與任何定義的路由不匹配時的問題。我們將通過在`updateRoute`函數中添加一個回退到現有路由的功能來解決這個問題。
```js
function updateRoute() {
const path = window.location.pathname;
const route = routes[path];
if (!route) {
return navigate('/login');
}
...
```
如果找不到路由,我們現在會重定向到`login`頁面。
接下來我們創建一個函數來獲取點擊連結時的URL並防止瀏覽器的默認連結行為
```js
function onLinkClick(event) {
event.preventDefault();
navigate(event.target.href);
}
```
最後通過在HTML中的*登入*和*登出*連結上添加綁定來完成導航系統。
```html
<a href="/dashboard" onclick="onLinkClick(event)">Login</a>
...
<a href="/login" onclick="onLinkClick(event)">Logout</a>
```
上述代碼中的`event`物件捕獲`click`事件並將其傳遞給我們的`onLinkClick`函數。
使用[`onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick)屬性將`click`事件綁定到JavaScript代碼這裡是對`navigate()`函數的調用。
嘗試點擊這些連結,你現在應該能夠在應用的不同畫面之間導航。
`history.pushState`方法是HTML5標準的一部分並在[所有現代瀏覽器](https://caniuse.com/?search=pushState)中實現。如果你正在為舊版瀏覽器構建網頁應用有一個替代這個API的技巧使用路徑前的[哈希符號(`#`](https://en.wikipedia.org/wiki/URI_fragment),你可以實現基於常規錨點導航的路由,且不會重新載入頁面,因為它的目的是在頁面內創建內部連結。
## 處理瀏覽器的返回與前進按鈕
使用`history.pushState`會在瀏覽器的導航歷史中創建新條目。你可以通過按住瀏覽器的*返回按鈕*來檢查,它應該顯示如下內容:
![導航歷史截圖](../../../../translated_images/history.7fdabbafa521e06455b738d3dafa3ff41d3071deae60ead8c7e0844b9ed987d8.hk.png)
如果你嘗試多次點擊返回按鈕你會看到當前URL發生了變化歷史也被更新但顯示的模板保持不變。
這是因為應用不知道每次歷史變化時需要調用`updateRoute()`。如果你查看[`history.pushState`文檔](https://developer.mozilla.org/docs/Web/API/History/pushState)你會看到當狀態改變時即我們移動到不同的URL會觸發[`popstate`](https://developer.mozilla.org/docs/Web/API/Window/popstate_event)事件。我們將利用這一點來解決這個問題。
### 任務
為了確保當瀏覽器歷史變化時顯示的模板被更新,我們將附加一個新函數來調用`updateRoute()`。我們會在`app.js`檔案的底部完成這件事:
```js
window.onpopstate = () => updateRoute();
updateRoute();
```
> 注意:我們在這裡使用了一個[箭頭函數](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions)來聲明`popstate`事件處理器以簡潔,但普通函數也可以正常工作。
這裡有一個關於箭頭函數的回顧影片:
[![箭頭函數](https://img.youtube.com/vi/OP6eEbOj2sc/0.jpg)](https://youtube.com/watch?v=OP6eEbOj2sc "箭頭函數")
> 🎥 點擊上方圖片觀看關於箭頭函數的影片。
現在嘗試使用瀏覽器的返回與前進按鈕,檢查這次顯示的路由是否正確更新。
---
## 🚀 挑戰
為這個應用新增一個模板和路由,顯示應用的製作團隊名單。
## 課後測驗
[課後測驗](https://ff-quizzes.netlify.app/web/quiz/42)
## 回顧與自學
路由是網頁開發中出乎意料的棘手部分之一,特別是隨著網頁從頁面刷新行為轉向單頁應用的頁面刷新。閱讀一些關於[Azure靜態網頁應用服務如何處理路由](https://docs.microsoft.com/azure/static-web-apps/routes/?WT.mc_id=academic-77807-sagibbon)的內容。你能解釋為什麼文檔中描述的一些決策是必要的嗎?
## 作業
[改進路由](assignment.md)
---
**免責聲明**
此文件已使用 AI 翻譯服務 [Co-op Translator](https://github.com/Azure/co-op-translator) 翻譯。我們致力於提供準確的翻譯,但請注意,自動翻譯可能包含錯誤或不準確之處。應以原文文件作為權威來源。對於關鍵資訊,建議使用專業人工翻譯。我們對因使用此翻譯而引起的任何誤解或誤釋不承擔責任。