函数式组件已成为 React 开发的主流模式,Hooks 的出现彻底改变了状态管理和生命周期处理的方式。本文将深入剖析核心 Hooks 的使用技巧和最佳实践!🔍✨
一、useState:函数式组件的状态核心 💡
import React, { useState } from 'react';
function Counter() {
// 声明状态变量和更新函数
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: 'John', age: 25 });
// 更新状态(函数式更新确保最新值)
const increment = () => setCount(prev => prev + 1);
// 更新对象(保持不可变性)
const updateUser = () => setUser(prev => ({ ...prev, age: 26 }));
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
<p>User: {user.name}, Age: {user.age}</p>
<button onClick={updateUser}>Update Age</button>
</div>
);
}
关键点:
- 状态更新函数是异步的,连续调用会被批量处理 🔄
- 对象/数组更新需保持不可变性,使用扩展运算符 🌀
- 函数式更新 (
setCount(prev => prev + 1)) 避免闭包陷阱 ⚠️
二、useEffect:副作用管理的瑞士军刀 🔧
import React, { useState, useEffect } from 'react';
function DataFetcher({ userId }) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
// 创建取消标志
const abortController = new AbortController();
const fetchData = async () => {
try {
const response = await fetch(
`https://api.example.com/users/${userId}`,
{ signal: abortController.signal }
);
setData(await response.json());
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
}
}
};
fetchData();
// 清理函数:取消请求和定时器
return () => abortController.abort();
}, [userId]); // 依赖项:userId 变化时重新执行
// 组件卸载时自动执行清理函数
// ...
}
三、生命周期映射:类组件 → Hooks 🧩
| 类组件生命周期 | Hooks 等效实现 |
|---|---|
| componentDidMount | useEffect(fn, []) |
| componentDidUpdate | useEffect(fn, [deps]) |
| componentWillUnmount | useEffect(() => { return cleanup }, []) |
挂载阶段示例:
useEffect(() => {
console.log('组件挂载完成!');
const timer = setInterval(() => {
console.log('定时器运行中...');
}, 1000);
return () => clearInterval(timer); // 卸载时清理
}, []);
四、数据请求最佳实践 📡
useEffect(() => {
let isMounted = true; // 组件挂载标志
const fetchData = async () => {
try {
const result = await axios.get('/api/data');
if (isMounted) setData(result.data);
} catch (err) {
if (isMounted) setError(err);
}
};
fetchData();
return () => {
isMounted = false; // 组件卸载时标记
// 可在此取消 Axios 请求
};
}, []);
五、为什么 useEffect 不能直接用 async? 🚫
错误示例:
// ❌ 危险!返回 Promise 而非清理函数
useEffect(async () => {
const data = await fetchData();
setData(data);
}, []);
正确解决方案:
useEffect(() => {
// 在效果内部声明 async 函数
const loadData = async () => {
const data = await fetchData();
setData(data);
};
loadData();
// 返回同步的清理函数
return () => { /* 清理逻辑 */ };
}, []);
六、高级技巧:自定义 Hook 封装 🛠️
// 创建可复用的数据获取 Hook
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
setData(await response.json());
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading };
}
// 在组件中使用
function UserProfile({ userId }) {
const { data: user, loading } = useFetch(`/users/${userId}`);
if (loading) return <Spinner />;
return <ProfileCard user={user} />;
}
关键总结 📝
- 状态分离:多个 useState 调用比合并大对象更清晰 💡
- 依赖项诚实:确保 useEffect 依赖项包含所有变化值 📈
- 清理资源:定时器、订阅、请求必须清理 🧹
- 异步处理:在 useEffect 内部声明 async 函数 🔄
- 自定义Hook:提取复杂逻辑保持组件简洁 🧑💻
黄金法则:每个 useEffect 只做一件事!将不同逻辑拆分到多个 effect 中,让代码更可预测且易于维护!🔮
通过合理运用 useState 和 useEffect,你的函数式组件将获得媲美类组件的能力,同时享受更简洁的代码结构和更好的可测试性!🎯
【延伸思考】当遇到复杂状态逻辑时,可探索 useReducer 与 useContext 的配合使用,它们能优雅处理跨组件状态共享!🌐