优雅提交:Form表单中往往会忽略掉细节

302 阅读3分钟

在现代 Web 开发中,表单是用户与网站互动的重要桥梁。传统的表单提交方式会导致页面刷新,影响用户体验。本文将深入探讨如何使用 JavaScript 提升表单交互的艺术,实现优雅的无刷新提交,并结合实例讲解关键技术点。

1. e.currentTargete.preventDefault():事件处理的基石

在处理表单提交事件时,e.currentTargete.preventDefault() 是两个非常重要的概念。

  • e.currentTarget 指的是绑定事件的元素。在表单的 submit 事件中,如果事件直接绑定在 <form> 元素上,则 e.currentTarget 就是 form 元素本身。如果事件绑定在 form 表单内部的某个元素上,例如 button,则 e.currentTarget 指向绑定事件的元素。

  • e.preventDefault() 用于阻止事件的默认行为。对于 submit 事件来说,默认行为是页面刷新。使用 e.preventDefault() 可以阻止这种默认行为,从而实现无刷新提交。

此外,我们还需要重点关注 e.currentTarget 上的 form 属性。 form 属性指向触发事件的元素所属的表单。以下元素拥有 form 属性:

  • <button> (当 type 属性为 submitreset 或不设置时)
  • <input> (当 type 属性为 submitimage 时)
  • <output>
  • <fieldset>

特别注意的是,拥有form属性的表单元素可以写在form表单外面,使用form属性指向表单的id,这样点击该元素,相当于点击了form表单内部的submit按钮。

理解 e.currentTarget 上的 form 属性非常重要,因为通过它我们可以方便地访问到表单元素,即使触发事件的元素不是表单本身,而是表单内的按钮。

2. requestSubmit():触发表单提交的新方式

requestSubmit() 方法是 HTMLFormElement 的一个方法,用于以编程方式触发表单提交。与直接调用 form.submit() 不同,requestSubmit() 会触发表单的 submit 事件,从而可以执行表单校验等操作。这使得我们可以更灵活地控制表单提交流程,例如我们在textarea按下ctrl+回车可以直接提交表单。

3. JavaScript 表单校验:保证数据的有效性

表单校验是确保用户输入数据有效性的重要步骤。通过在客户端进行校验,可以减少服务器的负担,并及时向用户提供反馈。

function verifyForm(form, rules) {
    const errors = {};
    let isValid = true;

    for (const fieldName in rules) {
        const fieldValue = form.elements[fieldName]?.value?.trim();
        const fieldRules = rules[fieldName];

        // 清除错误提示
        document.getElementById(`${fieldName}Error`)?.textContent = "";

        for (const ruleName in fieldRules) {
            const ruleValue = fieldRules[ruleName];
            let errorMessage = null;

            switch (ruleName) {
                case 'required':
                    if (!fieldValue) {
                        errorMessage = ruleValue || `${fieldName} 是必填项`;
                    }
                    break;
                case 'minLength':
                    if (fieldValue && fieldValue.length < ruleValue) { // 空值不进行长度校验
                        errorMessage = `${fieldName} 至少需要 ${ruleValue} 个字符`;
                    }
                    break;
                case 'maxLength':
                    if (fieldValue && fieldValue.length > ruleValue) { // 空值不进行长度校验
                        errorMessage = `${fieldName} 最多允许 ${ruleValue} 个字符`;
                    }
                    break;
                case 'pattern':
                    if (fieldValue && !ruleValue.test(fieldValue)) {// 空值不进行正则校验
                        errorMessage = ruleValue.message || `${fieldName} 格式不正确`;
                    }
                    break;
                case 'custom':
                    if (typeof ruleValue === 'function' && !ruleValue(fieldValue)) {
                        errorMessage = `${fieldName} 不符合自定义规则`;
                    }
                    break;
            }

            if (errorMessage) {
                errors[fieldName] = errors[fieldName] || [];
                errors[fieldName].push(errorMessage);
                isValid = false;
                document.getElementById(`${fieldName}Error`).textContent = errorMessage; //在页面上显示错误信息
            }
        }
    }

    return { isValid, errors };
}

知名度表单验证js库:Validate.js

4. 无刷新提交:提升用户体验的关键

传统的 Form 提交如何导致页面刷新?

