React Hooks 常见陷阱与最佳实践

Nina Santos | 2026-08-27T20:56:06 | JavaScript, Frontend

总结 React Hooks 开发中最常见的 8 个陷阱:闭包过期、依赖遗漏、无限循环等,给出正确写法和排查思路。

# React Hooks 常见陷阱与最佳实践 ## 陷阱一:闭包过期(Stale Closure) ```jsx // 错误:count 永远是 0 function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { console.log('count:', count); // 永远打印 0 setCount(count + 1); // 永远设为 1 }, 1000); return () => clearInterval(timer); }, []); // 空依赖,闭包捕获了初始 count return {count}; } // 正确:使用函数式更新 useEffect(() => { const timer = setInterval(() => { setCount(prev => prev + 1); // 使用最新值 }, 1000); return () => clearInterval(timer); }, []); ``` ## 陷阱二:依赖遗漏 ```jsx // 错误:fetchData 不在依赖中 function UserProfile({ userId }) { const [user, setUser] = useState(null); const fetchData = async () => { const res = await fetch('/api/users/' + userId); setUser(await res.json()); }; useEffect(() => { fetchData(); // eslint 会警告缺少依赖 }, [userId]); // fetchData 每次渲染都是新函数 // 正确:将函数移入 useEffect 内 useEffect(() => { async function fetchData() { const res = await fetch('/api/users/' + userId); setUser(await res.json()); } fetchData(); }, [userId]); } ``` ## 陷阱三:对象/数组依赖 ```jsx // 错误:每次渲染 options 都是新对象,导致无限循环 function SearchResults({ query }) { const options = { query: query, limit: 10 }; // 每次新对象 useEffect(() => { search(options); }, [options]); // 引用不同,每次都触发 // 正确:用 useMemo 缓存对象 const options = useMemo(() => ({ query: query, limit: 10 }), [query]); } ``` ## 陷阱四:useEffect 中的异步清理 ```jsx // 错误:组件卸载后仍然设置状态 useEffect(() => { let cancelled = false; async function load() { const data = await fetchData(); if (!cancelled) { setData(data); // 只在未取消时更新 } } load(); return () => { cancelled = true; }; }, []); ``` ## 陷阱五:useState 初始化陷阱 ```jsx // 错误:每次渲染都计算初始值 const [data, setData] = useState(expensiveComputation()); // 正确:使用惰性初始化(传函数) const [data, setData] = useState(() => expensiveComputation()); ``` ## 陷阱六:useCallback 滥用 ```jsx // 不需要 useCallback 的场景 function Parent() { // 如果 handleClick 不传给子组件,useCallback 没有意义 const handleClick = useCallback(() => { console.log('clicked'); }, []); return Click; } // 需要 useCallback 的场景 function Parent() { const handleClick = useCallback(() => { console.log('clicked'); }, []); return ; } ``` ## 陷阱七:useRef 不触发重渲染 ```jsx function Timer() { const countRef = useRef(0); const [, forceUpdate] = useState(0); useEffect(() => { const timer = setInterval(() => { countRef.current += 1; // 修改 ref 不会触发渲染! // 需要手动触发 forceUpdate(n => n + 1); }, 1000); return () => clearInterval(timer); }, []); return {countRef.current}; } ``` ## 陷阱八:自定义 Hook 返回值不稳定 ```jsx // 错误:每次返回新对象 function useUser(id) { const [user, setUser] = useState(null); useEffect(() => { /* fetch */ }, [id]); return { user: user, loading: !user }; // 每次新对象 } // 正确:用 useMemo 缓存 function useUser(id) { const [user, setUser] = useState(null); useEffect(() => { /* fetch */ }, [id]); return useMemo(() => ({ user: user, loading: !user }), [user]); } ``` ## Hook 规则速记 1. 只在顶层调用 Hook(不要在条件/循环中) 2. 只在 React 函数中调用 Hook 3. useEffect 依赖数组诚实申报 4. 用 ESLint 规则 `react-hooks/exhaustive-deps` 自动检查

← Back to Blog