在现代 React 开发中,函数组件已不再是"无状态"的代名词。借助 Hooks ------以 use 开头的一系列内置函数,开发者可以在不编写类的前提下,轻松管理组件的状态、执行副作用、订阅外部数据源,甚至自定义逻辑复用机制。这一设计不仅让代码更贴近原生 JavaScript 的表达习惯,也推动了组件逻辑的清晰化与模块化。
useState:声明响应式状态
useState 是最基础的 Hook,用于在函数组件中引入可变状态:
scss
const [num, setNum] = useState(0);
它返回一个包含当前状态值和更新函数的数组。值得注意的是,useState 的初始值可以是一个函数,适用于需要复杂同步计算的场景:
ini
const [num, setNum] = useState(() => {
const a = 1 + 2;
const b = 2 + 3;
return a + b; // 返回确定的初始值
});
这种形式确保初始化逻辑仅在组件首次渲染时执行一次,避免不必要的重复计算。但需注意:该函数必须是同步且纯的 ,不能包含异步操作(如 fetch),因为状态必须在渲染前确定。
此外,setNum 不仅能接收新值,还可接受一个函数,其参数为上一次的状态:
scss
<button onClick={() => setNum(prev => prev + 1)}>
{num}
</button>
当状态更新依赖于前一状态时(如计数器、列表追加),使用函数式更新能避免因闭包捕获旧值而导致的竞态问题,确保状态演进的正确性。
useEffect:统一处理副作用
如果说 useState 负责"记忆",那么 useEffect 就负责"行动"。它用于执行副作用操作------即那些不影响组件渲染结果但必须发生的逻辑,如数据请求、定时器、DOM 操作等。
scss
useEffect(() => {
console.log('组件挂载完成');
}, []);
通过传入空依赖数组 [],该副作用仅在组件首次挂载后执行一次 ,等效于类组件中的 componentDidMount。
当依赖项变化时,useEffect 会重新运行:
javascript
useEffect(() => {
console.log(`num 变为 ${num}`);
}, [num]);
这类似于 componentDidUpdate,可用于监听特定状态或 props 的变化并作出响应。
清理副作用:防止内存泄漏
许多副作用需要在组件卸载或重新执行前进行清理,例如清除定时器、取消网络请求、移除事件监听器等。useEffect 支持返回一个清理函数:
javascript
useEffect(() => {
const timer = setInterval(() => {
console.log(num);
}, 1000);
return () => {
console.log('清除定时器');
clearInterval(timer);
};
}, [num]);
该清理函数会在以下两种情况下被调用:
- 组件卸载时:释放资源,防止内存泄漏;
- 下一次副作用执行前(若依赖项变化):先清理旧副作用,再执行新副作用。
这种机制确保了副作用的生命周期与组件状态严格同步,避免了常见的"已卸载组件仍尝试更新状态"错误。
副作用的本质:打破纯函数的边界
React 组件本质上应是一个纯函数:给定相同的 props 和 state,始终返回相同的 JSX。而副作用(如修改全局变量、发起网络请求、改变 DOM)则打破了这一原则,因其结果具有不确定性或对外部环境产生影响。
例如,以下函数存在副作用:
javascript
function add(nums) {
nums.push(3); // 修改了外部数组
return nums.reduce((a, b) => a + b, 0);
}
调用后,原始 nums 数组被改变,后续代码行为不可预测。而在 React 中,useEffect 正是将这类"不纯"的操作集中管理的容器,使主渲染逻辑保持纯净,提升可测试性与可维护性。
实际应用:数据获取与条件渲染
结合 useState 与 useEffect,可实现典型的数据驱动 UI:
javascript
function App() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(setData);
}, []);
return data ? <div>{data}</div> : <p>加载中...</p>;
}
这里,数据请求作为副作用在挂载后执行,成功后通过 setData 触发重新渲染,展示最新内容。整个流程清晰、线性,无需关心生命周期钩子的切换。
总结
React Hooks 通过 useState 和 useEffect 等核心 API,将状态管理和副作用处理从类组件的生命周期中解放出来,赋予函数组件完整的逻辑表达能力。它们以声明式的方式描述"何时做什么",而非"在哪个阶段做什么",更符合直觉。同时,依赖数组机制强制开发者显式声明副作用的触发条件,提升了代码的可读性与健壮性。掌握 Hooks,不仅是使用现代 React 的必备技能,更是迈向函数式、响应式前端开发思维的关键一步。