MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

React组件中的表单处理

2021-11-166.8k 阅读

React 表单基础概念

在 React 应用程序中,表单是用户输入信息并与应用程序进行交互的重要组成部分。React 处理表单的方式与传统的 HTML 表单处理有所不同。传统的 HTML 表单会在用户提交表单时直接将数据发送到服务器。而在 React 中,表单数据的处理通常是在客户端进行,然后再根据应用的需求将数据发送到服务器。

React 中的表单元素,如 <input><textarea><select> 等,由于它们会维护自身的状态并在用户输入时更新,因此被称为“受控组件”。受控组件的值由 React 的 state 控制,这使得 React 可以更好地管理和跟踪表单数据的变化。

受控组件

文本输入框

在 React 中,创建一个简单的文本输入框受控组件可以如下实现:

import React, { useState } from 'react';

function TextInput() {
    const [inputValue, setInputValue] = useState('');

    const handleChange = (event) => {
        setInputValue(event.target.value);
    };

    return (
        <div>
            <input
                type="text"
                value={inputValue}
                onChange={handleChange}
            />
            <p>你输入的内容是: {inputValue}</p>
        </div>
    );
}

export default TextInput;

在上述代码中,useState 钩子用于创建一个状态变量 inputValue 和更新它的函数 setInputValueinput 元素的 value 属性被设置为 inputValue,这意味着输入框的值始终由 inputValue 控制。onChange 事件处理函数 handleChange 会在输入框的值发生变化时被调用,它会将新的值更新到 inputValue 中。

多行文本框

多行文本框(<textarea>)同样可以作为受控组件处理。

import React, { useState } from 'react';

function TextAreaInput() {
    const [textAreaValue, setTextAreaValue] = useState('');

    const handleChange = (event) => {
        setTextAreaValue(event.target.value);
    };

    return (
        <div>
            <textarea
                value={textAreaValue}
                onChange={handleChange}
            />
            <p>你输入的内容是: {textAreaValue}</p>
        </div>
    );
}

export default TextAreaInput;

这里的原理与文本输入框类似,textareavalue 属性受 textAreaValue 状态控制,onChange 事件用于更新状态。

单选框和复选框

  1. 单选框 单选框在一组选项中只能选择一个。以下是一个简单的示例:
import React, { useState } from 'react';

function RadioButtons() {
    const [selectedOption, setSelectedOption] = useState('option1');

    const handleChange = (event) => {
        setSelectedOption(event.target.value);
    };

    return (
        <div>
            <input
                type="radio"
                value="option1"
                checked={selectedOption === 'option1'}
                onChange={handleChange}
            />选项1
            <input
                type="radio"
                value="option2"
                checked={selectedOption === 'option2'}
                onChange={handleChange}
            />选项2
            <p>你选择的是: {selectedOption}</p>
        </div>
    );
}

export default RadioButtons;

在这个例子中,selectedOption 状态决定了哪个单选框被选中。每个 inputchecked 属性根据 selectedOption 的值来判断是否被选中,onChange 事件用于更新 selectedOption。 2. 复选框 复选框允许用户选择多个选项。以下是一个示例:

import React, { useState } from 'react';

function Checkboxes() {
    const [checkedOptions, setCheckedOptions] = useState([]);

    const handleChange = (event) => {
        const { value, checked } = event.target;
        if (checked) {
            setCheckedOptions([...checkedOptions, value]);
        } else {
            setCheckedOptions(checkedOptions.filter(option => option!== value));
        }
    };

    return (
        <div>
            <input
                type="checkbox"
                value="option1"
                onChange={handleChange}
            />选项1
            <input
                type="checkbox"
                value="option2"
                onChange={handleChange}
            />选项2
            <p>你选择的是: {checkedOptions.join(', ')}</p>
        </div>
    );
}

export default Checkboxes;

这里 checkedOptions 是一个数组,用于存储所有被选中的选项的值。handleChange 函数根据复选框的 checked 状态来更新 checkedOptions 数组。

非受控组件

虽然受控组件在 React 中是处理表单的常用方式,但在某些情况下,非受控组件也是有用的。非受控组件允许表单数据由 DOM 自身来处理,而不是完全由 React 的 state 控制。

