React Hooks 深入理解:原理与最佳实践
React Hooks是React 16.8引入的革命性特性,让函数组件拥有了状态和副作用管理能力,彻底改变了React的编程方式。本文深入讲解核心Hooks的工作原理和使用最佳实践。
一、useState:状态管理
import { useState } from 'react';
function Counter() {
// useState返回[state, setState]
const [count, setCount] = useState(0);
// 函数式更新(推荐,避免闭包陷阱)
const increment = () => setCount(prev => prev + 1);
// 状态初始化:传入函数避免重复计算
const [data, setData] = useState(() => {
return expensiveComputation(); // 只在首次渲染时执行
});
return <button onClick={increment}>{count}</button>;
}
// 复杂状态使用对象
const [form, setForm] = useState({ name: '', email: '' });
// 更新时必须展开合并,否则会丢失其他字段
const handleChange = (field, value) => {
setForm(prev => ({ ...prev, [field]: value }));
};
二、useEffect:副作用处理
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let cancelled = false;
async function fetchUser() {
const data = await api.getUser(userId);
if (!cancelled) {
setUser(data);
}
}
fetchUser();
// 清理函数(组件卸载或依赖变化时执行)
return () => {
cancelled = true; // 防止内存泄漏
};
}, [userId]); // 依赖数组:userId变化时重新执行
return user ? <div>{user.name}</div> : <Loading />;
}
// 常见useEffect使用模式
// 1. 只在挂载时执行(空依赖数组)
useEffect(() => { /* 只执行一次 */ }, []);
// 2. 每次渲染后都执行(无依赖数组)
useEffect(() => { /* 每次渲染后执行 */ });
// 3. 依赖变化时执行
useEffect(() => { /* 依赖变化时执行 */ }, [dep1, dep2]);
三、useCallback 和 useMemo:性能优化
import { useCallback, useMemo } from 'react';
function ExpensiveComponent({ items, onSelect }) {
// useMemo:缓存计算结果
const sortedItems = useMemo(() => {
console.log('Sorting items...');
return [...items].sort((a, b) => a.name.localeCompare(b.name));
}, [items]); // 只在items变化时重新计算
// useCallback:缓存函数引用
const handleClick = useCallback((id) => {
onSelect(id);
}, [onSelect]); // 避免子组件不必要的重渲染
return (
<ul>
{sortedItems.map(item => (
<Item key={item.id} item={item} onClick={handleClick} />
))}
</ul>
);
}
四、useRef:引用与可变值
import { useRef, useEffect } from 'react';
function TextInput() {
// 1. 访问DOM元素
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus(); // 组件挂载后自动聚焦
}, []);
// 2. 存储不触发重渲染的可变值
const renderCount = useRef(0);
renderCount.current++; // 不会触发重渲染
// 3. 保存定时器ID(清理时使用)
const timerRef = useRef(null);
const startTimer = () => {
timerRef.current = setInterval(() => console.log('tick'), 1000);
};
const stopTimer = () => clearInterval(timerRef.current);
return <input ref={inputRef} />;
}
五、useContext:跨层级状态传递
// 创建Context
const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} });
// Provider组件
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = useCallback(() => {
setTheme(t => t === 'light' ? 'dark' : 'light');
}, []);
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 消费Context(任意嵌套深度的子组件)
function Button() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button className={theme} onClick={toggleTheme}>
Toggle Theme
</button>
);
}
六、自定义Hook:逻辑复用
// 自定义Hook:封装数据获取逻辑
function useAsync(asyncFn, deps = []) {
const [state, setState] = useState({ data: null, loading: false, error: null });
useEffect(() => {
setState(prev => ({ ...prev, loading: true }));
asyncFn()
.then(data => setState({ data, loading: false, error: null }))
.catch(error => setState({ data: null, loading: false, error }));
}, deps);
return state;
}
// 使用自定义Hook
function UserList() {
const { data, loading, error } = useAsync(() => api.getUsers(), []);
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <ul>{data?.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
七、Hooks使用规则
- 只能在React函数组件或自定义Hook中调用Hooks
- 只能在函数顶层调用Hooks,不能在条件语句、循环或嵌套函数中调用
- 自定义Hook命名必须以”use”开头
- 依赖数组必须包含所有在effect中使用的响应式变量(使用ESLint exhaustive-deps规则)
八、总结
React Hooks让代码组织从”生命周期”转变为”关注点”,相关逻辑内聚在一个Hook中,大幅提升代码可读性和复用性。建议从useState和useEffect入手,逐步掌握useCallback、useMemo等优化Hooks,最终能够封装自定义Hook实现业务逻辑复用。