本文来自于光神的文章,搬到这里方便自己复习学习,没其他作用。
前端开发经常会涉及表单的处理,或者其他一些用于输入的组件。涉及到输入,就绕不开受控模式和非受控模式的概念。
什么是受控,什么是非受控呢?想一下,改变表单值只有两种情况:

用户去改变 value 或者代码去改变 value。
如果不能通过代码改表单值 value,那就是非受控,也就是不受我们控制。但是代码可以给表单设置初始值 defaultValue。

代码设置表单的初始 value,但是能改变 value 的只有用户,代码通过监听 onChange 来拿到最新的值,或者通过 ref 拿到 dom 之后读取 value。
这种就是非受控模式。
反过来,代码可以改变表单的 value,就是受控模式。

注意,value 和 defaultValue 不一样:
defaultValue 会作为 value 的初始值,后面用户改变的是 value。
而一旦你给 input 设置了 value,那用户就不能修改它了,可以输入触发 onChange 事件,但是表单的值不会变。
用户输入之后在 onChange 事件里拿到输入,然后通过代码去设置 value。
这就是受控模式。
其实绝大多数情况下,非受控就可以了,因为我们只是要拿到用户的输入,不需要手动去修改表单值。
但有的时候,你需要根据用户的输入做一些处理,然后设置为表单的值,这种就需要受控模式。
或者你想同步表单的值到另一个地方的时候,类似 Form 组件,也可以用受控模式。
value 由用户控制就是非受控模式,由代码控制就是受控模式。
下面看一下非受控组件:
js
const [checked, setChecked] = useState(true)
<div>
<label htmlFor="checkbox">checkbox</label>
<input
type="checkbox"
id="checkbox"
value="beijing"
defaultChecked={checked}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
console.log('e.target.checked', e.target.checked)
console.log('e.target.value', e.target.value)
}}
/>
</div>
此时,我只是设置默认值defaultChecked,所以如果我要获取这个选中状态,需要通过onChange函数获取。
如果我加上checked={checked},不加setChecked(e.target.checked)的话:
js
<input
type="checkbox"
id="checkbox"
value="beijing"
checked={checked}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
console.log('e.target.checked', e.target.checked)
console.log('e.target.value', e.target.value)
}}
/>
此时,点击checkbox不会有任何变化,必须要使用setChecked(e.target.checked)进行更新。
这就是受控组件。
虽然功能上差不多,但这种写法并不推荐:

你不让用户自己控制,而是通过代码控制,绕了一圈结果也没改 value 的值,还是原封不动的,图啥呢?
而且受控模式每次 setValue 都会导致组件重新渲染。试一下:

每次输入都会 setValue,然后触发组件重新渲染,而非受控模式下只会渲染一次。
绕了一圈啥也没改,还导致很多组件的重新渲染,那你用受控模式图啥呢?
那什么情况用受控模式呢?
当然是你需要对输入的值做处理之后设置到表单的时候,或者是你想实时同步状态值到父组件。
比如把用户输入改为大写:
js
import { ChangeEvent, useState } from "react"
function App() {
const [value, setValue] = useState('guang');
function onChange(event: ChangeEvent<HTMLInputElement>) {
console.log(event.target.value)
setValue(event.target.value.toUpperCase());
}
return <input value={value} onChange={onChange}/>
}
export default App
这种,需要把用户的输入修改一下再设置 value 的。
但这种场景其实很少。
有的同学可能会说 Form 组件,确实,用 Form.Item 包裹的表单项都是受控组件:

确实,那是因为 Form 组件内有一个 Store,它需要把表单值同步到store里面,然后集中管理和设置值,比如有个getFieldsValue函数获取所有表单的值。
但也因为都是受控组件,随着用户的输入,表单重新渲染很多次,性能会不好。
如果是单独用的组件,比如 Calendar,那就没必要用受控模式了,用非受控模式,设置 defaultValue 就可以了。
很多人上来就设置 value,然后监听 onChange,但是绕了一圈又原封不动的把用户输入转为 value。
没啥意义,还平白导致组件的很多次重新渲染。
除了原生表单元素外,组件也需要考虑受控和非受控的情况。
比如日历组件:

它的参数就要考虑是支持非受控模式的 defaultValue,还是用受控模式的 value + onChange。
如果这是一个业务组件,那基本就是用非受控模式的 defaultValue 了,调用方只要拿到用户的输入就行。
用受控模式的 value 还要 setValue 触发额外的渲染。
但是基础组件不能这样,你得都支持,让调用者自己去选择。
ant design 的 Calendar 组件就是这样的:
