React Hooks 自定义封装与避坑指南
文章目录
- React Hooks 自定义封装与避坑指南
- 为什么需要自定义 Hooks?
- 三个实用的自定义 Hooks
- 1. useLocalStorage - 本地存储管理
- 2. useFetch - 数据获取
- 3. useDebounce - 防抖处理
- 五个常见陷阱与解决方案
- 1. 依赖数组遗漏 ❌
- 2. 内存泄漏 ❌
- 3. 无限循环 ❌
- 4. 条件调用 Hooks ❌
- 5. 过度使用 useCallback ❌
- 最佳实践
- 1. 命名规范
- 2. 返回值设计
- 3. 错误处理
- 总结
React Hooks 自定义封装与避坑指南
掌握自定义 Hooks 的核心技巧,避开常见陷阱
为什么需要自定义 Hooks?
自定义 Hooks 让我们能够:
- 复用状态逻辑
- 简化组件代码
- 提高代码可维护性
三个实用的自定义 Hooks
1. useLocalStorage - 本地存储管理
import { useState, useEffect } from 'react';function useLocalStorage(key, initialValue) {const [storedValue, setStoredValue] = useState(() => {try {const item = window.localStorage.getItem(key);return item ? JSON.parse(item) : initialValue;} catch (error) {return initialValue;}});const setValue = (value) => {try {setStoredValue(value);window.localStorage.setItem(key, JSON.stringify(value));} catch (error) {console.error('Error saving to localStorage:', error);}};return [storedValue, setValue];
}// 使用示例
function App() {const [name, setName] = useLocalStorage('username', '');return (<input value={name} onChange={(e) => setName(e.target.value)} placeholder="输入用户名"/>);
}
2. useFetch - 数据获取
import { useState, useEffect } from 'react';function useFetch(url) {const [data, setData] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {const fetchData = async () => {try {setLoading(true);const response = await fetch(url);const result = await response.json();setData(result);} catch (err) {setError(err.message);} finally {setLoading(false);}};fetchData();}, [url]);return { data, loading, error };
}// 使用示例
function UserList() {const { data, loading, error } = useFetch('/api/users');if (loading) return <div>加载中...</div>;if (error) return <div>错误: {error}</div>;return (<ul>{data?.map(user => <li key={user.id}>{user.name}</li>)}</ul>);
}
3. useDebounce - 防抖处理
import { useState, useEffect } from 'react';function useDebounce(value, delay) {const [debouncedValue, setDebouncedValue] = useState(value);useEffect(() => {const handler = setTimeout(() => {setDebouncedValue(value);}, delay);return () => {clearTimeout(handler);};}, [value, delay]);return debouncedValue;
}// 使用示例
function SearchBox() {const [searchTerm, setSearchTerm] = useState('');const debouncedSearchTerm = useDebounce(searchTerm, 500);useEffect(() => {if (debouncedSearchTerm) {// 执行搜索console.log('搜索:', debouncedSearchTerm);}}, [debouncedSearchTerm]);return (<inputvalue={searchTerm}onChange={(e) => setSearchTerm(e.target.value)}placeholder="搜索..."/>);
}
五个常见陷阱与解决方案
1. 依赖数组遗漏 ❌
// 错误:缺少依赖
function BadExample() {const [count, setCount] = useState(0);useEffect(() => {const timer = setInterval(() => {setCount(count + 1); // count 永远是 0}, 1000);return () => clearInterval(timer);}, []); // 缺少 count 依赖
}// 正确:使用函数式更新
function GoodExample() {const [count, setCount] = useState(0);useEffect(() => {const timer = setInterval(() => {setCount(prev => prev + 1); // 使用函数式更新}, 1000);return () => clearInterval(timer);}, []); // 不需要 count 依赖
}
2. 内存泄漏 ❌
// 错误:组件卸载后仍然更新状态
function BadComponent() {const [data, setData] = useState(null);useEffect(() => {fetch('/api/data').then(res => res.json()).then(setData); // 组件卸载后可能仍会执行}, []);
}// 正确:使用清理函数
function GoodComponent() {const [data, setData] = useState(null);useEffect(() => {let isMounted = true;fetch('/api/data').then(res => res.json()).then(result => {if (isMounted) {setData(result);}});return () => {isMounted = false;};}, []);
}
3. 无限循环 ❌
// 错误:对象作为依赖导致无限循环
function BadExample() {const [user, setUser] = useState({ name: '', age: 0 });useEffect(() => {// 每次渲染都会执行,因为 user 对象引用改变console.log('用户信息更新');}, [user]);
}// 正确:使用 useMemo 或具体属性
function GoodExample() {const [user, setUser] = useState({ name: '', age: 0 });useEffect(() => {console.log('用户信息更新');}, [user.name, user.age]); // 依赖具体属性
}
4. 条件调用 Hooks ❌
// 错误:条件调用 Hooks
function BadExample({ shouldFetch }) {if (shouldFetch) {const [data, setData] = useState(null); // 违反 Hooks 规则}
}// 正确:Hooks 在顶层调用,逻辑在内部处理
function GoodExample({ shouldFetch }) {const [data, setData] = useState(null);useEffect(() => {if (shouldFetch) {// 条件逻辑在 Hook 内部fetchData().then(setData);}}, [shouldFetch]);
}
5. 过度使用 useCallback ❌
// 错误:不必要的 useCallback
function BadExample() {const [count, setCount] = useState(0);// 没有必要,因为没有依赖const increment = useCallback(() => {setCount(prev => prev + 1);}, []);
}// 正确:只在需要时使用 useCallback
function GoodExample({ onUpdate }) {const [count, setCount] = useState(0);// 有必要,因为 onUpdate 可能变化const handleUpdate = useCallback(() => {onUpdate(count);}, [count, onUpdate]);
}
最佳实践
1. 命名规范
- 自定义 Hooks 必须以
use开头 - 使用描述性的名称:
useAuth、useApi、useLocalStorage
2. 返回值设计
// 推荐:返回对象,便于解构和重命名
function useCounter(initialValue = 0) {const [count, setCount] = useState(initialValue);return {count,increment: () => setCount(prev => prev + 1),decrement: () => setCount(prev => prev - 1),reset: () => setCount(initialValue)};
}// 使用时可以重命名
const { count: userCount, increment: incrementUser } = useCounter(0);
3. 错误处理
function useApi(url) {const [state, setState] = useState({data: null,loading: false,error: null});useEffect(() => {setState(prev => ({ ...prev, loading: true, error: null }));fetch(url).then(res => res.json()).then(data => setState({ data, loading: false, error: null })).catch(error => setState({ data: null, loading: false, error }));}, [url]);return state;
}
总结
自定义 Hooks 的核心是:
- 复用逻辑:将重复的状态逻辑提取出来
- 遵循规则:始终在顶层调用,不要在条件语句中使用
- 正确依赖:仔细管理 useEffect 的依赖数组
- 清理资源:防止内存泄漏,及时清理副作用
- 简单实用:保持 Hook 功能单一,易于理解和测试
掌握这些技巧,你就能写出高质量的自定义 Hooks,让 React 开发更加高效!
希望这篇精简指南能帮你快速掌握自定义 Hooks 的核心要点!
