React Hooks 完全指南
Hooks 改变了 React 的开发方式。本文全面讲解如何使用和创建 Hooks。
内置 Hooks
useState - 状态管理
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
const [name, setName] = useState('John')
const [user, setUser] = useState({
age: 30,
email: 'john@example.com',
})
// 使用函数初始化状态(对于复杂初始值)
const [data, setData] = useState(() => {
console.log('初始化数据...')
return fetchInitialData() // 仅在首次渲染时调用
})
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
{/* 函数式更新 */}
<button onClick={() => setCount(prev => prev + 1)}>
函数式增加
</button>
{/* 更新对象 */}
<button onClick={() => setUser({ ...user, age: user.age + 1 })}>
增加年龄
</button>
</div>
)
}
useEffect - 副作用处理
import { useState, useEffect } from 'react'
function DataFetcher() {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [userId, setUserId] = useState(1)
// 副作用 - 每次渲染后执行
useEffect(() => {
console.log('组件已挂载或已更新')
})
// 挂载时执行一次
useEffect(() => {
console.log('组件已挂载')
return () => {
console.log('组件已卸载')
}
}, [])
// 当 userId 改变时执行
useEffect(() => {
let isMounted = true // 防止内存泄漏
const fetchData = async () => {
setLoading(true)
try {
const response = await fetch(\`/api/users/\${userId}\`)
const result = await response.json()
if (isMounted) {
setData(result)
}
} catch (err) {
if (isMounted) {
setError(err)
}
} finally {
if (isMounted) {
setLoading(false)
}
}
}
fetchData()
// 清理函数
return () => {
isMounted = false
}
}, [userId])
if (loading) return <p>加载中...</p>
if (error) return <p>错误: {error.message}</p>
return <div>{data && JSON.stringify(data)}</div>
}
useContext - 跨组件通信
import { createContext, useContext, useState } from 'react'
// 创建上下文
const ThemeContext = createContext()
// 提供者组件
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light')
}
const value = { theme, toggleTheme }
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
)
}
// 使用 Hook
function useTheme() {
const context = useContext(ThemeContext)
if (!context) {
throw new Error('useTheme 必须在 ThemeProvider 内使用')
}
return context
}
// 组件使用
function App() {
const { theme, toggleTheme } = useTheme()
return (
<div style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#000' : '#fff',
}}>
<p>当前主题: {theme}</p>
<button onClick={toggleTheme}>切换主题</button>
</div>
)
}
// 使用
export default function Root() {
return (
<ThemeProvider>
<App />
</ThemeProvider>
)
}
自定义 Hooks
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) {
console.error(error)
return initialValue
}
})
// 当值改变时更新本地存储
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
console.error(error)
}
}
return [storedValue, setValue]
}
// 使用
function App() {
const [name, setName] = useLocalStorage('name', 'Guest')
return (
<div>
<p>姓名: {name}</p>
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
)
}
useAsync - 异步操作
import { useState, useEffect, useRef } from 'react'
function useAsync(asyncFunction, immediate = true) {
const [status, setStatus] = useState('idle')
const [value, setValue] = useState(null)
const [error, setError] = useState(null)
// 使用 ref 来防止无限循环
const executeRef = useRef(null)
const execute = useRef(async () => {
setStatus('pending')
setValue(null)
setError(null)
try {
const response = await asyncFunction()
setValue(response)
setStatus('success')
return response
} catch (error) {
setError(error)
setStatus('error')
}
})
executeRef.current = execute.current
useEffect(() => {
if (!immediate) return
executeRef.current()
}, [immediate])
return { execute: executeRef.current, status, value, error }
}
// 使用
function UserProfile({ userId }) {
const { execute, status, value: user, error } = useAsync(
() => fetch(\`/api/users/\${userId}\`).then(r => r.json()),
true
)
if (status === 'pending') return <p>加载中...</p>
if (status === 'error') return <p>错误: {error?.message}</p>
if (status === 'success') return <p>用户: {user?.name}</p>
return null
}
useFetch - 数据获取
import { useState, useEffect } from 'react'
function useFetch(url, options = {}) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let isMounted = true
const fetchData = async () => {
try {
const response = await fetch(url, {
method: 'GET',
...options,
})
if (!response.ok) {
throw new Error(\`HTTP error! status: \${response.status}\`)
}
const result = await response.json()
if (isMounted) {
setData(result)
setError(null)
}
} catch (err) {
if (isMounted) {
setError(err)
setData(null)
}
} finally {
if (isMounted) {
setLoading(false)
}
}
}
fetchData()
return () => {
isMounted = false
}
}, [url, options])
const refetch = async () => {
setLoading(true)
try {
const response = await fetch(url, options)
const result = await response.json()
setData(result)
} catch (err) {
setError(err)
} finally {
setLoading(false)
}
}
return { data, loading, error, refetch }
}
// 使用
function UserList() {
const { data: users, loading, error, refetch } = useFetch('/api/users')
if (loading) return <p>加载中...</p>
if (error) return <p>错误: {error.message}</p>
return (
<div>
<button onClick={refetch}>刷新</button>
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
)
}
usePrevious - 保存前一个值
import { useEffect, useRef } from 'react'
function usePrevious(value) {
const ref = useRef()
useEffect(() => {
ref.current = value
}, [value])
return ref.current
}
// 使用
function Counter() {
const [count, setCount] = React.useState(0)
const prevCount = usePrevious(count)
return (
<div>
<p>当前: {count}, 前一个: {prevCount}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
)
}
高级模式
useReducer - 复杂状态管理
import { useReducer } from 'react'
const initialState = {
todos: [],
filter: 'all',
error: null,
}
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, { id: Date.now(), text: action.payload }],
}
case 'REMOVE_TODO':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload),
}
case 'SET_FILTER':
return { ...state, filter: action.payload }
case 'SET_ERROR':
return { ...state, error: action.payload }
default:
return state
}
}
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, initialState)
const addTodo = (text) => {
dispatch({ type: 'ADD_TODO', payload: text })
}
const removeTodo = (id) => {
dispatch({ type: 'REMOVE_TODO', payload: id })
}
return (
<div>
{state.todos.map(todo => (
<div key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>删除</button>
</div>
))}
</div>
)
}
最佳实践
✅ 应该做的事:
- 将相关逻辑提取到自定义 Hooks
- 在 useEffect 的依赖数组中包含所有依赖
- 使用 useCallback 和 useMemo 优化性能
- 为自定义 Hooks 编写文档
- 及时清理副作用
❌ 不应该做的事:
- 在条件或循环中调用 Hooks
- 在普通函数中调用 Hooks
- 忘记依赖数组
- 过度使用 useMemo/useCallback
- 在 Hooks 中创建过多的闭包
检查清单
- Hooks 调用顺序正确
- 依赖数组完整
- 副作用正确清理
- 性能优化得当
- 代码易于理解和测试


