核心概念
简单来说,区别就在于"谁负责存储并管理表单数据"。
1. 受控组件
在受控组件中,React 组件的 state 是表单数据的"唯一真相源"。
-
如何工作 :你需要为每个表单元素(如
input
,textarea
,select
)设置两个属性:value
:将其设置为 React state 中的一个值。onChange
:设置一个事件处理函数,当用户输入时,用事件对象(e.target.value
)中的新值来更新 state。
-
数据流 :数据流是单向的 。
state -> value属性 -> 输入框显示的值
用户输入 ->onChange
事件 -> 更新 state -> 重新渲染 -> 输入框显示新的值。 -
代码示例:
jsximport { useState } from 'react'; function ControlledComponent() { const [inputValue, setInputValue] = useState(''); const handleChange = (event) => { setInputValue(event.target.value); }; const handleSubmit = (event) => { event.preventDefault(); alert('提交的名字是: ' + inputValue); }; return ( <form onSubmit={handleSubmit}> <label> 名字: <input type="text" value={inputValue} // input 的值由 state 控制 onChange={handleChange} // 一旦输入就更新 state /> </label> <button type="submit">提交</button> </form> ); }
-
优点:
- 即时验证/处理 :你可以直接在
onChange
处理函数中对用户的每一次输入进行操作,比如实时验证格式、即时搜索提示、禁用按钮等。 - 完全控制:你可以以编程方式改变输入框的值(例如,通过重置表单的 state 来清空所有输入框)。
- 符合 React 设计哲学:数据流清晰,易于理解和调试。
- 即时验证/处理 :你可以直接在
-
缺点:
- 代码量稍多:需要为每个字段编写事件处理函数。
- 潜在性能问题:如果表单非常庞大,每次击键都可能导致重新渲染,但通常对于现代浏览器和 React 的优化来说,这很少成为实际问题。
2. 非受控组件
在非受控组件中,表单数据由 DOM 本身处理 。你不需要用 state 来存储每个输入的值,而是使用 ref
来在需要时(例如提交表单时)从 DOM 节点中获取值。
-
如何工作 :你不为
input
设置value
属性(或设置为undefined
)。你给input
一个ref
,然后通过ref.current.value
来访问它的值。 -
数据流 :数据流是传统式的,用户输入直接显示在输入框里,React 不干预这个过程,只在需要时去"拉取"数据。
-
代码示例:
jsximport { useRef } from 'react'; function UncontrolledComponent() { const inputRef = useRef(null); const handleSubmit = (event) => { event.preventDefault(); // 在提交时,通过 ref 获取 DOM 元素的值 alert('提交的名字是: ' + inputRef.current.value); }; return ( <form onSubmit={handleSubmit}> <label> 名字: <input type="text" ref={inputRef} // 将 ref 关联到 input 元素 // 没有 value 和 onChange 属性 /> </label> <button type="submit">提交</button> </form> ); }
-
默认值 :如果你想给输入框一个初始值,可以使用
defaultValue
属性(对于复选框则是defaultChecked
),而不是value
。jsx<input type="text" defaultValue="Hello" ref={inputRef} />
-
优点:
- 代码简单:对于简单的表单,代码更少,更接近原生 HTML。
- 性能:对于巨型表单,可能因为更少的 state 更新和重新渲染而有一丝性能优势(但通常不明显)。
-
缺点:
- 即时反馈困难:难以实现实时验证、条件禁用等需要根据当前输入值动态变化的 UI。
- 不够"React":数据流不清晰,需要直接操作 DOM,这与 React 声明式的理念有些背道而驰。
对比总结
特性 | 受控组件 | 非受控组件 |
---|---|---|
数据管理 | React state | DOM 自身 |
推荐度 | 官方推荐,更常用 | 在特定场景下使用 |
值控制 | value 属性 |
ref |
事件处理 | 必需 (onChange 等) |
可选 |
实时操作 | 容易(验证、禁用等) | 困难 |
表单提交 | 从 state 中获取数据 | 从 ref 中获取数据 |
默认值 | 通过 state 设置(如 useState('') ) |
使用 defaultValue 属性 |
如何选择?
-
大多数情况下,请使用受控组件。这是 React 的标准实践,让你对数据有完全的控制权,能轻松实现各种交互逻辑。
-
在以下情况,可以考虑非受控组件:
- 一次性获取:你只需要在提交时获取一下值,中间过程不需要任何验证或控制。
- 集成非 React 库 :当你需要集成一个第三方库(如 jQuery 插件),而这个库需要直接操作 DOM 时,使用
ref
是必要的。 - 文件输入 :
<input type="file">
始终是一个非受控组件,因为它的值是只读的,只能由用户设置,而不能通过编程方式设置。 - 性能极端敏感:面对一个包含大量输入字段的表单(如数据表格),每次击键都更新 state 可能导致性能问题,这时非受控组件可能是一个优化选择。
最终建议:默认优先考虑受控组件。除非你有非常明确的理由(如上所述)要使用非受控组件,否则坚持使用受控组件会让你的应用更可控、更健壮。