在传统的 HTML form 提交中,当用户点击 submit 按钮(<input type="submit"><button type="submit">),浏览器会执行以下步骤:

  1. 构建请求: 浏览器根据 form 元素的 action 属性指定的 URL 和 method 属性指定的 HTTP 方法(GET 或 POST)构建一个 HTTP 请求。form 中的所有表单元素的值会被编码成请求体(POST 方法)或 URL 参数(GET 方法)。
  2. 发送请求: 浏览器向服务器发送构建好的 HTTP 请求。
  3. 服务器处理请求: 服务器接收到请求后,根据请求中的数据进行相应的处理(例如,将数据保存到数据库、执行某些计算等)。
  4. 服务器返回响应: 服务器处理完成后,会返回一个 HTTP 响应。这个响应通常包含一个新的 HTML 页面。
  5. 浏览器接收响应并渲染新页面: 浏览器接收到服务器的响应后,会解析响应体中的 HTML,并用新的页面内容替换当前页面,这就是我们看到的页面刷新。

为什么会刷新?

根本原因是传统的 form 提交是基于浏览器原生的提交机制,它依赖于服务器返回一个新的完整 HTML 页面。浏览器为了显示服务器返回的新内容,必须重新加载整个页面。

这种方式的缺点

  1. 用户体验差: 页面刷新会导致短暂的白屏,打断用户的操作流程,影响用户体验。
  2. 浪费带宽: 每次提交都需要重新加载整个页面,浪费不必要的带宽。
  3. 状态丢失: 页面刷新会导致之前的页面状态丢失,例如表单中已填写的数据、滚动条的位置等。

5. 完整示例:融会贯通

将无刷新提交的讲解和完整示例合并在一起,以便更清晰地展示如何将所有知识点应用到实际代码中。

<!DOCTYPE html>
<html>
<head>
    <title>优雅提交示例</title>
    <style>
        .error {
            color: red;
            margin-top: 5px;
            font-size: small;
        }
    </style>
</head>
<body>

<form id="myForm">
    <label for="username">用户名:</label>
    <input type="text" id="username" name="username"><span class="error" id="usernameError"></span><br><br>

    <label for="email">邮箱:</label>
    <input type="email" id="email" name="email"><span class="error" id="emailError"></span><br><br>

    <label for="age">年龄:</label>
    <input type="number" id="age" name="age"><span class="error" id="ageError"></span><br><br>

    <label for="desc">描述:</label><br>
    <textarea id="desc" name="desc"></textarea><span class="error" id="descError"></span><br><br>

    <button type="submit">提交</button>
</form>
<form id="myForm2">
    <input type="text" id = 'input1'>
</form>
<button type="submit" form = 'myForm2'>外部提交</button>

<div id="result"></div>

<script>
    // ... (verifyForm 函数,同上)

    const myForm = document.getElementById('myForm');
    const resultDiv = document.getElementById('result');
    const descTextarea = document.getElementById('desc');
        const btn = document.querySelector('[type="submit"][form]');
    myForm.addEventListener('submit', function(event) {
        event.preventDefault();

        const rules = {
            username: { required: "用户名不能为空", minLength: 3, maxLength: 10 },
            email: { required: true, pattern: {test: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message:"请输入正确的邮箱格式"} },
            age:{custom:(value)=>{
                return !value || (value >= 18 && value <= 100)
            }},
            desc: {required: "描述不能为空"}
        };

        const validationResult = verifyForm(myForm, rules);

        if (!validationResult.isValid) {
            return; // 阻止提交
        }

        const formData = new FormData(myForm);
        const submitButton = myForm.querySelector('[type="submit"]'); //找到提交按钮
        submitButton.disabled = true; //禁用按钮,防止重复提交
        resultDiv.textContent = '正在提交...';

        fetch('/your-api-endpoint', { // 请替换为你的 API 端点
            method: 'POST',
            body: formData
        })
            .then(response => response.json())
            .then(data => {
                resultDiv.textContent = '提交成功!服务器返回:' + JSON.stringify(data);
                myForm.reset();
                submitButton.disabled = false; //恢复按钮可用
                alert('提交成功')
            })
            .catch(error => {
                submitButton.disabled = false; //恢复按钮可用
                resultDiv.textContent = '提交失败:' + error;
                console.error("请求错误:", error)
            });
    });
        btn.addEventListener('click', function(e){
            e.preventDefault();
    //通过requestSubmit()方法触发表单的提交事件
    e.target.form.requestSubmit()
        })
    descTextarea.addEventListener('keydown', (event) => {
        if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
            event.preventDefault();
            myForm.requestSubmit();
        }
    });
</script>

</body>
</html>

6. 一些优秀的表单库

  • React Hook Form: 专门为 React 应用设计的表单库,提供了强大的表单管理和验证功能。

  • Formik: 一个流行的表单库,可以与 React、Vue、Angular 等框架一起使用。

  • FormKit: 通过简化表单结构、生成、验证、主题化、提交、错误处理等,使开发人员能够以10倍的速度构建表单。

  • Yup: 一个用于对象 schema 验证的 JavaScript schema 构建器。它可以与 Formik 等表单库结合使用