在 React 中,可以使用 ref 来访问非受控组件的值。以下是一个非受控文本输入框的示例:

import React, { useRef } from'react';

function UncontrolledInput() {
    const inputRef = useRef();

    const handleSubmit = (event) => {
        event.preventDefault();
        console.log('输入的值是: ', inputRef.current.value);
    };

    return (
        <form onSubmit={handleSubmit}>
            <input
                type="text"
                ref={inputRef}
            />
            <button type="submit">提交</button>
        </form>
    );
}

export default UncontrolledInput;

在上述代码中,useRef 创建了一个 inputRef。通过将 ref 附加到 input 元素上,可以在提交表单时通过 inputRef.current.value 来获取输入框的值。

非受控组件适用于一些简单的表单场景,例如文件上传输入框(<input type="file">),因为 HTML 原生的文件上传功能较难用受控组件的方式完全模拟。

表单验证

在处理表单时,验证用户输入的数据是非常重要的。可以在表单提交时或者输入过程中实时进行验证。

实时验证

以文本输入框为例,假设要求输入内容长度至少为 5 个字符:

import React, { useState } from'react';

function ValidationInput() {
    const [inputValue, setInputValue] = useState('');
    const [isValid, setIsValid] = useState(true);

    const handleChange = (event) => {
        const value = event.target.value;
        setInputValue(value);
        setIsValid(value.length >= 5);
    };

    return (
        <div>
            <input
                type="text"
                value={inputValue}
                onChange={handleChange}
            />
            {!isValid && <p>输入内容长度至少为 5 个字符</p>}
        </div>
    );
}

export default ValidationInput;

在这个示例中,isValid 状态用于表示输入是否有效。handleChange 函数在更新 inputValue 的同时,检查输入长度并更新 isValid 状态。如果 isValidfalse,则显示错误提示信息。

提交时验证

对于更复杂的表单,通常在提交时进行全面验证。以下是一个包含多个输入字段并在提交时验证的示例:

import React, { useState } from'react';

