結論先行:`
| 特性 | <input type="submit"> / <button type="submit"> | <button type="button"> |
|---|---|---|
| 預設行為 | 觸發 ` | 無預設行為,需 JS 綁定 click |
| 阻止提交 | 需 event.preventDefault() 在 onsubmit 或 onclick 中 | 天然不提交,邏輯集中在 JS |
| 驗證攔截 | HTML5 驗證 (`required`, `pattern`) 會在提交前自動執行,難以自定義錯誤 UI | 完全手動驗證,靈活對接表單庫 |
| Enter 鍵觸發 | 表單內任意輸入框按 Enter 會觸發第一個 submit 按鈕 | 不觸發,需監聽 keydown |
| 語義化 | 明確表達「提交表單」 | 通用按鈕,需配合 `aria-label` |
<form id="loginForm" novalidate>
<input type="email" name="email" required>
<input type="password" name="password" required>
<button type="button" id="submitBtn">登入</button>
</form>
<script>
document.getElementById('submitBtn').addEventListener('click', async (e) => {
const form = document.getElementById('loginForm');
// 1. 手動驗證或使用表單庫
if (!form.checkValidity()) {
form.reportValidity(); // 顯示瀏覽器原生提示
return;
}
// 2. 禁用按鈕防重複提交
e.target.disabled = true;
e.target.textContent = '登入中...';
try {
const res = await fetch('/api/login', {
method: 'POST',
body: new FormData(form)
});
if (res.ok) location.href = '/dashboard';
} catch (err) {
alert('網路錯誤');
} finally {
e.target.disabled = false;
e.target.textContent = '登入';
}
});
</script>
`
總結:SPA/現代 Web 應用統一使用 `