从 Vue3 到 React · 快速上手系列第 5 篇:事件处理与表单(受控组件 vs v-model)

面向 Vue3 开发者的 React 快速上手系列

Vue 的 v-model 是双向绑定的神器,一个指令搞定表单。React 没有 v-model------它推崇受控组件(Controlled Component):状态是唯一数据源,输入框的值由 state 决定,用户输入时通过事件更新 state。这一篇讲透事件和表单。


一、事件处理:从 @clickonClick

vue 复制代码
<!-- Vue3 -->
<button @click="handleClick">点我</button>
<button @click="count++">+1</button>
<input @input="onInput" />
jsx 复制代码
// React:驼峰命名 + 传函数引用
<button onClick={handleClick}>点我</button>
<button onClick={() => setCount(count + 1)}>+1</button>
<input onChange={onInput} />

要点:

  • 事件名驼峰onClickonChangeonInputonSubmitonMouseEnter......
  • 传的是函数引用onClick={handleClick}),不是函数调用(onClick={handleClick()} ❌ 会立即执行);
  • 需要传参时用箭头函数包一层:onClick={() => handleDelete(id)}

事件对象

jsx 复制代码
function Demo() {
  const handleClick = (e) => {
    e.preventDefault()   // 阻止默认行为,对标 Vue 的 .prevent
    e.stopPropagation()  // 阻止冒泡,对标 .stop
    console.log(e.target)
  }
  return <a href="#" onClick={handleClick}>链接</a>
}

事件修饰符去哪了?

Vue 有 .prevent.stop.once.enter 等修饰符。React 没有,全部手写

Vue3 React
@click.prevent 在处理函数里 e.preventDefault()
@click.stop 在处理函数里 e.stopPropagation()
@keyup.enter="fn" onKeyUp={e => e.key === 'Enter' && fn()}
@click.once 自己用状态标志控制

二、受控组件:理解 React 的表单哲学

Vue 的 v-model 本质是"值绑定 + 事件监听"的语法糖。React 让你显式地写出这两半:

vue 复制代码
<!-- Vue3 -->
<input v-model="name" />
jsx 复制代码
// React:这就是 v-model 的"拆解版"
<input
  value={name}                              // 值来自 state
  onChange={e => setName(e.target.value)}   // 输入时更新 state
/>

这就叫受控组件 :输入框显示什么,完全由 state name 决定;用户敲键盘触发 onChange,我们更新 state,state 变了组件重渲染,输入框显示新值。数据流是单向闭环的------这正是 React 的核心理念。

复制代码
用户输入 → onChange → setState → 重渲染 → value 更新 → 显示新值

一开始会觉得比 v-model 啰嗦,但好处是:任何时候 state 就是表单的唯一真相,校验、格式化、联动都极其直观。


三、各种表单元素

文本框 / 文本域

jsx 复制代码
<input value={text} onChange={e => setText(e.target.value)} />
<textarea value={desc} onChange={e => setDesc(e.target.value)} />

