# 建立銀行應用程式第二部分:建立登入和註冊表單 ## 課前測驗 [課前測驗](https://ff-quizzes.netlify.app/web/quiz/43) ### 簡介 在幾乎所有現代的網頁應用程式中,您都可以創建一個帳戶來擁有自己的私人空間。由於多個使用者可以同時訪問網頁應用程式,因此需要一種機制來分別儲存每個使用者的個人資料,並選擇要顯示的資訊。我們不會涵蓋如何[安全地管理使用者身份](https://en.wikipedia.org/wiki/Authentication),因為這是一個非常廣泛的主題,但我們會確保每個使用者能夠在我們的應用程式中創建一個(或多個)銀行帳戶。 在這部分,我們將使用 HTML 表單為我們的網頁應用程式新增登入和註冊功能。我們將學習如何以程式化方式將資料發送到伺服器 API,並最終定義使用者輸入的基本驗證規則。 ### 先決條件 您需要完成本課程的[HTML 模板和路由](../1-template-route/README.md)部分。此外,您還需要安裝 [Node.js](https://nodejs.org) 並在本地運行[伺服器 API](../api/README.md),以便能夠發送資料來創建帳戶。 **請注意** 您需要同時運行以下兩個終端: 1. 用於我們在[HTML 模板和路由](../1-template-route/README.md)課程中建立的主要銀行應用程式。 2. 用於我們剛剛設置的[銀行應用程式伺服器 API](../api/README.md)。 您需要這兩個伺服器都在運行,才能繼續完成本課程的其餘部分。它們分別監聽不同的埠(埠 `3000` 和埠 `5000`),因此應該可以正常運作。 您可以在終端中執行以下命令來測試伺服器是否正常運行: ```sh curl http://localhost:5000/api # -> should return "Bank API v1.0.0" as a result ``` --- ## 表單與控制項 `
` 元素封裝了 HTML 文件的一個區域,使用者可以在其中使用互動式控制項輸入和提交資料。在表單中可以使用各種使用者介面(UI)控制項,其中最常見的是 `` 和 `
``` 使用 `value` 屬性,我們可以為給定的輸入欄位定義預設值。 另外,請注意 `balance` 的輸入欄位使用了 `number` 類型。它看起來是否與其他輸入欄位不同?試著與它互動看看。 ✅ 您能僅使用鍵盤導航並與表單互動嗎?您會如何操作? ## 將資料提交到伺服器 現在我們有了一個功能性 UI,下一步是將資料發送到伺服器。讓我們使用當前的程式碼進行快速測試:如果您點擊*登入*或*註冊*按鈕會發生什麼? 您是否注意到瀏覽器的 URL 區域發生了變化? ![點擊註冊按鈕後瀏覽器 URL 的變化截圖](../../../../translated_images/click-register.e89a30bf0d4bc9ca867dc537c4cea679a7c26368bd790969082f524fed2355bc.hk.png) `
` 的預設行為是使用 [GET 方法](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3)將表單資料提交到當前伺服器 URL,並將表單資料直接附加到 URL 中。然而,這種方法有一些缺點: - 發送的資料大小非常有限(約 2000 個字元)。 - 資料直接顯示在 URL 中(對於密碼來說並不理想)。 - 不支持檔案上傳。 因此,您可以將其更改為使用 [POST 方法](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5),該方法將表單資料作為 HTTP 請求的主體發送到伺服器,沒有上述限制。 > 雖然 POST 是最常用於發送資料的方法,但[在某些特定情況下](https://www.w3.org/2001/tag/doc/whenToUseGet.html),例如實現搜尋欄時,使用 GET 方法更為合適。 ### 任務 為註冊表單新增 `action` 和 `method` 屬性: ```html ``` 現在嘗試使用您的名字註冊一個新帳戶。點擊*註冊*按鈕後,您應該會看到如下內容: ![瀏覽器窗口顯示 localhost:5000/api/accounts 的地址,並顯示包含使用者資料的 JSON 字串](../../../../translated_images/form-post.61de4ca1b964d91a9e338416e19f218504dd0af5f762fbebabfe7ae80edf885f.hk.png) 如果一切順利,伺服器應該會以包含已創建帳戶資料的 [JSON](https://www.json.org/json-en.html) 回應您的請求。 ✅ 再次使用相同的名字嘗試註冊。會發生什麼? ## 無需重新載入頁面提交資料 您可能已經注意到,我們剛剛使用的方法有一個小問題:提交表單時,我們離開了應用程式,瀏覽器重定向到伺服器 URL。我們正在嘗試避免所有頁面重新載入,因為我們正在構建一個[單頁應用程式 (SPA)](https://en.wikipedia.org/wiki/Single-page_application)。 要將表單資料發送到伺服器而不強制重新載入頁面,我們需要使用 JavaScript 程式碼。與其在 `` 元素的 `action` 屬性中放置 URL,您可以使用任何以 `javascript:` 字串開頭的 JavaScript 程式碼來執行自定義操作。使用這種方法意味著您需要實現一些瀏覽器之前自動完成的任務: - 獲取表單資料 - 將表單資料轉換並編碼為合適的格式 - 創建 HTTP 請求並將其發送到伺服器 ### 任務 將註冊表單的 `action` 替換為: ```html ``` 打開 `app.js`,新增一個名為 `register` 的函數: ```js function register() { const registerForm = document.getElementById('registerForm'); const formData = new FormData(registerForm); const data = Object.fromEntries(formData); const jsonData = JSON.stringify(data); } ``` 在這裡,我們使用 `getElementById()` 獲取表單元素,並使用 [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData) 幫助器將表單控制項中的值提取為一組鍵/值對。然後,我們使用 [`Object.fromEntries()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries) 將資料轉換為普通物件,最後將資料序列化為 [JSON](https://www.json.org/json-en.html),這是一種網頁上常用的資料交換格式。 資料現在已準備好發送到伺服器。新增一個名為 `createAccount` 的函數: ```js async function createAccount(account) { try { const response = await fetch('//localhost:5000/api/accounts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: account }); return await response.json(); } catch (error) { return { error: error.message || 'Unknown error' }; } } ``` 這個函數在做什麼?首先,注意這裡的 `async` 關鍵字。這表示該函數包含將[**異步**](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function)執行的程式碼。當與 `await` 關鍵字一起使用時,它允許等待異步程式碼執行(例如等待伺服器回應)後再繼續。 這裡有一段關於 `async/await` 用法的影片: [![管理 Promise 的 Async 和 Await](https://img.youtube.com/vi/YwmlRkrxvkk/0.jpg)](https://youtube.com/watch?v=YwmlRkrxvkk "管理 Promise 的 Async 和 Await") > 🎥 點擊上方圖片觀看關於 async/await 的影片。 我們使用 `fetch()` API 將 JSON 資料發送到伺服器。此方法接受兩個參數: - 伺服器的 URL,因此我們在這裡放回 `//localhost:5000/api/accounts`。 - 請求的設置。在這裡,我們將方法設置為 `POST`,並提供請求的 `body`。由於我們將 JSON 資料發送到伺服器,因此還需要將 `Content-Type` 標頭設置為 `application/json`,以便伺服器知道如何解釋內容。 由於伺服器將以 JSON 回應請求,我們可以使用 `await response.json()` 解析 JSON 內容並返回結果物件。請注意,此方法是異步的,因此我們在返回之前使用 `await` 關鍵字以確保在解析期間的任何錯誤也能被捕獲。 現在,新增一些程式碼到 `register` 函數中以呼叫 `createAccount()`: ```js const result = await createAccount(jsonData); ``` 由於我們在這裡使用了 `await` 關鍵字,因此需要在 register 函數之前新增 `async` 關鍵字: ```js async function register() { ``` 最後,讓我們新增一些日誌來檢查結果。最終的函數應如下所示: ```js async function register() { const registerForm = document.getElementById('registerForm'); const formData = new FormData(registerForm); const jsonData = JSON.stringify(Object.fromEntries(formData)); const result = await createAccount(jsonData); if (result.error) { return console.log('An error occurred:', result.error); } console.log('Account created!', result); } ``` 這有點長,但我們完成了!如果您打開[瀏覽器開發者工具](https://developer.mozilla.org/docs/Learn/Common_questions/What_are_browser_developer_tools),並嘗試註冊一個新帳戶,您應該不會看到網頁發生任何變化,但控制台中會出現一條訊息,確認一切正常運作。 ![瀏覽器控制台中顯示日誌訊息的截圖](../../../../translated_images/browser-console.efaf0b51aaaf67782a29e1a0bb32cc063f189b18e894eb5926e02f1abe864ec2.hk.png) ✅ 您認為資料是安全地發送到伺服器的嗎?如果有人能攔截請求會怎麼樣?您可以閱讀[HTTPS](https://en.wikipedia.org/wiki/HTTPS)來了解更多關於安全資料通信的內容。 ## 資料驗證 如果您嘗試在未先設置用戶名的情況下註冊新帳戶,您會看到伺服器返回了一個狀態碼為 [400 (Bad Request)](https://developer.mozilla.org/docs/Web/HTTP/Status/400#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,%2C%20or%20deceptive%20request%20routing).) 的錯誤。 在將資料發送到伺服器之前,最好先[驗證表單資料](https://developer.mozilla.org/docs/Learn/Forms/Form_validation),以確保您發送的是有效請求。HTML5 表單控制項提供了內建的驗證功能,通過使用各種屬性: - `required`:該欄位必須填寫,否則表單無法提交。 - `minlength` 和 `maxlength`:定義文本欄位的最小和最大字元數。 - `min` 和 `max`:定義數字欄位的最小和最大值。 - `type`:定義預期的資料類型,例如 `number`、`email`、`file` 或[其他內建類型](https://developer.mozilla.org/docs/Web/HTML/Element/input)。此屬性還可能改變表單控制項的視覺呈現。 - `pattern`:允許定義一個[正則表達式](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Regular_Expressions)模式,用於測試輸入的資料是否有效。 提示:你可以使用 `:valid` 和 `:invalid` CSS 偽類來根據表單控件是否有效進行自定義外觀設計。 ### 任務 要創建一個有效的新帳戶,需要填寫兩個必填欄位:用戶名和貨幣,其餘欄位為可選。更新表單的 HTML,使用 `required` 屬性以及在欄位標籤中添加文字來說明: ```html ... ``` 雖然這個特定的伺服器實現並未對欄位的最大長度設置具體限制,但為用戶輸入的文字定義合理的限制始終是一個良好的做法。 為文字欄位添加 `maxlength` 屬性: ```html ... ... ``` 現在,如果你按下 *註冊* 按鈕,而某個欄位未遵守我們定義的驗證規則,你應該會看到類似以下的提示: ![截圖顯示嘗試提交表單時的驗證錯誤](../../../../translated_images/validation-error.8bd23e98d416c22f80076d04829a4bb718e0e550fd622862ef59008ccf0d5dce.hk.png) 這種在將任何數據發送到伺服器之前執行的驗證被稱為 **客戶端驗證**。但需要注意的是,並非所有檢查都能在不發送數據的情況下完成。例如,我們無法在這裡檢查是否已經存在相同用戶名的帳戶,除非向伺服器發送請求。在伺服器上執行的額外驗證被稱為 **伺服器端驗證**。 通常需要同時實現這兩種驗證。客戶端驗證通過即時向用戶提供反饋來改善用戶體驗,而伺服器端驗證則至關重要,確保你處理的用戶數據是可靠且安全的。 --- ## 🚀 挑戰 如果用戶已存在,請在 HTML 中顯示一條錯誤訊息。 以下是一個經過一些樣式設計後的最終登錄頁面的示例: ![添加 CSS 樣式後的登錄頁面截圖](../../../../translated_images/result.96ef01f607bf856aa9789078633e94a4f7664d912f235efce2657299becca483.hk.png) ## 課後測驗 [課後測驗](https://ff-quizzes.netlify.app/web/quiz/44) ## 回顧與自學 開發者在構建表單,特別是驗證策略方面,已經變得非常有創意。通過瀏覽 [CodePen](https://codepen.com) 來了解不同的表單流程;你能找到一些有趣且具有啟發性的表單嗎? ## 作業 [為你的銀行應用程式設計樣式](assignment.md) --- **免責聲明**: 此文件已使用人工智能翻譯服務 [Co-op Translator](https://github.com/Azure/co-op-translator) 翻譯。我們致力於提供準確的翻譯,但請注意,自動翻譯可能包含錯誤或不準確之處。應以原始語言的文件作為權威來源。對於關鍵資訊,建議使用專業的人類翻譯。我們對因使用此翻譯而引起的任何誤解或誤釋不承擔責任。