4、React状态管理:useState Hook、状态提升、单向数据流、受控组件与非受控组件
好,咱们今天聊聊React的状态管理。说实话,这块是很多新手最头疼的地方。我刚学React那会儿,也被各种概念绕得晕头转向。但别怕,咱们一个一个拆开讲。
4.1 useState Hook:最基础的状态管理
先说说 useState。这是React里最常用的Hook,没有之一。它的作用很简单——让函数组件拥有自己的状态。
看个例子:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>你点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>点我</button>
</div>
);
}
这里 useState(0) 返回一个数组。第一个值是当前状态,第二个是更新状态的函数。我个人习惯用解构赋值来拿这两个值,代码看起来更清爽。
setCount(count + 1),结果可能不是你想的那样。我建议用函数式更新:setCount(prev => prev + 1)。
4.2 状态提升:让兄弟组件共享数据
你想想看,如果两个兄弟组件需要共享同一个状态,怎么办?
答案就是——状态提升。把共享的状态放到它们共同的父组件里,然后通过props传下去。
我在项目中遇到过这样一个场景:一个搜索框和一个搜索结果列表,它们需要共享搜索关键词。我一开始把状态分别放在两个组件里,结果数据不同步,改起来特别痛苦。后来把状态提升到父组件,问题就解决了。
function Parent() {
const [keyword, setKeyword] = useState('');
return (
<div>
<SearchBox keyword={keyword} onSearch={setKeyword} />
<ResultList keyword={keyword} />
</div>
);
}
这样,keyword 状态只存在于 Parent 中,两个子组件通过props获取或修改它。数据流清晰,维护起来也方便。
4.3 单向数据流:React的核心理念
React的数据流是单向的。什么意思?说白了,数据从父组件流向子组件,子组件不能直接修改父组件的状态。
为什么这样设计?我刚开始也觉得麻烦,但后来发现,单向数据流让数据变化变得可预测。你想想看,如果子组件能随便改父组件的状态,那调试起来得多崩溃?
举个例子:
// 父组件
function App() {
const [name, setName] = useState('张三');
return <Child name={name} onChangeName={setName} />;
}
// 子组件
function Child({ name, onChangeName }) {
return (
<div>
<p>我的名字是:{name}</p>
<button onClick={() => onChangeName('李四')}>改名</button>
</div>
);
}
子组件通过 onChangeName 回调来通知父组件修改状态,而不是直接改。这就是单向数据流的精髓。
4.4 受控组件与非受控组件
这块是表单处理的核心。很多新手搞不清楚,其实很简单。
受控组件
受控组件,就是表单元素的值由React状态控制。每次输入变化,都会触发 onChange 事件,更新状态,然后React重新渲染,把新值赋给表单元素。
function ControlledForm() {
const [inputValue, setInputValue] = useState('');
return (
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
);
}
这样做的好处是,你可以随时获取表单的值,做校验、格式化等操作。我习惯用受控组件,因为数据完全在React掌控之中。
非受控组件
非受控组件,就是表单元素的值由DOM自己管理。你通过 ref 来获取它的值。
function UncontrolledForm() {
const inputRef = useRef(null);
const handleSubmit = () => {
alert(inputRef.current.value);
};
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={handleSubmit}>提交</button>
</div>
);
}
非受控组件适合那些你不需要实时监控值的场景,比如文件上传。我曾经在一个旧项目里用过非受控组件,因为表单字段太多,全部受控的话性能有点扛不住。
4.5 实战中的选择建议
说了这么多,到底怎么选?我总结一下:
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 简单表单(1-3个字段) | 受控组件 | 数据可控,方便校验 |
| 复杂表单(大量字段) | 受控组件 + 状态管理库 | 避免props层层传递 |
| 文件上传 | 非受控组件 | 文件对象由DOM管理更合适 |
| 第三方UI库集成 | 视情况而定 | 有些库强制要求受控或非受控 |
value 设成了 undefined,结果输入框变成了非受控模式,导致数据不同步。嗯,这里要注意:value 要么不传,要么传一个有效的值,别传 undefined。
好了,这一章的内容就这些。记住,状态管理是React的基石,多练练,慢慢就熟练了。下一章咱们聊聊React的副作用处理——useEffect,那又是一个让人又爱又恨的东西。