4、React状态管理入门:useState Hook详解、状态提升与共享、受控组件与非受控组件、表单处理实战
好,咱们今天聊聊React里的状态管理。说实话,很多新手刚接触React时,最困惑的就是「状态」到底是个什么东西。我当年也一样,看着官方文档里那个计数器例子,心里想:这不就是个变量吗?搞这么复杂干嘛?
后来我才明白,状态和普通变量的区别,就在于它跟UI是绑定的。你改了状态,React会自动帮你更新视图。嗯,这才是核心。
4.1 useState Hook:最基础的状态管理
先看个最简单的例子。咱们用函数组件加Hook的方式,写一个计数器:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>当前计数:{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
这里有几个关键点,我跟你掰扯清楚:
- useState(0):括号里的0是初始值。可以是数字、字符串、对象、数组,啥都行。
- 返回值是个数组:第一个是当前状态值,第二个是更新函数。用解构赋值拿过来用。
- setCount:调用它,React会重新渲染组件,并且把新值赋给count。
但有个坑,我踩过好几次。你想想看,如果连续调用两次setCount会怎样?
function handleClick() {
setCount(count + 1);
setCount(count + 1);
}
结果不是加2,而是加1。为什么?因为React的setState是异步的,两次调用拿到的count是同一个值。正确的做法是用函数式更新:
function handleClick() {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
}
这样每次都能拿到最新的值,结果就是加2。嗯,这个细节很重要。
4.2 状态提升:兄弟组件怎么共享数据?
实际项目中,你经常会遇到这种情况:两个兄弟组件需要共享同一个状态。比如一个输入框组件和一个展示组件,输入的内容要实时显示。
React的哲学是「单向数据流」。子组件不能直接改父组件的状态,但父组件可以把状态和更新函数一起传给子组件。这就是状态提升。
看个例子:
function Parent() {
const [text, setText] = useState('');
return (
<div>
<InputBox value={text} onChange={setText} />
<DisplayBox text={text} />
</div>
);
}
function InputBox({ value, onChange }) {
return (
<input
value={value}
onChange={(e) => onChange(e.target.value)}
/>
);
}
function DisplayBox({ text }) {
return <p>你输入的是:{text}</p>;
}
你看,状态text定义在Parent里,通过props传给两个子组件。InputBox负责改,DisplayBox负责读。数据流清晰,谁都不越界。
4.3 受控组件 vs 非受控组件
这两个概念,说白了就是「表单数据归谁管」的问题。
受控组件:表单数据由React状态控制。你输入一个字,触发onChange,更新state,state再回流到value里。整个过程React全程参与。
function ControlledInput() {
const [name, setName] = useState('');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}
非受控组件:表单数据由DOM自己管理。你想取值的时候,用ref去拿。
function UncontrolledInput() {
const inputRef = useRef(null);
function handleSubmit() {
alert(inputRef.current.value);
}
return (
<div>
<input ref={inputRef} defaultValue="默认值" />
<button onClick={handleSubmit}>提交</button>
</div>
);
}
| 对比项 | 受控组件 | 非受控组件 |
|---|---|---|
| 数据来源 | React state | DOM自身 |
| 取值方式 | 直接从state读 | 通过ref获取 |
| 适用场景 | 需要实时校验、联动、格式化 | 简单表单、文件上传 |
| 代码量 | 稍多 | 较少 |
4.4 表单处理实战:一个完整的注册表单
理论说完了,咱们来点实战。写一个注册表单,包含用户名、密码、确认密码、性别选择、兴趣爱好。
function RegisterForm() {
const [formData, setFormData] = useState({
username: '',
password: '',
confirmPassword: '',
gender: 'male',
hobbies: []
});
const [errors, setErrors] = useState({});
function handleChange(e) {
const { name, value, type, checked } = e.target;
if (type === 'checkbox') {
// 处理多选框
const updatedHobbies = checked
? [...formData.hobbies, value]
: formData.hobbies.filter(h => h !== value);
setFormData(prev => ({ ...prev, hobbies: updatedHobbies }));
} else {
setFormData(prev => ({ ...prev, [name]: value }));
}
}
function validate() {
const newErrors = {};
if (!formData.username.trim()) {
newErrors.username = '用户名不能为空';
}
if (formData.password.length < 6) {
newErrors.password = '密码至少6位';
}
if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = '两次密码不一致';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
}
function handleSubmit(e) {
e.preventDefault();
if (validate()) {
console.log('提交数据:', formData);
// 这里调用API
}
}
return (
<form onSubmit={handleSubmit}>
<div>
<label>用户名:</label>
<input
name="username"
value={formData.username}
onChange={handleChange}
/>
{errors.username && <span style={{color: 'red'}}>{errors.username}</span>}
</div>
<div>
<label>密码:</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
{errors.password && <span style={{color: 'red'}}>{errors.password}</span>}
</div>
<div>
<label>确认密码:</label>
<input
type="password"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
/>
{errors.confirmPassword && <span style={{color: 'red'}}>{errors.confirmPassword}</span>}
</div>
<div>
<label>性别:</label>
<select name="gender" value={formData.gender} onChange={handleChange}>
<option value="male">男</option>
<option value="female">女</option>
</select>
</div>
<div>
<label>兴趣爱好:</label>
<label>
<input
type="checkbox"
value="reading"
checked={formData.hobbies.includes('reading')}
onChange={handleChange}
/> 阅读
</label>
<label>
<input
type="checkbox"
value="coding"
checked={formData.hobbies.includes('coding')}
onChange={handleChange}
/> 编程
</label>
</div>
<button type="submit">注册</button>
</form>
);
}
这个例子涵盖了大部分表单场景。有几个点我想强调一下:
- 统一的事件处理函数:通过name属性区分不同字段,避免写一堆重复的onChange。
- 复选框的特殊处理:因为是多选,需要维护一个数组,选中时添加,取消时移除。
- 表单验证:提交前校验,错误信息存在errors对象里,实时展示。
好了,这一章的内容就到这儿。useState是React状态管理的基石,状态提升是组件通信的常用手段,受控组件是React表单的核心模式。把这些搞明白,后面学useReducer、Context、Redux就会轻松很多。
下一章,咱们聊聊useEffect和副作用处理。到时候见。