受控表单与 v-model
React 显式连接 value 和事件处理函数;Vue 使用 v-model 描述表单值的双向绑定。
核心结论
两边都可以让组件状态成为表单的事实来源。React 显式写出 value → onChange → setter;Vue 的 v-model 是属性绑定与更新事件的语法糖。
表单数据流
组件关系与数据流
Form StateComponentBindingDOM Control
差异焦点读取、更新和回写都是显式代码,state 是唯一数据源。
实线表示依赖、数据或命令传递,虚线表示事件、回调或清理路径。
flowchart TB
accTitle: React 受控表单数据流
accDescr: input 事件读取 DOM value,onChange 调用 setter 更新 state,组件重新渲染后再通过 value prop 把状态写回表单控件。
input[("input / select DOM")] -.->|"onChange event"| handler{{"读取 event.target.value"}}
handler --> setter{{"调用 setter"}}
setter --> state(["name / direction state"])
state --> demo["ReactDemo render"]
demo -->|"value prop"| input
class state state
class demo component
class handler,setter process
class input resource
classDef state fill:#fffdf8,stroke:#149eca,color:#25221d,stroke-width:1.2px
classDef component fill:#e4f5fa,stroke:#149eca,color:#25221d,stroke-width:2px,font-weight:600
classDef process fill:#f4fafb,stroke:#149eca,color:#25221d,stroke-width:1.6px
classDef resource fill:#eee9df,stroke:#817a70,color:#25221d,stroke-width:1.4px事件把 DOM 值提交给 state,下一次 render 再用 value prop 控制表单。
组件关系与数据流
Form StateComponentBindingDOM Control
差异焦点模板只声明绑定目标,事件读取和属性回写由编译结果完成。
实线表示依赖、数据或命令传递,虚线表示事件、回调或清理路径。
flowchart TB
accTitle: Vue v-model 表单双向绑定
accDescr: v-model 编译为表单属性绑定和更新事件,用户输入时写入 ref,响应式更新又把最新值同步到表单与预览模板。
input[("input / select DOM")] <--> model{{"v-model 编译绑定"}}
model <--> state(["name / direction refs"])
state --> trigger{{"响应式依赖更新"}}
trigger --> template["表单 + 预览模板"]
class state state
class template component
class model,trigger process
class input resource
classDef state fill:#fffdf8,stroke:#2f9d6b,color:#25221d,stroke-width:1.2px
classDef component fill:#e4f4eb,stroke:#2f9d6b,color:#25221d,stroke-width:2px,font-weight:600
classDef process fill:#f2f8f4,stroke:#2f9d6b,color:#25221d,stroke-width:1.6px
classDef resource fill:#eee9df,stroke:#817a70,color:#25221d,stroke-width:1.4pxv-model 组合属性绑定和更新事件,把 DOM 控件与响应式 ref 连接起来。
完整 Demo 源码
以下代码直接读取在线 Demo 使用的源文件,没有省略或改写。
React 源码
import { useState } from "react";import type { SubmitEvent } from "react";export function ReactDemo() { const [name, setName] = useState("小明"); const [direction, setDirection] = useState("React"); const [submitted, setSubmitted] = useState(0); function submit(event: SubmitEvent<HTMLFormElement>) { event.preventDefault(); setSubmitted((current) => current + 1); } return ( <div className="demo-stack"> <form className="demo-form" onSubmit={submit}> <label className="demo-field"> 姓名 <input className="demo-input" value={name} onChange={(event) => setName(event.target.value)} /> </label> <label className="demo-field"> 学习方向 <select className="demo-select" value={direction} onChange={(event) => setDirection(event.target.value)} > <option>React</option> <option>Vue</option> <option>两者对比</option> </select> </label> <button className="demo-button" type="submit"> 保存资料 </button> </form> <div className="demo-card"> <span className="demo-tag">实时 state</span> <h2>{name || "未填写姓名"}</h2> <p>正在学习:{direction}</p> <p>已保存:{submitted} 次</p> </div> </div> );}Vue 3 源码
<script setup lang="ts">import { ref } from "vue";const name = ref("小明");const direction = ref("Vue");const submitted = ref(0);function submit() { submitted.value += 1;}</script><template> <div class="demo-stack"> <form class="demo-form" @submit.prevent="submit"> <label class="demo-field"> 姓名 <input v-model="name" class="demo-input" /> </label> <label class="demo-field"> 学习方向 <select v-model="direction" class="demo-select"> <option>React</option> <option>Vue</option> <option>两者对比</option> </select> </label> <button class="demo-button" type="submit">保存资料</button> </form> <div class="demo-card"> <span class="demo-tag">实时 ref</span> <h2>{{ name || "未填写姓名" }}</h2> <p>正在学习:{{ direction }}</p> <p>已保存:{{ submitted }} 次</p> </div> </div></template>在线观察
两个表单都实时展示姓名和学习方向。
React
实时 state
小明
正在学习:React
已保存:0 次
Vue 3
实时 ref
小明
正在学习:Vue
已保存:0 次
常见边界
- React 输入框在受控与非受控模式之间切换会产生警告;初始值应保持类型稳定。
v-model不是不可解释的“魔法双向绑定”,组件上会展开为 prop 与update:*事件。- 表单值转换应放在输入边界,避免业务层同时处理字符串、数字和空值。
- 大型 React 表单可用 React Hook Form 减少整棵表单的受控更新,见表单校验实践。