function SignupForm() {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');
    const [usernameError, setUsernameError] = useState('');
    const [passwordError, setPasswordError] = useState('');

    const handleUsernameChange = (event) => {
        setUsername(event.target.value);
        setUsernameError('');
    };

    const handlePasswordChange = (event) => {
        setPassword(event.target.value);
        setPasswordError('');
    };

    const handleSubmit = (event) => {
        event.preventDefault();
        let hasError = false;
        if (username.length < 3) {
            setUsernameError('用户名长度至少为 3 个字符');
            hasError = true;
        }
        if (password.length < 6) {
            setPasswordError('密码长度至少为 6 个字符');
            hasError = true;
        }
        if (!hasError) {
            console.log('表单提交成功,用户名: ', username, '密码: ', password);
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <label>
                用户名:
                <input
                    type="text"
                    value={username}
                    onChange={handleUsernameChange}
                />
                {usernameError && <span style={{ color:'red' }}>{usernameError}</span>}
            </label>
            <br />
            <label>
                密码:
                <input
                    type="password"
                    value={password}
                    onChange={handlePasswordChange}
                />
                {passwordError && <span style={{ color:'red' }}>{passwordError}</span>}
            </label>
            <br />
            <button type="submit">注册</button>
        </form>
    );
}

export default SignupForm;

在这个注册表单示例中,handleSubmit 函数在表单提交时检查用户名和密码的长度。如果不符合要求,则设置相应的错误信息并阻止表单提交。

表单数据的提交

在 React 中,处理表单数据提交通常涉及将数据发送到服务器。这可以通过使用 fetch 或者第三方库如 axios 来实现。

使用 fetch 提交表单数据

假设我们有一个简单的登录表单,需要将用户名和密码发送到服务器:

import React, { useState } from'react';

function LoginForm() {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');

    const handleUsernameChange = (event) => {
        setUsername(event.target.value);
    };

    const handlePasswordChange = (event) => {
        setPassword(event.target.value);
    };

    const handleSubmit = async (event) => {
        event.preventDefault();
        const data = {
            username,
            password
        };
        try {
            const response = await fetch('/api/login', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(data)
            });
            if (response.ok) {
                const result = await response.json();
                console.log('登录成功: ', result);
            } else {
                console.error('登录失败');
            }
        } catch (error) {
            console.error('网络错误: ', error);
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <label>
                用户名:
                <input
                    type="text"
                    value={username}
                    onChange={handleUsernameChange}
                />
            </label>
            <br />
            <label>
                密码:
                <input
                    type="password"
                    value={password}
                    onChange={handlePasswordChange}
                />
            </label>
            <br />
            <button type="submit">登录</button>
        </form>
    );
}

export default LoginForm;

在上述代码中,handleSubmit 函数在表单提交时构建一个包含用户名和密码的对象 data。然后使用 fetch 发送一个 POST 请求到 /api/login 端点。如果请求成功,会解析响应数据并在控制台打印登录成功信息;如果失败,则打印相应的错误信息。

使用 axios 提交表单数据

axios 是一个流行的 HTTP 客户端库,使用起来更加简洁。以下是使用 axios 实现相同登录表单提交的示例:

import React, { useState } from'react';
import axios from 'axios';

function LoginFormWithAxios() {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');

    const handleUsernameChange = (event) => {
        setUsername(event.target.value);
    };

    const handlePasswordChange = (event) => {
        setPassword(event.target.value);
    };

    const handleSubmit = async (event) => {
        event.preventDefault();
        const data = {
            username,
            password
        };
        try {
            const response = await axios.post('/api/login', data);
            console.log('登录成功: ', response.data);
        } catch (error) {
            console.error('登录失败: ', error);
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <label>
                用户名:
                <input
                    type="text"
                    value={username}
                    onChange={handleUsernameChange}
                />
            </label>
            <br />
            <label>
                密码:
                <input
                    type="password"
                    value={password}
                    onChange={handlePasswordChange}
                />
            </label>
            <br />
            <button type="submit">登录</button>
        </form>
    );
}

export default LoginFormWithAxios;

这里使用 axios.post 方法发送 POST 请求,axios 会自动设置正确的 Content-Type 并处理请求和响应。

表单状态管理

随着表单复杂度的增加,管理表单状态变得更加重要。除了简单的 useState 钩子,还可以使用更高级的状态管理方案,如 Redux 或 MobX。

使用 Redux 管理表单状态

  1. 安装依赖 首先需要安装 reduxreact - redux
npm install redux react-redux
  1. 创建 Redux 相关文件
  • actions.js
const UPDATE_USERNAME = 'UPDATE_USERNAME';
const UPDATE_PASSWORD = 'UPDATE_PASSWORD';

export const updateUsername = (username) => ({
    type: UPDATE_USERNAME,
    payload: username
});

export const updatePassword = (password) => ({
    type: UPDATE_PASSWORD,
    payload: password
});
  • reducers.js
const initialState = {
    username: '',
    password: ''
};

const formReducer = (state = initialState, action) => {
    switch (action.type) {
        case 'UPDATE_USERNAME':
            return {
               ...state,
                username: action.payload
            };
        case 'UPDATE_PASSWORD':
            return {
               ...state,
                password: action.payload
            };
        default:
            return state;
    }
};

export default formReducer;
  • store.js
import { createStore } from'redux';
import formReducer from './reducers';

const store = createStore(formReducer);

export default store;
  1. 在 React 组件中使用 Redux
import React from'react';
import { useSelector, useDispatch } from'react-redux';
import { updateUsername, updatePassword } from './actions';

function LoginFormWithRedux() {
    const dispatch = useDispatch();
    const username = useSelector(state => state.username);
    const password = useSelector(state => state.password);

    const handleUsernameChange = (event) => {
        dispatch(updateUsername(event.target.value));
    };

    const handlePasswordChange = (event) => {
        dispatch(updatePassword(event.target.value));
    };

    const handleSubmit = (event) => {
        event.preventDefault();
        console.log('用户名: ', username, '密码: ', password);
    };

    return (
        <form onSubmit={handleSubmit}>
            <label>
                用户名:
                <input
                    type="text"
                    value={username}
                    onChange={handleUsernameChange}
                />
            </label>
            <br />
            <label>
                密码:
                <input
                    type="password"
                    value={password}
                    onChange={handlePasswordChange}
                />
            </label>
            <br />
            <button type="submit">登录</button>
        </form>
    );
}

export default LoginFormWithRedux;

在这个示例中,Redux 用于管理登录表单的状态。useSelector 钩子用于从 Redux store 中获取状态,useDispatch 用于分发 action 来更新状态。

使用 MobX 管理表单状态

  1. 安装依赖
npm install mobx mobx - react
  1. 创建 MobX 相关文件
  • store.js
import { makeObservable, observable, action } from'mobx';

class FormStore {
    constructor() {
        this.username = '';
        this.password = '';
        makeObservable(this, {
            username: observable,
            password: observable,
            updateUsername: action,
            updatePassword: action
        });
    }

    updateUsername = (username) => {
        this.username = username;
    };

    updatePassword = (password) => {
        this.password = password;
    };
}

const formStore = new FormStore();

export default formStore;
  1. 在 React 组件中使用 MobX
import React from'react';
import { observer } from'mobx - react';
import formStore from './store';

function LoginFormWithMobX() {
    const handleUsernameChange = (event) => {
        formStore.updateUsername(event.target.value);
    };

    const handlePasswordChange = (event) => {
        formStore.updatePassword(event.target.value);
    };

    const handleSubmit = (event) => {
        event.preventDefault();
        console.log('用户名: ', formStore.username, '密码: ', formStore.password);
    };

    return (
        <form onSubmit={handleSubmit}>
            <label>
                用户名:
                <input
                    type="text"
                    value={formStore.username}
                    onChange={handleUsernameChange}
                />
            </label>
            <br />
            <label>
                密码:
                <input
                    type="password"
                    value={formStore.password}
                    onChange={handlePasswordChange}
                />
            </label>
            <br />
            <button type="submit">登录</button>
        </form>
    );
}

export default observer(LoginFormWithMobX);

这里 MobX 通过 makeObservable 使 FormStore 的属性可观察,并提供了更新状态的 action 方法。observer 函数将 React 组件包装,使其能够响应 MobX store 的变化。

复杂表单场景处理

动态表单

在一些情况下,表单的字段可能需要根据用户的操作动态添加或删除。例如,一个添加联系人的表单,用户可以点击按钮添加更多的联系人信息字段。

import React, { useState } from'react';

function DynamicForm() {
    const [contacts, setContacts] = useState([{ name: '', phone: '' }]);

    const handleNameChange = (index, event) => {
        const newContacts = [...contacts];
        newContacts[index].name = event.target.value;
        setContacts(newContacts);
    };

    const handlePhoneChange = (index, event) => {
        const newContacts = [...contacts];
        newContacts[index].phone = event.target.value;
        setContacts(newContacts);
    };

    const addContact = () => {
        setContacts([...contacts, { name: '', phone: '' }]);
    };

    const removeContact = (index) => {
        const newContacts = [...contacts];
        newContacts.splice(index, 1);
        setContacts(newContacts);
    };

    const handleSubmit = (event) => {
        event.preventDefault();
        console.log('提交的联系人信息: ', contacts);
    };

    return (
        <form onSubmit={handleSubmit}>
            {contacts.map((contact, index) => (
                <div key={index}>
                    <label>
                        姓名:
                        <input
                            type="text"
                            value={contact.name}
                            onChange={(event) => handleNameChange(index, event)}
                        />
                    </label>
                    <label>
                        电话:
                        <input
                            type="text"
                            value={contact.phone}
                            onChange={(event) => handlePhoneChange(index, event)}
                        />
                    </label>
                    {contacts.length > 1 && (
                        <button
                            type="button"
                            onClick={() => removeContact(index)}
                        >删除</button>
                    )}
                    <br />
                </div>
            ))}
            <button type="button" onClick={addContact}>添加联系人</button>
            <button type="submit">提交</button>
        </form>
    );
}

export default DynamicForm;

在这个示例中,contacts 状态是一个包含多个联系人信息对象的数组。map 方法用于渲染每个联系人的输入字段,handleNameChangehandlePhoneChange 函数根据索引更新相应联系人的信息。addContactremoveContact 函数分别用于添加和删除联系人。

嵌套表单

当表单具有嵌套结构时,处理起来会更加复杂。例如,一个订单表单,包含多个订单项,每个订单项又有自己的详细信息。

import React, { useState } from'react';

function OrderForm() {
    const [orderItems, setOrderItems] = useState([{ product: '', quantity: 1 }]);

    const handleProductChange = (index, event) => {
        const newOrderItems = [...orderItems];
        newOrderItems[index].product = event.target.value;
        setOrderItems(newOrderItems);
    };

    const handleQuantityChange = (index, event) => {
        const newOrderItems = [...orderItems];
        newOrderItems[index].quantity = parseInt(event.target.value, 10) || 1;
        setOrderItems(newOrderItems);
    };

    const addOrderItem = () => {
        setOrderItems([...orderItems, { product: '', quantity: 1 }]);
    };

    const removeOrderItem = (index) => {
        const newOrderItems = [...orderItems];
        newOrderItems.splice(index, 1);
        setOrderItems(newOrderItems);
    };

    const handleSubmit = (event) => {
        event.preventDefault();
        console.log('提交的订单信息: ', orderItems);
    };

    return (
        <form onSubmit={handleSubmit}>
            {orderItems.map((orderItem, index) => (
                <div key={index}>
                    <label>
                        产品:
                        <input
                            type="text"
                            value={orderItem.product}
                            onChange={(event) => handleProductChange(index, event)}
                        />
                    </label>
                    <label>
                        数量:
                        <input
                            type="number"
                            value={orderItem.quantity}
                            onChange={(event) => handleQuantityChange(index, event)}
                        />
                    </label>
                    {orderItems.length > 1 && (
                        <button
                            type="button"
                            onClick={() => removeOrderItem(index)}
                        >删除</button>
                    )}
                    <br />
                </div>
            ))}
            <button type="button" onClick={addOrderItem}>添加订单项</button>
            <button type="submit">提交订单</button>
        </form>
    );
}

export default OrderForm;

在这个订单表单示例中,orderItems 状态是一个包含订单项对象的数组。每个订单项有产品和数量字段,通过类似动态表单的方式处理每个订单项的输入和整体表单的操作。

与第三方表单库集成

在 React 开发中,使用第三方表单库可以简化表单的开发流程,提高开发效率。一些流行的第三方表单库包括 FormikYup

使用 Formik 和 Yup 构建表单

  1. 安装依赖
npm install formik yup
  1. 使用 Yup 进行表单验证 首先创建一个 Yup 验证模式:
import * as yup from 'yup';

const signupSchema = yup.object().shape({
    username: yup.string().min(3, '用户名长度至少为 3 个字符').required('用户名是必填项'),
    password: yup.string().min(6, '密码长度至少为 6 个字符').required('密码是必填项')
});

export default signupSchema;
  1. 使用 Formik 构建表单
import React from'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import signupSchema from './signupSchema';

function SignupFormWithFormik() {
    const initialValues = {
        username: '',
        password: ''
    };

    const onSubmit = (values, { setSubmitting }) => {
        console.log('提交的值: ', values);
        setSubmitting(false);
    };

    return (
        <Formik
            initialValues={initialValues}
            validationSchema={signupSchema}
            onSubmit={onSubmit}
        >
            <Form>
                <label>
                    用户名:
                    <Field name="username" />
                    <ErrorMessage name="username" component="span" style={{ color:'red' }} />
                </label>
                <br />
                <label>
                    密码:
                    <Field name="password" type="password" />
                    <ErrorMessage name="password" component="span" style={{ color:'red' }} />
                </label>
                <br />
                <button type="submit">注册</button>
            </Form>
        </Formik>
    );
}

export default SignupFormWithFormik;

在这个示例中,Formik 提供了表单的状态管理和提交功能,Yup 用于定义表单验证规则。FormikinitialValues 设置初始表单值,validationSchema 设置验证模式,onSubmit 处理表单提交。Field 组件用于渲染表单输入字段,ErrorMessage 用于显示验证错误信息。

通过以上对 React 组件中表单处理的各个方面的详细介绍,从基础概念到复杂场景以及与第三方库的集成,开发者可以全面掌握在 React 应用中高效处理表单的方法,为用户提供良好的交互体验并确保数据的准确性和安全性。