复选框(用 checked 而非 value

jsx 复制代码
<input
  type="checkbox"
  checked={agreed}
  onChange={e => setAgreed(e.target.checked)}
/>

单选框

jsx 复制代码
<label>
  <input type="radio" value="male" checked={gender === 'male'}
         onChange={e => setGender(e.target.value)} /> 男
</label>
<label>
  <input type="radio" value="female" checked={gender === 'female'}
         onChange={e => setGender(e.target.value)} /> 女
</label>

下拉选择(注意和 Vue 不同)

Vue 在 <option> 上写 selected,React 则把 value 放在 <select> 上:

jsx 复制代码
<select value={city} onChange={e => setCity(e.target.value)}>
  <option value="bj">北京</option>
  <option value="sh">上海</option>
</select>

四、多字段表单:一个 handler 搞定

字段多时,别写一堆 setter。用一个对象 state + 通用 handler(靠 name 属性区分):

jsx 复制代码
import { useState } from 'react'

function SignupForm() {
  const [form, setForm] = useState({ username: '', email: '', password: '' })

  // 通用 handler:根据 input 的 name 更新对应字段
  const handleChange = (e) => {
    const { name, value } = e.target
    setForm(prev => ({ ...prev, [name]: value }))  // 计算属性名 + 不可变更新
  }

  const handleSubmit = (e) => {
    e.preventDefault()           // 阻止表单默认刷新页面
    console.log('提交', form)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="username" value={form.username} onChange={handleChange} placeholder="用户名" />
      <input name="email"    value={form.email}    onChange={handleChange} placeholder="邮箱" />
      <input name="password" type="password" value={form.password} onChange={handleChange} placeholder="密码" />
      <button type="submit">注册</button>
    </form>
  )
}

注意 [name]: value 这个计算属性名语法------它让一个 handler 能更新任意字段,是 React 表单的常用技巧。


五、表单提交

<form onSubmit>e.preventDefault()(阻止浏览器默认刷新),比给按钮绑 click 更规范(支持回车提交):

jsx 复制代码
<form onSubmit={handleSubmit}>
  {/* ... */}
  <button type="submit">提交</button>
</form>

六、非受控组件与 useRef(偶尔用)

如果不想为每个输入维护 state(比如简单表单、或对接第三方 DOM 库),可以用非受控组件 ,通过 ref 在提交时直接读 DOM 值。这类似 Vue 里用模板 ref 拿 DOM:

jsx 复制代码
import { useRef } from 'react'

function SearchBox() {
  const inputRef = useRef(null)

  const handleSearch = () => {
    console.log(inputRef.current.value)  // 直接读 DOM 的值
  }

  return (
    <>
      <input ref={inputRef} defaultValue="" />  {/* 用 defaultValue 而非 value */}
      <button onClick={handleSearch}>搜索</button>
    </>
  )
}

受控 vs 非受控:默认优先用受控组件(state 驱动,可控性强);只有在性能敏感或对接命令式 DOM 库时才用非受控。


七、React 19 表单新特性(了解)

React 19 引入了 useActionState<form action={fn}>useFormStatus 等,能进一步简化表单提交与 pending 状态处理,减少手写 useState。等你熟悉了受控组件基础后,可以去了解这些新 API------它们让表单代码更接近"声明式"。


八、对照速查

场景 Vue3 React
点击事件 @click="fn" onClick={fn}
传参 @click="fn(id)" onClick={() => fn(id)}
阻止默认 @click.prevent e.preventDefault()
阻止冒泡 @click.stop e.stopPropagation()
回车触发 @keyup.enter onKeyUp={e => e.key==='Enter' && fn()}
文本双向绑定 v-model value + onChange(受控)
复选框 v-model checked + onChange
下拉 v-model on select value on <select>
直接读 DOM 值 模板 ref useRef + .current.value

小结

  • 事件用驼峰 + 函数引用;传参包箭头函数;修饰符全部手写。
  • React 没有 v-model,用受控组件value + onChange 手动闭环,state 是唯一真相。
  • 复选框用 checked,下拉的 value 放在 <select> 上。
  • 多字段表单用一个对象 state + [name]: value 通用 handler。
  • 简单场景可用 useRef 做非受控组件,但默认优先受控。

下一篇讲状态管理 useState 与 useReducer

相关推荐
大猫会长2 小时前
获取favicon.ico的方法
前端·javascript·html
默_笙3 小时前
🎀 大模型说"呀"还是"吗"?temperature 和 Top K 在偷偷控制它的"性格"
前端·javascript
渣波3 小时前
别再死磕MySQL了!用Milvus+LangChain给AI加个“海马体”,手把手带你写个RAG日记本
前端
乌夷3 小时前
vue知识点
前端·javascript·vue.js
何时梦醒3 小时前
🚀 从零上手 Milvus 向量数据库:打造一个 AI 智能日记本
前端·人工智能
blns_yxl3 小时前
拖拽排序列表(HTML+JS)
前端·javascript·html
Data3 小时前
把 React 前端做成「插件市场」:前后端联动的行业包可插拔架构
前端
光影少年3 小时前
RN Bridge 原理
前端·react native·react.js
moMo3 小时前
用 React 写一个 TodoList
react native·react.js