useState:状态管理的基石
useState 是最基础的 Hook,用于在函数组件中声明状态。它返回一个包含当前值和更新函数的数组。React 通过调用顺序来识别每个 Hook,因此必须保证 Hook 在组件的每次渲染中以相同的顺序执行。
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
// 函数式更新:基于前一个状态计算新值
const increment = () => setCount(prev => prev + 1)
// 惰性初始化:初始值只在首次渲染时计算
const [data, setData] = useState(() => {
const saved = localStorage.getItem('data')
return saved ? JSON.parse(saved) : []
})
return (
<div>
<p>计数:{count}</p>
<button onClick={increment}>+1</button>
</div>
)
}
useEffect:副作用与生命周期
useEffect 用于处理副作用,如数据请求、DOM 操作、事件订阅等。它接受一个回调函数和一个依赖数组。当依赖数组中的值发生变化时,回调函数会在渲染完成后执行。返回的清理函数会在组件卸载或下次 effect 执行前调用。
import { useEffect, useState } from 'react'
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
useEffect(() => {
let cancelled = false
async function fetchUser() {
const res = await fetch(`/api/users/${userId}`)
const data = await res.json()
if (!cancelled) {
setUser(data)
}
}
fetchUser()
// 清理函数:防止组件卸载后的状态更新和竞态条件
return () => {
cancelled = true
}
}, [userId]) // 仅在 userId 变化时重新执行
if (!user) return <p>加载中...</p>
return <h2>{user.name}</h2>
}
useRef:跨越渲染的持久引用
useRef 返回一个可变的 ref 对象,其 .current 属性可以在组件的整个生命周期内保持不变。修改 .current 不会触发重新渲染,这使得它非常适合存储 DOM 引用、定时器 ID 或任何不需要驱动视图更新的可变值。
import { useRef, useEffect } from 'react'
function TextInput() {
const inputRef = useRef(null)
const renderCount = useRef(0)
useEffect(() => {
inputRef.current?.focus()
}, [])
// 不推荐用 ref 追踪渲染次数来做副作用,此处仅作演示
renderCount.current++
return <input ref={inputRef} placeholder="自动聚焦" />
}
useCallback 与 useMemo:性能优化
useCallback 缓存函数引用,useMemo 缓存计算结果。它们都接受一个工厂函数和依赖数组,仅在依赖变化时重新计算。这两个 Hook 的核心价值在于避免不必要的子组件重渲染或重复计算。
import { useCallback, useMemo, useState } from 'react'
function SearchPanel({ items }) {
const [query, setQuery] = useState('')
// useMemo:缓存过滤结果
const filteredItems = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
)
}, [items, query])
// useCallback:缓存事件处理函数
const handleChange = useCallback((e) => {
setQuery(e.target.value)
}, [])
return (
<div>
<input onChange={handleChange} placeholder="搜索..." />
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
)
}
useContext:跨层级数据传递
useContext 让组件直接订阅 Context 的值,避免逐层传递 props(即 prop drilling)。当 Context 值变化时,所有消费该 Context 的组件都会重新渲染。
import { createContext, useContext, useState } from 'react'
const ThemeContext = createContext('light')
function App() {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider value={theme}>
<Toolbar />
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
切换主题
</button>
</ThemeContext.Provider>
)
}
function Toolbar() {
return <ThemeButton />
}
function ThemeButton() {
const theme = useContext(ThemeContext)
return <button className={theme}>当前主题:{theme}</button>
}
自定义 Hook:逻辑复用的最佳方式
自定义 Hook 是以 use 开头的函数,内部可以调用其他 Hook。它允许将组件逻辑提取到可复用的函数中,是 React 推荐的逻辑复用模式。
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const item = localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch {
return initialValue
}
})
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value))
}, [key, value])
return [value, setValue]
}
// 使用
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light')
return (
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
主题:{theme}
</button>
)
}
Hooks 的规则与常见陷阱
React Hooks 有两条铁律:只在函数组件或自定义 Hook 的顶层调用 Hook,不要在循环、条件或嵌套函数中调用。这是因为 React 依赖调用顺序来匹配 Hook 和其对应的状态。违反这条规则会导致状态错乱。
常见的陷阱包括:闭包陷阱(effect 中引用了过期的变量)、无限循环(useEffect 缺少依赖或依赖引用每次都变化)、遗漏依赖(eslint-plugin-react-hooks 可以帮助检测)。使用函数式更新可以规避许多闭包问题,而在依赖数组中正确声明所有引用则是避免 bug 的关键。