返回博客

React Hooks 完全指北(2025 版)

React Hooks 是 React 16.8 引入的革命性特性,它让函数组件拥有了状态管理和副作用处理的能力。本文将从设计哲学、核心 Hooks、高级应用、性能优化、最佳实践等多个维度,为你提供一份完整的 React Hooks 学习指南。

React Hooks 完全指北(2025 版)
大约 23 小时前
37 分钟阅读
92 次浏览

React Hooks 完全指北(2025 版)

摘要:React Hooks 是 React 16.8 引入的革命性特性,它让函数组件拥有了状态管理和副作用处理的能力。本文将从设计哲学、核心 Hooks、高级应用、性能优化、最佳实践等多个维度,为你提供一份完整的 React Hooks 学习指南。

目录

一、Hooks 的设计哲学

1.1 React Hooks 简介

React Hooks 是 React 16.8 版本带来的革命性特性,它让函数组件也能像类组件那样拥有状态(state)和其他 React 特性,同时还解决了类组件中的许多痛点。

类组件的问题:

  1. 逻辑复用困难:需要使用高阶组件(HOC)或渲染属性(Render Props),导致组件树嵌套过深
  2. 生命周期复杂:不相关的逻辑被强制放在同一个生命周期方法中
  3. this 指向问题:需要频繁使用 bind 或箭头函数来绑定 this
  4. 代码组织混乱:相关逻辑分散在不同的生命周期方法中

Hooks 的优势:

  • 逻辑复用:通过自定义 Hooks 实现逻辑复用,无需修改组件结构
  • 代码组织:相关逻辑可以聚合在一起,而不是被生命周期方法拆分
  • 函数式编程:更符合函数式编程思维,代码更简洁、易测试
  • 性能优化:更细粒度的性能优化控制

1.2 Hooks 规则与最佳实践

使用 Hooks 必须遵守两条核心规则:

规则一:只在最顶层调用 Hooks

错误示例:

