React官方的文档和社区中对于受控组件
和非受控组件
均有相关的文章进行讲解,作为React新手老实讲还是花了些时间去理解和消化。
本文从一个新手的学习和实践的角度来试图对其进行对比,并通过hooks代码进行相应地说明。
为何有受控/非受控的区别
在HTML标签中,有一类特殊的dom标签,表单类标签,比如input
,textarea
,select
等。这类标签自身具备状态,并且自身会维护自己的状态
。当开发者使用React来开发表单组件时,会有相应的一套状态管理机制,此时如何将React的状态和表单组件的状态统一到一起就涉及到对表单组件状态的管控。
所谓受控(controlled)与非受控(uncontrolled),其核心的区别在于组件的状态是否可以通过代码控制
。顾名思义,受控是状态受代码控制,相反则是状态不受代码控制。
受控组件
下面通过简单的示例代码来展示受控组件中对于状态的管控
input
组件
import {useState} from 'react'
export default function Input() {
const [value, setValue] = useState('')
const handleChange = (e: any) => {
setValue(e.target.value)
}
const handleSubmit = (e: any) => {
e.preventDefault()
// 消费状态 value
console.log(value)
}
return (
<form onSubmit={handleSubmit}>
<label>
名字:
<input type="text" value={value} onChange={handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
textarea
组件
与input些许不同的是,textarea通过子元素定义其文本(状态),代码示例如下
<textarea>我是textarea的状态</textarea>
在React中,相应的状态管控则如下代码所示
import {useState} from 'react'
export default function Textarea() {
const [value, setValue] = useState('我是textarea的状态')
const handleChange = (e: any) => {
setValue(e.target.value)
}
const handleSubmit = (e: any) => {
e.preventDefault()
// 消费状态 value
console.log(value)
}
return (
<form onSubmit={handleSubmit}>
<label>
精彩的文案:
<textarea value={value} onChange={handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
从受控代码逻辑来看,input和textarea在状态管控上几乎一模一样,组件的状态均交由上层的React控制组件进行读取和设置。
select
组件
先看看html中的表现,以三个颜色的列表项选择为例,简单的代码如下:
<select>
<option value="red">红色</option>
<option value="blue">蓝色</option>
<option selected value="yellow">黄色</option>
</select>
select
组件在options
中被selected
的项的value
是组件的状态值,如果使用React对该状态进行控制的话,同样是修改select
组件的value
即可。
import {useState} from 'react'
interface SelectProps {
list: { value: string, text: string }[]
}
export default function Select(props: SelectProps) {
const { list } = props
// 默认选中第2项
const [value, setValue] = useState(list[1].value)
const handleChange = (e: any) => {
setValue(e.target.value)
}
const handleSubmit = (e: any) => {
e.preventDefault()
// 消费状态 value
console.log(value)
}
return (
<form onSubmit={handleSubmit}>
<label>
请选择颜色:
<select onChange={handleChange} defaultValue={value}>
{list.map(item => (<option key={item.value} value={item.value}>{item.text}</option>))}
</select>
</label>
<input type="submit" value="提交" />
</form>
);
}
与input
和textarea
有个地方不同,设置初始状态时,可以通过defaultValue
来触发相应的UI渲染(selected状态
)。
非受控组件
从上面的介绍和示例可以看出,受控组件要求开发者接管所有与状态读取和设置逻辑,否则在状态的使用上就可能出现不一致的错误。
非受控组件的场景相应的就比较容易理解了,无外乎以下两种场景
- 组件状态外部只读,程序无法修改
- 组件状态维护逻辑复杂,开发者期望组件内部自维护状态
那如何实现对非受控组件的状态使用呢?同样参考官方的案例,实现对文件选择input
的封装,代码如下:
import {useRef} from 'react'
export default function FileInput() {
const fileInputRef = useRef<HTMLInputElement>(null)
const handleSubmit = (e: any) => {
e.preventDefault()
// 消费状态 value
console.log(fileInputRef.current?.value)
}
return (
<form onSubmit={handleSubmit}>
<label>
请选择文件:
<input type="file" ref={fileInputRef} />
</label>
<input type="submit" value="提交" />
</form>
);
}
在上述的示例代码中,类型为file的input组件,它的状态只能由用户操作产生,不受程序代码的控制
,这是非常典型的非受控组件的应用场景。
非受控组件中的关键就是通过ref保留目标组件的引用
,通过组件的引用获取组件内部的状态,而组件内部的状态对外部是透明的。
小结
本文根据官方的示例,使用hooks的方式对示例代码进行重写,代码会更易于阅读理解,同时从受控组件和非受控组件各自的特点和使用场景进行相应地讲解,总结为下面几点。
-
区别
-
受控组件
:将状态交给组件外部管理,包括状态的设置和使用,外部程序需要接管一切与状态相关的逻辑,保持状态的一致性 -
非受控组件
:在内部自行管理状态,对外部透明,外部程序可通过组件的引用使用组件的状态
-
-
场景
-
受控组件
:使状态在程序中保持一致性使用 -
非受控组件
:- 不能。无法修改组件内部状态
- 不想。如保持一致性的成本过高时,只通过引用使用组件状态
-