当前位置: 首页 > article >正文

React 第五十六节 Router 中useSubmit的使用详解及注意事项

前言

useSubmitReact Router v6.4+ 引入的强大钩子,用于以编程方式提交表单数据。
它提供了对表单提交过程的精细控制,特别适合需要自定义提交行为或非标准表单场景的应用。

一、useSubmit 核心用途

  1. 编程式表单提交:不依赖 <form> 元素手动提交数据
  2. 自定义提交逻辑:在提交前/后执行额外操作
  3. 动态表单构建:根据应用状态生成提交数据
  4. 替代传统表单:在复杂UI中实现表单功能

二、useSubmit 基本用法

import { useSubmit } from 'react-router-dom';function CustomSubmitButton() {const submit = useSubmit();const handleSubmit = () => {// 构建表单数据const formData = new FormData();formData.append('username', 'john_doe');formData.append('password', 'secure123');// 提交数据submit(formData, {method: 'post',action: '/login',encType: 'application/x-www-form-urlencoded'});};return (<button onClick={handleSubmit}>登录</button>);
}

三、useSubmit 参数详解

3.1、submit 函数参数

```javascript
submit(data: FormData | URLSearchParams | { [key: string]: string } | null,options?: {method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';action?: string;encType?: 'application/x-www-form-urlencoded' | 'multipart/form-data';replace?: boolean;}
)
```

3.2、选项说明:

method:HTTP方法(默认为"GET")
action:提交的目标URL(默认为当前URL
encType:表单编码类型(默认为"application/x-www-form-urlencoded")
replace:是否替换历史记录而不是添加新条目(默认为false

四、useSubmit 实际应用场景

4.1、自定义删除按钮

import { useSubmit } from 'react-router-dom';function DeleteButton({ itemId }) {const submit = useSubmit();const handleDelete = () => {if (window.confirm('确定要删除此项吗?')) {submit({ id: itemId }, { method: 'delete', action: `/items/${itemId}` });}};return (<button onClick={handleDelete}className="delete-btn"aria-label="删除项目">🗑️ 删除</button>);
}

4.2、动态搜索表单

import { useState, useEffect } from 'react';
import { useSubmit, useLocation } from 'react-router-dom';function SearchBar() {const submit = useSubmit();const location = useLocation();const [query, setQuery] = useState('');// 使用防抖自动提交搜索useEffect(() => {const timerId = setTimeout(() => {if (query.trim() !== '') {const formData = new FormData();formData.append('q', query);submit(formData, {method: 'get',action: '/search',replace: query === '' // 空查询时替换历史记录});}}, 300);return () => clearTimeout(timerId);}, [query, submit]);// 初始化时从URL读取查询参数useEffect(() => {const params = new URLSearchParams(location.search);setQuery(params.get('q') || '');}, [location.search]);return (<div className="search-container"><inputtype="text"value={query}onChange={(e) => setQuery(e.target.value)}placeholder="搜索..."className="search-input"/>{query && (<button onClick={() => setQuery('')}className="clear-btn"></button>)}</div>);
}

4.3、多步骤表单

import { useState } from 'react';
import { useSubmit } from 'react-router-dom';function MultiStepForm() {const submit = useSubmit();const [step, setStep] = useState(1);const [formData, setFormData] = useState({personal: {},address: {},preferences: {}});const handleNextStep = () => {if (step < 3) {setStep(step + 1);} else {// 最终提交submitForm();}};const handlePrevStep = () => {setStep(step - 1);};const updateFormData = (section, data) => {setFormData(prev => ({...prev,[section]: { ...prev[section], ...data }}));};const submitForm = () => {// 构建完整表单数据const finalData = {...formData.personal,...formData.address,...formData.preferences};submit(finalData, {method: 'post',action: '/register',encType: 'application/json'});};return (<div className="multi-step-form"><div className="progress-bar"><div className={`step ${step >= 1 ? 'active' : ''}`}>个人信息</div><div className={`step ${step >= 2 ? 'active' : ''}`}>地址信息</div><div className={`step ${step >= 3 ? 'active' : ''}`}>偏好设置</div></div><div className="form-content">{step === 1 && (<PersonalInfo data={formData.personal}onChange={data => updateFormData('personal', data)}/>)}{step === 2 && (<AddressInfo data={formData.address}onChange={data => updateFormData('address', data)}/>)}{step === 3 && (<Preferences data={formData.preferences}onChange={data => updateFormData('preferences', data)}/>)}</div><div className="form-navigation">{step > 1 && (<button onClick={handlePrevStep}>上一步</button>)}<button onClick={handleNextStep}>{step < 3 ? '下一步' : '提交注册'}</button></div></div>);
}

4.4、文件上传

import { useRef, useState } from 'react';
import { useSubmit } from 'react-router-dom';function FileUploader() {const submit = useSubmit();const fileInputRef = useRef(null);const [isUploading, setIsUploading] = useState(false);const [progress, setProgress] = useState(0);const handleFileChange = async (e) => {const file = e.target.files[0];if (!file) return;setIsUploading(true);try {// 创建FormData对象const formData = new FormData();formData.append('file', file);formData.append('description', '用户上传的文件');// 创建XMLHttpRequest以获取上传进度const xhr = new XMLHttpRequest();// 进度事件处理xhr.upload.addEventListener('progress', (event) => {if (event.lengthComputable) {const percent = Math.round((event.loaded / event.total) * 100);setProgress(percent);}});// 完成事件处理xhr.addEventListener('load', () => {setIsUploading(false);setProgress(0);alert('文件上传成功!');});// 错误处理xhr.addEventListener('error', () => {setIsUploading(false);alert('文件上传失败');});// 打开并发送请求xhr.open('POST', '/upload');xhr.send(formData);// 或者使用submit钩子(但无法获取进度)// submit(formData, {//   method: 'post',//   action: '/upload',//   encType: 'multipart/form-data'// });} catch (error) {setIsUploading(false);console.error('上传失败:', error);}};return (<div className="file-uploader"><input type="file" ref={fileInputRef} onChange={handleFileChange}style={{ display: 'none' }} /><button onClick={() => fileInputRef.current.click()}disabled={isUploading}>选择文件</button>{isUploading && (<div className="upload-progress"><div className="progress-bar" style={{ width: `${progress}%` }}></div><span>{progress}%</span></div>)}</div>);
}

4.5、乐观更新与表单提交

import { useState } from 'react';
import { useSubmit } from 'react-router-dom';function TodoItem({ todo }) {const submit = useSubmit();const [isCompleted, setIsCompleted] = useState(todo.completed);const [isUpdating, setIsUpdating] = useState(false);const handleToggle = async () => {// 乐观更新:立即更新UIconst newCompleted = !isCompleted;setIsCompleted(newCompleted);setIsUpdating(true);try {// 准备表单数据const formData = new FormData();formData.append('id', todo.id);formData.append('completed', newCompleted.toString());// 提交更新submit(formData, {method: 'patch',action: `/todos/${todo.id}`,encType: 'application/x-www-form-urlencoded'});} catch (error) {// 出错时回滚状态setIsCompleted(!newCompleted);console.error('更新失败:', error);} finally {setIsUpdating(false);}};return (<li className={`todo-item ${isCompleted ? 'completed' : ''}`}><inputtype="checkbox"checked={isCompleted}onChange={handleToggle}disabled={isUpdating}/><span className="todo-text">{todo.text}</span>{isUpdating && <span className="updating-indicator">更新中...</span>}</li>);
}

五、useSubmit 与相关API对比

在这里插入图片描述

六、useSubmit 最佳实践

  1. 数据验证:提交前验证数据

  2. 用户反馈:提交时显示加载状态

  3. 错误处理:捕获并处理提交错误

  4. 编码类型:

    a.简单数据:application/x-www-form-urlencoded

    b.文件上传:multipart/form-data

    c.JSON数据:手动序列化并设置适当headers

  5. 性能优化:大文件上传使用分块或流式上传

// 带验证的提交示例
function ValidatedForm() {const submit = useSubmit();const [email, setEmail] = useState('');const [error, setError] = useState('');const handleSubmit = () => {// 验证邮箱格式if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {setError('请输入有效的邮箱地址');return;}// 提交数据submit({ email }, { method: 'post', action: '/subscribe' });};return (<div className="subscribe-form"><inputtype="email"value={email}onChange={(e) => {setEmail(e.target.value);setError('');}}placeholder="输入您的邮箱"className={error ? 'error' : ''}/>{error && <div className="error-message">{error}</div>}<button onClick={handleSubmit}>订阅</button></div>);
}

七、useSubmit 注意事项

  1. 路由上下文:必须在路由组件中使用(在 <Router> 上下文中)

  2. 数据格式:

    对象会被转换为 URLSearchParams

    文件上传必须使用 FormData

  3. GET请求:数据会作为查询参数添加到URL

  4. 重定向:提交后服务器应返回重定向响应

  5. 状态管理:提交不会自动管理加载状态,需自行实现

总结

useSubmitReact Router 中处理表单提交的高级工具,特别适合以下场景:

  1. 自定义表单逻辑:当需要超出标准表单的行为时
  2. 动态数据提交:根据应用状态生成提交数据
  3. 非表单元素提交:从按钮或其他UI元素触发提交
  4. 复杂交互流程:如多步骤表单、乐观更新
  5. 文件上传:处理二进制数据提交

通过合理使用 useSubmit,您可以构建更灵活、更强大的表单交互体验,同时保持与 React Router 数据路由模型的无缝集成

http://www.lryc.cn/news/2402641.html

相关文章:

  • 华为云学堂-云原生开发者认证课程列表
  • Vue.js 组件:深入理解与实践
  • 什么是强化学习:设置奖励函数最为loss, 监督学习:标签准确率作为loss
  • 理解网络协议
  • placeholder不显示and模板字符串无效
  • 在MyBatis中设计SQL返回布尔值(Boolean)有几种常见方法
  • 全球知名具身智能/AI机器人实验室介绍之AI FACTORY基于慕尼黑工业大学
  • DASCTF
  • 钉钉 - 机器人消息推送(签名版)
  • Redux 实践与中间件应用
  • ModBus总线协议
  • 【计算机网络】非阻塞IO——poll实现多路转接
  • 在.NET Core控制器中获取AJAX传递的Body参数
  • snprintf函数用法及注意事项详解
  • vue-20(Vuex 状态管理的最佳实践)
  • DAX权威指南8:DAX引擎与存储优化
  • 智慧货运飞船多维度可视化管控系统
  • 电脑开不了机,主板显示67码解决过程
  • Spring Boot 类加载机制深度解析
  • Python 训练营打卡 Day 45
  • 自托管图书搜索引擎Bookologia
  • 前端flex、grid布局
  • Maven相关问题:jna版本与ES冲突 + aop失效
  • Tomcat全方位监控实施方案指南
  • 开源PHP在线客服系统源码搭建教程
  • centos7升级glibic-2.28
  • 在Docker里面运行Docker
  • 设计模式复习小结
  • To be or Not to be, That‘s a Token——论文阅读笔记——Beyond the 80/20 Rule和R2R
  • 【基础】每天掌握一个Linux命令 - awk