javascript
function MyComponent({ condition }) { if (condition) { const [count, setCount] = useState(0); // ❌ 违反规则 } for (let i = 0; i < 10; i++) { useEffect(() => {}); // ❌ 违反规则 } }

** 正确示例:**

javascript
function MyComponent({ condition }) { const [count, setCount] = useState(0); // 在顶层调用 useEffect(() => { if (condition) { // 在 effect 内部使用条件逻辑 } }, [condition]); }

原因: React 依赖 Hooks 的调用顺序来正确管理状态。如果在条件语句或循环中调用,会导致每次渲染时 Hooks 的调用顺序不一致,从而引发 bug。

规则二:只在 React 函数中调用 Hooks

** 可以调用:**

  • React 函数组件
  • 自定义 Hooks(以 use 开头的函数)

不能调用:

  • 普通 JavaScript 函数
  • 类组件
  • 事件处理函数(除非是自定义 Hook 的一部分)

二、核心 Hooks 详解

2.1 useState:状态管理的基础

useState 是最基础也是最重要的 Hook,用于在函数组件中声明状态。

基本用法

javascript
import { useState } from "react"; function Counter() { const [count, setCount] = useState(0); return ( <div> <p>当前计数:{count}</p> <button onClick={() => setCount(count + 1)}>增加</button> <button onClick={() => setCount(count - 1)}>减少</button> </div> ); }

函数式更新

当新状态依赖于旧状态时,应该使用函数式更新:

javascript
function Counter() { const [count, setCount] = useState(0); // ❌ 错误:可能丢失更新 const handleClick = () => { setCount(count + 1); setCount(count + 1); // 两次更新都基于同一个 count 值 }; // 正确:使用函数式更新 const handleClick = () => { setCount(prev => prev + 1); setCount(prev => prev + 1); // 每次更新都基于最新的状态 }; return <button onClick={handleClick}>点击两次增加</button>; }

惰性初始化

如果初始状态需要通过复杂计算得到,可以使用函数进行惰性初始化:

javascript
function ExpensiveComponent() { // ❌ 错误:每次渲染都会执行 expensiveCalculation const [value, setValue] = useState(expensiveCalculation()); // 正确:只在首次渲染时执行 const [value, setValue] = useState(() => expensiveCalculation()); }

对象和数组状态

javascript
function Form() { const [formData, setFormData] = useState({ name: "", email: "", age: 0 }); // 正确:使用展开运算符保持不可变性 const updateName = (name) => { setFormData(prev => ({ ...prev, name })); }; // ❌ 错误:直接修改对象 const updateNameWrong = (name) => { formData.name = name; // 不会触发重新渲染 setFormData(formData); }; }

2.2 useReducer:复杂状态管理

useReduceruseState 的替代方案,适用于状态逻辑复杂或包含多个子值的场景。

基本用法

javascript
import { useReducer } from "react"; // Reducer 函数:纯函数,接收当前状态和动作,返回新状态 function counterReducer(state, action) { switch (action.type) { case "increment": return { count: state.count + 1 }; case "decrement": return { count: state.count - 1 }; case "reset": return { count: 0 }; case "set": return { count: action.payload }; default: throw new Error(`未知动作类型: ${action.type}`); } } function Counter() { const [state, dispatch] = useReducer(counterReducer, { count: 0 }); return ( <div> <p>计数:{state.count}</p> <button onClick={() => dispatch({ type: "increment" })}>+</button> <button onClick={() => dispatch({ type: "decrement" })}>-</button> <button onClick={() => dispatch({ type: "reset" })}>重置</button> <button onClick={() => dispatch({ type: "set", payload: 10 })}> 设置为 10 </button> </div> ); }

惰性初始化

javascript
function init(initialCount) { return { count: initialCount }; } function counterReducer(state, action) { switch (action.type) { case "increment": return { count: state.count + 1 }; case "reset": return init(action.payload); default: return state; } } function Counter({ initialCount }) { const [state, dispatch] = useReducer( counterReducer, initialCount, init // 第三个参数是初始化函数 ); return ( <> <p>计数:{state.count}</p> <button onClick={() => dispatch({ type: "reset", payload: initialCount })}> 重置 </button> </> ); }

适用场景

  • 状态逻辑复杂,包含多个子值
  • 下一个状态依赖于前一个状态
  • 需要可预测的状态更新逻辑
  • 需要更好的测试性(reducer 是纯函数)

2.3 useRef:持久化引用

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。返回的 ref 对象在组件的整个生命周期内保持不变。

基本用法

javascript
import { useRef } from "react"; function TextInput() { const inputRef = useRef(null); const focusInput = () => { inputRef.current?.focus(); }; return ( <div> <input ref={inputRef} type="text" /> <button onClick={focusInput}>聚焦输入框</button> </div> ); }

保存任意可变值

useRef 不仅可以保存 DOM 引用,还可以保存任何可变值,且不会触发重新渲染:

javascript
function Timer() { const [count, setCount] = useState(0); const intervalRef = useRef(null); const startTimer = () => { intervalRef.current = setInterval(() => { setCount(prev => prev + 1); }, 1000); }; const stopTimer = () => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; useEffect(() => { return () => { if (intervalRef.current) { clearInterval(intervalRef.current); } }; }, []); return ( <div> <p>计时:{count}</p> <button onClick={startTimer}>开始</button> <button onClick={stopTimer}>停止</button> </div> ); }

避免闭包陷阱

javascript
function Counter() { const [count, setCount] = useState(0); const countRef = useRef(count); // 保持 countRef.current 与 count 同步 useEffect(() => { countRef.current = count; }, [count]); useEffect(() => { const timer = setInterval(() => { // 使用 ref 获取最新值,避免闭包陷阱 console.log("当前计数:", countRef.current); }, 1000); return () => clearInterval(timer); }, []); // 依赖数组为空,但可以访问最新值 return <div>计数:{count}</div>; }

2.4 useEffect:副作用处理

useEffect 用于处理副作用,如数据请求、订阅、手动修改 DOM 等。

基本用法

javascript
import { useEffect, useState } from "react"; function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { // 副作用逻辑 async function fetchUser() { setLoading(true); try { const response = await fetch(`/api/users/${userId}`); const userData = await response.json(); setUser(userData); } catch (error) { console.error("获取用户失败:", error); } finally { setLoading(false); } } fetchUser(); // 清理函数(可选) return () => { // 取消请求、清理订阅等 console.log("清理副作用"); }; }, [userId]); // 依赖数组:当 userId 变化时重新执行 if (loading) return <div>加载中...</div>; if (!user) return <div>用户不存在</div>; return <div>{user.name}</div>; }

依赖数组详解

javascript
useEffect(() => { // 1. 无依赖数组:每次渲染后都执行 console.log("每次渲染都执行"); }); useEffect(() => { // 2. 空依赖数组:仅在挂载时执行一次 console.log("仅执行一次"); }, []); useEffect(() => { // 3. 有依赖:依赖变化时执行 console.log("当 count 变化时执行"); }, [count]);

清理副作用

javascript
function ChatRoom({ roomId }) { useEffect(() => { const connection = createConnection(roomId); connection.connect(); // 清理函数:在组件卸载或依赖变化前执行 return () => { connection.disconnect(); }; }, [roomId]); return <div>聊天室:{roomId}</div>; }

常见副作用模式

1. 数据获取

javascript
function useFetch(url) { const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { let cancelled = false; async function fetchData() { try { setLoading(true); const response = await fetch(url); const json = await response.json(); if (!cancelled) { setData(json); setError(null); } } catch (err) { if (!cancelled) { setError(err); } } finally { if (!cancelled) { setLoading(false); } } } fetchData(); return () => { cancelled = true; // 防止在组件卸载后更新状态 }; }, [url]); return { data, error, loading }; }

2. 事件监听

javascript
function WindowSize() { const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight }); useEffect(() => { const handleResize = () => { setSize({ width: window.innerWidth, height: window.innerHeight }); }; window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, []); return <div>窗口大小:{size.width} x {size.height}</div>; }

3. 订阅

javascript
function OnlineStatus() { const [isOnline, setIsOnline] = useState(navigator.onLine); useEffect(() => { const handleOnline = () => setIsOnline(true); const handleOffline = () => setIsOnline(false); window.addEventListener("online", handleOnline); window.addEventListener("offline", handleOffline); return () => { window.removeEventListener("online", handleOnline); window.removeEventListener("offline", handleOffline); }; }, []); return <div>{isOnline ? "🟢 在线" : "🔴 离线"}</div>; }

2.5 useLayoutEffect:同步副作用

useLayoutEffectuseEffect 类似,但它在 DOM 更新后、浏览器绘制前同步执行。

与 useEffect 的区别

javascript
function Tooltip() { const [position, setPosition] = useState({ x: 0, y: 0 }); const tooltipRef = useRef(null); // ❌ 使用 useEffect:可能闪烁 useEffect(() => { if (tooltipRef.current) { const rect = tooltipRef.current.getBoundingClientRect(); setPosition({ x: rect.left, y: rect.top }); } }, []); // 使用 useLayoutEffect:避免闪烁 useLayoutEffect(() => { if (tooltipRef.current) { const rect = tooltipRef.current.getBoundingClientRect(); setPosition({ x: rect.left, y: rect.top }); } }, []); return <div ref={tooltipRef}>工具提示</div>; }

适用场景

  • 需要同步读取 DOM 布局
  • 需要在浏览器绘制前更新 DOM
  • 避免视觉闪烁

⚠️ 警告useLayoutEffect 会阻塞浏览器绘制,不要滥用。

2.6 useCallback:缓存函数

useCallback 返回一个记忆化的回调函数,只有当依赖项改变时才会返回新的函数。

基本用法

javascript
import { useCallback, useState } from "react"; function Parent() { const [count, setCount] = useState(0); const [name, setName] = useState(""); // ❌ 每次渲染都创建新函数 const handleClick = () => { console.log("点击了"); }; // 使用 useCallback 缓存函数 const handleClickMemoized = useCallback(() => { console.log("点击了"); }, []); // 空依赖数组:函数永远不会改变 // 依赖 count:当 count 变化时创建新函数 const handleCountClick = useCallback(() => { setCount(prev => prev + 1); }, []); // 使用函数式更新,不需要 count 依赖 return ( <div> <input value={name} onChange={e => setName(e.target.value)} /> <Child onClick={handleClickMemoized} /> </div> ); } // 使用 React.memo 优化子组件 const Child = React.memo(({ onClick }) => { console.log("Child 渲染"); return <button onClick={onClick}>点击</button>; });

性能优化场景

javascript
function TodoList({ todos, onToggle }) { // 缓存回调函数,避免子组件不必要的重新渲染 const handleToggle = useCallback((id) => { onToggle(id); }, [onToggle]); return ( <ul> {todos.map(todo => ( <TodoItem key={todo.id} todo={todo} onToggle={handleToggle} /> ))} </ul> ); }

2.7 useMemo:缓存计算结果

useMemo 返回一个记忆化的值,只有当依赖项改变时才会重新计算。

基本用法

javascript
import { useMemo, useState } from "react"; function ExpensiveComponent({ items, filter }) { // ❌ 每次渲染都重新计算 const filteredItems = items.filter(item => item.category === filter); // 使用 useMemo 缓存计算结果 const filteredItems = useMemo(() => { return items.filter(item => item.category === filter); }, [items, filter]); return ( <ul> {filteredItems.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> ); }

复杂计算优化

javascript
function Fibonacci({ n }) { const result = useMemo(() => { // 昂贵的计算 function fib(num) { if (num <= 1) return num; return fib(num - 1) + fib(num - 2); } return fib(n); }, [n]); return <div>{n} 个斐波那契数:{result}</div>; }

useMemo vs useCallback

特性useMemouseCallback
返回值记忆化的值记忆化的函数
使用场景昂贵计算函数引用稳定
示例const value = useMemo(() => compute(a, b), [a, b])const fn = useCallback(() => {}, [])

2.8 useContext:上下文共享

useContext 用于在组件树中共享数据,避免 props 层层传递。

基本用法

javascript
import { createContext, useContext } from "react"; // 1. 创建 Context const ThemeContext = createContext("light"); // 2. 提供 Context 值 function App() { const [theme, setTheme] = useState("light"); return ( <ThemeContext.Provider value={{ theme, setTheme }}> <Toolbar /> </ThemeContext.Provider> ); } // 3. 消费 Context 值 function Button() { const { theme, setTheme } = useContext(ThemeContext); return ( <button className={theme} onClick={() => setTheme(theme === "light" ? "dark" : "light")} > 当前主题:{theme} </button> ); }

性能优化

javascript
// 拆分 Context,避免不必要的重新渲染 const ThemeContext = createContext("light"); const SetThemeContext = createContext(() => {}); function ThemeProvider({ children }) { const [theme, setTheme] = useState("light"); return ( <ThemeContext.Provider value={theme}> <SetThemeContext.Provider value={setTheme}> {children} </SetThemeContext.Provider> </ThemeContext.Provider> ); } // 只有需要 theme 的组件会重新渲染 function ThemedButton() { const theme = useContext(ThemeContext); return <button className={theme}>按钮</button>; } // 只有需要 setTheme 的组件会重新渲染 function ThemeToggle() { const setTheme = useContext(SetThemeContext); return <button onClick={() => setTheme("dark")}>切换主题</button>; }

三、高级 Hooks 与应用

3.1 useImperativeHandle:自定义 Ref API

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。

javascript
import { forwardRef, useImperativeHandle, useRef } from "react"; const FancyInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); }, scrollIntoView: () => { inputRef.current.scrollIntoView(); }, getValue: () => { return inputRef.current.value; } }), []); return <input ref={inputRef} {...props} />; }); function Parent() { const inputRef = useRef(); return ( <div> <FancyInput ref={inputRef} /> <button onClick={() => inputRef.current?.focus()}>聚焦</button> <button onClick={() => console.log(inputRef.current?.getValue())}> 获取值 </button> </div> ); }

3.2 useInsertionEffect:样式插入(React 18+)

useInsertionEffect 在 DOM 更新前同步执行,主要用于注入样式。

javascript
import { useInsertionEffect } from "react"; function useCSS(rule) { useInsertionEffect(() => { const style = document.createElement("style"); style.textContent = rule; document.head.appendChild(style); return () => { document.head.removeChild(style); }; }, [rule]); } function StyledComponent() { useCSS(` .my-class { color: red; } `); return <div className="my-class">红色文字</div>; }

3.3 useSyncExternalStore:订阅外部 Store(React 18+)

useSyncExternalStore 用于订阅外部数据源,确保与并发渲染兼容。

javascript
import { useSyncExternalStore } from "react"; // 简单的 Store 实现 function createStore(initialState) { let state = initialState; const listeners = new Set(); return { getState: () => state, setState: (newState) => { state = newState; listeners.forEach(listener => listener()); }, subscribe: (listener) => { listeners.add(listener); return () => listeners.delete(listener); } }; } const store = createStore({ count: 0 }); function Counter() { const state = useSyncExternalStore( store.subscribe, store.getState ); return ( <div> <p>计数:{state.count}</p> <button onClick={() => store.setState({ count: state.count + 1 })}> 增加 </button> </div> ); }

3.4 useDeferredValue:延迟值(React 18+)

useDeferredValue 可以延迟更新某个值,让 UI 保持响应。

javascript
import { useDeferredValue, useMemo } from "react"; function SearchResults({ query }) { const deferredQuery = useDeferredValue(query); const results = useMemo(() => { // 昂贵的搜索操作 return expensiveSearch(deferredQuery); }, [deferredQuery]); return ( <div> {query !== deferredQuery && <div>搜索中...</div>} <ResultsList results={results} /> </div> ); }

3.5 useTransition:并发更新(React 18+)

useTransition 可以标记非紧急更新,提升交互响应速度。

javascript
import { useTransition, useState } from "react"; function SearchBox() { const [isPending, startTransition] = useTransition(); const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const handleChange = (e) => { const value = e.target.value; setQuery(value); // 紧急更新:立即更新输入框 startTransition(() => { // 非紧急更新:延迟更新搜索结果 setResults(search(value)); }); }; return ( <div> <input value={query} onChange={handleChange} /> {isPending && <div>搜索中...</div>} <ResultsList results={results} /> </div> ); }

3.6 useActionState:异步提交状态(React 19+)

useActionState 用于管理异步提交的状态,特别适用于表单提交。

javascript
import { useActionState } from "react"; async function submitForm(prevState, formData) { try { const response = await fetch("/api/submit", { method: "POST", body: formData }); if (!response.ok) { return { error: "提交失败" }; } return { success: true, message: "提交成功!" }; } catch (error) { return { error: error.message }; } } function ContactForm() { const [state, formAction, isPending] = useActionState(submitForm, null); return ( <form action={formAction}> <input name="name" required /> <input name="email" type="email" required /> <button type="submit" disabled={isPending}> {isPending ? "提交中..." : "提交"} </button> {state?.error && <div className="error">{state.error}</div>} {state?.success && <div className="success">{state.message}</div>} </form> ); }

3.7 useOptimistic:乐观更新(React 19+)

useOptimistic 用于实现乐观更新,在服务器响应前先更新 UI。

javascript
import { useOptimistic, useTransition } from "react"; function CommentList({ comments: initialComments }) { const [comments, setOptimisticComments] = useOptimistic( initialComments, (state, newComment) => [...state, newComment] ); const [isPending, startTransition] = useTransition(); async function addComment(newComment) { // 乐观更新:立即显示新评论 startTransition(() => { setOptimisticComments(newComment); }); try { // 实际提交到服务器 await postComment(newComment); } catch (error) { // 如果失败,可以回滚 console.error("提交失败:", error); } } return ( <div> {comments.map(comment => ( <div key={comment.id}>{comment.text}</div> ))} {isPending && <div>添加中...</div>} </div> ); }

3.8 useEffectEvent:稳定副作用事件(React 19+)

useEffectEvent 解决了 useEffect 中的闭包陷阱问题。

javascript
import { useEffectEvent } from "react"; function ChatRoom({ roomId, userId }) { const onMessage = useEffectEvent((message) => { // 这个函数可以访问最新的 props,但不会导致 effect 重新执行 sendMessage(roomId, userId, message); }); useEffect(() => { const connection = createConnection(roomId); connection.on("message", onMessage); connection.connect(); return () => { connection.disconnect(); }; }, [roomId]); // 不需要将 onMessage 或 userId 加入依赖 return <div>聊天室:{roomId}</div>; }

3.9 useDebugValue:调试辅助

useDebugValue 用于在 React DevTools 中显示自定义 Hook 的标签。

javascript
import { useDebugValue, useState, useEffect } from "react"; function useOnlineStatus() { const [isOnline, setIsOnline] = useState(navigator.onLine); useEffect(() => { const handleOnline = () => setIsOnline(true); const handleOffline = () => setIsOnline(false); window.addEventListener("online", handleOnline); window.addEventListener("offline", handleOffline); return () => { window.removeEventListener("online", handleOnline); window.removeEventListener("offline", handleOffline); }; }, []); // 在 React DevTools 中显示调试值 useDebugValue(isOnline ? "🟢 在线" : "🔴 离线"); return isOnline; }

四、自定义 Hooks 实战

自定义 Hooks 是以 use 开头的函数,可以组合多个内置 Hooks 来实现逻辑复用。

4.1 数据获取 Hook

javascript
function useFetch(url, options = {}) { const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { let cancelled = false; const controller = new AbortController(); async function fetchData() { try { setLoading(true); setError(null); const response = await fetch(url, { ...options, signal: controller.signal }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const json = await response.json(); if (!cancelled) { setData(json); } } catch (err) { if (!cancelled && err.name !== "AbortError") { setError(err); } } finally { if (!cancelled) { setLoading(false); } } } fetchData(); return () => { cancelled = true; controller.abort(); }; }, [url, JSON.stringify(options)]); return { data, error, loading, refetch: () => fetchData() }; }

4.2 本地存储 Hook

javascript
function useLocalStorage(key, initialValue) { // 从 localStorage 读取初始值 const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { console.error(`读取 localStorage 失败:`, error); return initialValue; } }); // 更新状态和 localStorage const setValue = useCallback((value) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); } catch (error) { console.error(`保存到 localStorage 失败:`, error); } }, [key, storedValue]); return [storedValue, setValue]; }

4.3 防抖 Hook

javascript
function useDebounce(value, delay) { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(handler); }; }, [value, delay]); return debouncedValue; } // 使用示例 function SearchInput() { const [query, setQuery] = useState(""); const debouncedQuery = useDebounce(query, 500); useEffect(() => { if (debouncedQuery) { // 执行搜索 search(debouncedQuery); } }, [debouncedQuery]); return <input value={query} onChange={e => setQuery(e.target.value)} />; }

4.4 节流 Hook

javascript
function useThrottle(value, limit) { const [throttledValue, setThrottledValue] = useState(value); const lastRan = useRef(Date.now()); useEffect(() => { const handler = setTimeout(() => { if (Date.now() - lastRan.current >= limit) { setThrottledValue(value); lastRan.current = Date.now(); } }, limit - (Date.now() - lastRan.current)); return () => { clearTimeout(handler); }; }, [value, limit]); return throttledValue; }

4.5 窗口尺寸 Hook

javascript
function useWindowSize() { const [windowSize, setWindowSize] = useState({ width: typeof window !== "undefined" ? window.innerWidth : 0, height: typeof window !== "undefined" ? window.innerHeight : 0 }); useEffect(() => { function handleResize() { setWindowSize({ width: window.innerWidth, height: window.innerHeight }); } window.addEventListener("resize", handleResize); handleResize(); // 立即调用一次 return () => window.removeEventListener("resize", handleResize); }, []); return windowSize; }

4.6 点击外部 Hook

javascript
function useClickOutside(ref, handler) { useEffect(() => { function handleClickOutside(event) { if (ref.current && !ref.current.contains(event.target)) { handler(event); } } document.addEventListener("mousedown", handleClickOutside); document.addEventListener("touchstart", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); document.removeEventListener("touchstart", handleClickOutside); }; }, [ref, handler]); } // 使用示例 function Dropdown() { const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); useClickOutside(dropdownRef, () => setIsOpen(false)); return ( <div ref={dropdownRef}> <button onClick={() => setIsOpen(!isOpen)}>打开菜单</button> {isOpen && <div>下拉内容</div>} </div> ); }

4.7 媒体查询 Hook

javascript
function useMediaQuery(query) { const [matches, setMatches] = useState(() => { if (typeof window !== "undefined") { return window.matchMedia(query).matches; } return false; }); useEffect(() => { const mediaQuery = window.matchMedia(query); const handleChange = (event) => { setMatches(event.matches); }; // 使用 addEventListener(现代浏览器) if (mediaQuery.addEventListener) { mediaQuery.addEventListener("change", handleChange); return () => mediaQuery.removeEventListener("change", handleChange); } else { // 兼容旧浏览器 mediaQuery.addListener(handleChange); return () => mediaQuery.removeListener(handleChange); } }, [query]); return matches; } // 使用示例 function ResponsiveComponent() { const isMobile = useMediaQuery("(max-width: 768px)"); const isTablet = useMediaQuery("(min-width: 769px) and (max-width: 1024px)"); const isDesktop = useMediaQuery("(min-width: 1025px)"); if (isMobile) return <div>移动端布局</div>; if (isTablet) return <div>平板布局</div>; if (isDesktop) return <div>桌面布局</div>; return null; }

4.8 异步操作 Hook

javascript
function useAsync(asyncFunction, immediate = true) { const [status, setStatus] = useState("idle"); const [value, setValue] = useState(null); const [error, setError] = useState(null); const execute = useCallback(async (...args) => { setStatus("pending"); setValue(null); setError(null); try { const response = await asyncFunction(...args); setValue(response); setStatus("success"); return response; } catch (err) { setError(err); setStatus("error"); throw err; } }, [asyncFunction]); useEffect(() => { if (immediate) { execute(); } }, [execute, immediate]); return { execute, status, value, error, loading: status === "pending" }; } // 使用示例 function UserProfile({ userId }) { const { value: user, loading, error, execute: refetch } = useAsync( () => fetchUser(userId), true ); if (loading) return <div>加载中...</div>; if (error) return <div>错误:{error.message}</div>; if (!user) return null; return ( <div> <h1>{user.name}</h1> <button onClick={refetch}>刷新</button> </div> ); }

五、性能优化策略

5.1 避免不必要的重新渲染

javascript
// ❌ 错误:每次渲染都创建新对象 function Parent() { const data = { name: "John" }; return <Child data={data} />; } // 正确:使用 useMemo function Parent() { const data = useMemo(() => ({ name: "John" }), []); return <Child data={data} />; } // 或使用 React.memo const Child = React.memo(({ data }) => { return <div>{data.name}</div>; });

5.2 正确使用依赖数组

javascript
function Component({ userId }) { const [user, setUser] = useState(null); // ❌ 错误:缺少依赖 useEffect(() => { fetchUser(userId).then(setUser); }, []); // 缺少 userId 依赖 // 正确:包含所有依赖 useEffect(() => { fetchUser(userId).then(setUser); }, [userId]); // 或使用 useCallback 稳定函数引用 const fetchUserData = useCallback(async () => { const data = await fetchUser(userId); setUser(data); }, [userId]); useEffect(() => { fetchUserData(); }, [fetchUserData]); }

5.3 拆分大组件

javascript
// ❌ 错误:一个巨大的组件 function HugeComponent() { const [state1, setState1] = useState(); const [state2, setState2] = useState(); // ... 很多状态和逻辑 return ( <div> {/* 大量 JSX */} </div> ); } // 正确:拆分为多个小组件 function ParentComponent() { return ( <div> <HeaderSection /> <ContentSection /> <FooterSection /> </div> ); } function HeaderSection() { const [state, setState] = useState(); // 只管理自己的状态 return <header>...</header>; }

5.4 使用 React.memo 优化子组件

javascript
const ExpensiveChild = React.memo(({ data, onUpdate }) => { console.log("ExpensiveChild 渲染"); return ( <div> <p>{data.name}</p> <button onClick={onUpdate}>更新</button> </div> ); }, (prevProps, nextProps) => { // 自定义比较函数 return prevProps.data.id === nextProps.data.id && prevProps.data.name === nextProps.data.name; }); function Parent() { const [count, setCount] = useState(0); const [user, setUser] = useState({ id: 1, name: "John" }); const handleUpdate = useCallback(() => { setUser(prev => ({ ...prev, name: "Jane" })); }, []); return ( <div> <p>计数:{count}</p> <button onClick={() => setCount(c => c + 1)}>增加计数</button> <ExpensiveChild data={user} onUpdate={handleUpdate} /> </div> ); }

六、常见陷阱与解决方案

6.1 闭包陷阱

javascript
// ❌ 错误:闭包陷阱 function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { console.log(count); // 总是打印初始值 0 setCount(count + 1); }, 1000); return () => clearInterval(timer); }, []); // 空依赖数组 return <div>{count}</div>; } // 解决方案1:使用函数式更新 function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { setCount(prev => prev + 1); // 使用函数式更新 }, 1000); return () => clearInterval(timer); }, []); return <div>{count}</div>; } // 解决方案2:使用 useRef function Counter() { const [count, setCount] = useState(0); const countRef = useRef(count); useEffect(() => { countRef.current = count; }, [count]); useEffect(() => { const timer = setInterval(() => { console.log(countRef.current); // 总是最新值 setCount(countRef.current + 1); }, 1000); return () => clearInterval(timer); }, []); return <div>{count}</div>; } // 解决方案3:使用 useEffectEvent (React 19+) function Counter() { const [count, setCount] = useState(0); const logCount = useEffectEvent(() => { console.log(count); // 总是最新值 }); useEffect(() => { const timer = setInterval(() => { logCount(); setCount(prev => prev + 1); }, 1000); return () => clearInterval(timer); }, []); return <div>{count}</div>; }

6.2 无限循环

javascript
// ❌ 错误:无限循环 function Component() { const [count, setCount] = useState(0); useEffect(() => { setCount(count + 1); // 每次渲染都更新,导致无限循环 }); // 缺少依赖数组 return <div>{count}</div>; } // 正确:添加依赖数组 function Component() { const [count, setCount] = useState(0); useEffect(() => { // 只在特定条件下更新 if (count < 10) { setCount(count + 1); } }, [count]); // 明确依赖 return <div>{count}</div>; }

6.3 对象/数组依赖问题

javascript
// ❌ 错误:对象引用总是变化 function Component() { const [data, setData] = useState({ count: 0 }); useEffect(() => { console.log(data); }, [data]); // data 对象引用每次都不同,导致无限循环 return <div>{data.count}</div>; } // 解决方案1:使用具体属性 function Component() { const [data, setData] = useState({ count: 0 }); useEffect(() => { console.log(data.count); }, [data.count]); // 只依赖 count 属性 return <div>{data.count}</div>; } // 解决方案2:使用 useMemo 稳定引用 function Component() { const [data, setData] = useState({ count: 0 }); const stableData = useMemo(() => data, [data.count]); useEffect(() => { console.log(stableData); }, [stableData]); return <div>{data.count}</div>; }

6.4 清理函数忘记调用

javascript
// ❌ 错误:忘记清理 function Component() { useEffect(() => { const subscription = subscribe(); // 忘记取消订阅 }, []); } // 正确:总是清理副作用 function Component() { useEffect(() => { const subscription = subscribe(); return () => { subscription.unsubscribe(); // 清理订阅 }; }, []); }

七、最佳实践

7.1 自定义 Hooks 命名规范

javascript
// 正确:以 use 开头 function useCounter() {} function useFetch() {} function useLocalStorage() {} // ❌ 错误:不以 use 开头 function getCounter() {} function fetchData() {}

7.2 单一职责原则

javascript
// ❌ 错误:一个 Hook 做太多事情 function useUserData() { const [user, setUser] = useState(null); const [posts, setPosts] = useState([]); const [comments, setComments] = useState([]); // ... 太多职责 } // 正确:拆分为多个 Hooks function useUser(userId) { // 只管理用户数据 } function useUserPosts(userId) { // 只管理用户文章 } function useUserComments(userId) { // 只管理用户评论 }

7.3 错误处理

javascript
function useAsyncOperation(operation) { const [state, setState] = useState({ data: null, loading: false, error: null }); const execute = useCallback(async (...args) => { setState(prev => ({ ...prev, loading: true, error: null })); try { const data = await operation(...args); setState({ data, loading: false, error: null }); return data; } catch (error) { setState(prev => ({ ...prev, loading: false, error: error.message || "操作失败" })); throw error; } }, [operation]); return { ...state, execute }; }

7.4 类型安全(TypeScript)

typescript
import { useState, useEffect } from "react"; interface UseFetchResult<T> { data: T | null; loading: boolean; error: Error | null; refetch: () => void; } function useFetch<T>(url: string): UseFetchResult<T> { const [data, setData] = useState<T | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<Error | null>(null); const fetchData = useCallback(async () => { try { setLoading(true); const response = await fetch(url); const json = await response.json(); setData(json); } catch (err) { setError(err instanceof Error ? err : new Error("未知错误")); } finally { setLoading(false); } }, [url]); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; }

八、总结

8.1 Hooks 核心要点

  1. 状态管理useState 用于简单状态,useReducer 用于复杂状态
  2. 副作用处理useEffect 用于异步副作用,useLayoutEffect 用于同步副作用
  3. 性能优化useMemo 缓存计算,useCallback 缓存函数
  4. 引用管理useRef 保存可变值,不触发重新渲染
  5. 上下文共享useContext 避免 props 层层传递

8.2 版本特性总结

React 版本新增 Hooks
16.8useState, useEffect, useContext, useReducer, useCallback, useMemo, useRef, useImperativeHandle, useLayoutEffect, useDebugValue
18.0useId, useTransition, useDeferredValue, useSyncExternalStore, useInsertionEffect
19.0useActionState, useOptimistic, useEffectEvent, useFormStatus, useFormState

8.3 学习路径建议

  1. 基础阶段:掌握 useStateuseEffectuseContext
  2. 进阶阶段:学习 useReduceruseCallbackuseMemo、自定义 Hooks
  3. 高级阶段:理解 React 18+ 的并发特性,掌握 useTransitionuseDeferredValue
  4. 实战阶段:在实际项目中应用,解决复杂业务场景

8.4 记忆口诀

Hooks 三原则:顶层调用、React 函数、依赖明确

  • 只在最顶层调用 Hooks
  • 只在 React 函数中调用 Hooks
  • 明确依赖数组,避免闭包陷阱

标签:React, Hooks, 前端开发, JavaScript, 函数式编程, React 18, React 19

周温

周温

全栈开发工程师,专注于前端技术栈和物联网应用开发。拥有丰富的 React、Next.js、Nest.js 项目经验,擅长构建高性能、可扩展的 Web 应用。热爱技术分享,致力于将复杂的技术问题转化为易懂的实践指南。

评论 (0)

请先登录后再发表评论

登录

还没有评论,来发表第一条吧!