React Hooks 与状态管理
一、useState - 组件状态
1. 基本用法
import { useState } from 'react';
function Counter() {
// 基本类型状态
const [count, setCount] = useState(0);
// 对象类型状态
const [user, setUser] = useState({
name: '',
age: 0,
email: ''
});
// 数组类型状态
const [items, setItems] = useState([]);
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<button onClick={() => setCount(prev => prev - 1)}>减少</button>
<input
value={user.name}
onChange={e => setUser({...user, name: e.target.value})}
placeholder="姓名"
/>
</div>
);
}
2. 状态更新注意事项
function UserProfile() {
const [user, setUser] = useState({
id: 1,
name: '张三',
profile: {
age: 25,
address: {
city: '北京',
street: '朝阳路'
}
}
});
// ❌ 错误:直接修改状态
const updateWrong = () => {
user.profile.age = 30; // 这不会触发重新渲染!
};
// ✅ 正确:创建新对象
const updateCorrect = () => {
setUser({
...user,
profile: {
...user.profile,
age: 30
}
});
};
// 对于深层嵌套对象,可以使用函数式更新
const updateAddress = (newStreet) => {
setUser(prevUser => ({
...prevUser,
profile: {
...prevUser.profile,
address: {
...prevUser.profile.address,
street: newStreet
}
}
}));
};
return (
<div>
<p>姓名: {user.name}</p>
<p>年龄: {user.profile.age}</p>
<p>地址: {user.profile.address.street}</p>
<button onClick={updateCorrect}>更新年龄</button>
<button onClick={() => updateAddress('长安街')}>更新地址</button>
</div>
);
}
二、useEffect - 副作用处理
1. 基本用法
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);
// 1. 只在组件挂载时执行(模拟componentDidMount)
useEffect(() => {
console.log('组件已挂载');
// 清理函数(模拟componentWillUnmount)
return () => {
console.log('组件即将卸载');
};
}, []); // 空依赖数组
// 2. 在userId变化时执行(模拟componentDidUpdate)
useEffect(() => {
async function fetchData() {
try {
setLoading(true);
setError(null);
const response = await fetch(
`https://jsonplaceholder.typicode.com/users/${userId}`
);
if (!response.ok) {
throw new Error('请求失败');
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchData();
}, [userId]); // 依赖数组包含userId
// 3. 每次渲染后都执行
useEffect(() => {
console.log('组件渲染完成');
}); // 没有依赖数组
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
return (
<div>
<h1>用户信息</h1>
<p>ID: {data.id}</p>
<p>姓名: {data.name}</p>
<p>邮箱: {data.email}</p>
<div>
<button onClick={() => setUserId(1)}>用户1</button>
<button onClick={() => setUserId(2)}>用户2</button>
<button onClick={() => setUserId(3)}>用户3</button>
</div>
</div>
);
}
2. 副作用清理
function Timer() {
const [seconds, setSeconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
useEffect(() => {
let intervalId = null;
if (isRunning) {
intervalId = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
}
// 清理函数:组件卸载或依赖变化时执行
return () => {
if (intervalId) {
clearInterval(intervalId);
console.log('定时器已清理');
}
};
}, [isRunning]); // 依赖isRunning
return (
<div>
<h2>计时器: {seconds}秒</h2>
<button onClick={() => setIsRunning(true)}>开始</button>
<button onClick={() => setIsRunning(false)}>暂停</button>
<button onClick={() => {
setIsRunning(false);
setSeconds(0);
}}>重置</button>
</div>
);
}
三、useContext - 跨组件数据传递
1. 创建和使用Context
import { createContext, useContext, useState } from 'react';
// 1. 创建Context
const ThemeContext = createContext('light');
const UserContext = createContext(null);
function App() {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState({ name: '张三', role: 'admin' });
return (
// 2. 提供Context值
<ThemeContext.Provider value={{ theme, setTheme }}>
<UserContext.Provider value={user}>
<div className={`app ${theme}`}>
<Header />
<MainContent />
<Footer />
</div>
</UserContext.Provider>
</ThemeContext.Provider>
);
}
function Header() {
// 3. 使用Context
const { theme, setTheme } = useContext(ThemeContext);
const user = useContext(UserContext);
return (
<header>
<h1>欢迎回来, {user.name}</h1>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题: {theme === 'light' ? '🌙' : '☀️'}
</button>
</header>
);
}
function MainContent() {
const { theme } = useContext(ThemeContext);
return (
<main>
<p>当前主题: {theme}</p>
<ThemedButton />
</main>
);
}
function ThemedButton() {
const { theme } = useContext(ThemeContext);
return (
<button
style={{
backgroundColor: theme === 'dark' ? '#333' : '#fff',
color: theme === 'dark' ? '#fff' : '#333'
}}
>
主题按钮
</button>
);
}
2. 自定义Context Hook
// 创建自定义Context
const AuthContext = createContext();
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const login = async (email, password) => {
// 模拟登录
const mockUser = {
id: 1,
email,
name: '测试用户',
token: 'fake-jwt-token'
};
setUser(mockUser);
setIsAuthenticated(true);
localStorage.setItem('user', JSON.stringify(mockUser));
};
const logout = () => {
setUser(null);
setIsAuthenticated(false);
localStorage.removeItem('user');
};
const value = {
user,
isAuthenticated,
login,
logout
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
// 自定义Hook
function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth必须在AuthProvider内使用');
}
return context;
}
// 使用示例
function LoginPage() {
const { login, isAuthenticated } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
login(email, password);
};
if (isAuthenticated) {
return <Navigate to="/dashboard" />;
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="邮箱"
/>
<input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
placeholder="密码"
/>
<button type="submit">登录</button>
</form>
);
}
四、useReducer - 复杂状态逻辑
import { useReducer } from 'react';
// 1. 定义初始状态
const initialState = {
loading: false,
data: null,
error: null,
filter: 'all',
sortBy: 'name'
};
// 2. 定义reducer函数
function apiReducer(state, action) {
switch (action.type) {
case 'FETCH_START':
return {
...state,
loading: true,
error: null
};
case 'FETCH_SUCCESS':
return {
...state,
loading: false,
data: action.payload,
error: null
};
case 'FETCH_ERROR':
return {
...state,
loading: false,
error: action.payload
};
case 'SET_FILTER':
return {
...state,
filter: action.payload
};
case 'SET_SORT':
return {
...state,
sortBy: action.payload
};
case 'UPDATE_ITEM':
return {
...state,
data: state.data.map(item =>
item.id === action.payload.id
? { ...item, ...action.payload.changes }
: item
)
};
default:
return state;
}
}
// 3. 创建自定义Hook
function useApi(endpoint) {
const [state, dispatch] = useReducer(apiReducer, initialState);
const fetchData = async () => {
try {
dispatch({ type: 'FETCH_START' });
const response = await fetch(endpoint);
const data = await response.json();
dispatch({
type: 'FETCH_SUCCESS',
payload: data
});
} catch (error) {
dispatch({
type: 'FETCH_ERROR',
payload: error.message
});
}
};
const setFilter = (filter) => {
dispatch({ type: 'SET_FILTER', payload: filter });
};
const setSort = (sortBy) => {
dispatch({ type: 'SET_SORT', payload: sortBy });
};
const updateItem = (id, changes) => {
dispatch({
type: 'UPDATE_ITEM',
payload: { id, changes }
});
};
return {
state,
fetchData,
setFilter,
setSort,
updateItem
};
}
// 4. 使用示例
function UserList() {
const {
state: { data, loading, error, filter, sortBy },
fetchData,
setFilter,
updateItem
} = useApi('https://api.example.com/users');
useEffect(() => {
fetchData();
}, []);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
// 过滤和排序
const filteredUsers = data
?.filter(user => {
if (filter === 'active') return user.active;
if (filter === 'inactive') return !user.active;
return true;
})
.sort((a, b) => a[sortBy].localeCompare(b[sortBy]));
return (
<div>
<div>
<button onClick={() => setFilter('all')}>全部</button>
<button onClick={() => setFilter('active')}>活跃</button>
<button onClick={() => setFilter('inactive')}>非活跃</button>
</div>
<ul>
{filteredUsers?.map(user => (
<li key={user.id}>
{user.name} - {user.email}
<button onClick={() => updateItem(user.id, { active: !user.active })}>
{user.active ? '停用' : '激活'}
</button>
</li>
))}
</ul>
</div>
);
}
五、自定义Hook
1. 数据获取Hook
import { useState, useEffect, useCallback } from 'react';
function useFetch(url, options = {}) {
const [state, setState] = useState({
data: null,
loading: true,
error: null
});
const [refetchIndex, setRefetchIndex] = useState(0);
const refetch = useCallback(() => {
setRefetchIndex(prev => prev + 1);
}, []);
useEffect(() => {
if (!url) return;
const controller = new AbortController();
async function fetchData() {
try {
setState(prev => ({ ...prev, loading: true, error: null }));
const response = await fetch(url, {
...options,
signal: controller.signal
});
if (!response.ok) {
throw new Error(`HTTP错误 ${response.status}`);
}
const data = await response.json();
setState({ data, loading: false, error: null });
} catch (error) {
if (error.name !== 'AbortError') {
setState(prev => ({
...prev,
loading: false,
error: error.message
}));
}
}
}
fetchData();
return () => {
controller.abort();
};
}, [url, refetchIndex, options]);
return { ...state, refetch };
}
// 使用示例
function UserProfile({ userId }) {
const { data: user, loading, error, refetch } = useFetch(
`https://api.example.com/users/${userId}`,
{ headers: { 'Authorization': 'Bearer token' } }
);
if (loading) return <div>加载用户信息...</div>;
if (error) return <div>错误: {error}</div>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<button onClick={refetch}>重新加载</button>
</div>
);
}
2. 表单Hook
import { useState, useCallback } from 'react';
function useForm(initialValues = {}, validations = {}) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const handleChange = useCallback((e) => {
const { name, value, type, checked } = e.target;
setValues(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
// 如果字段已被触摸过,验证
if (touched[name]) {
validateField(name, value);
}
}, [touched]);
const handleBlur = useCallback((e) => {
const { name, value } = e.target;
setTouched(prev => ({
...prev,
[name]: true
}));
validateField(name, value);
}, []);
const validateField = useCallback((name, value) => {
if (validations[name]) {
const error = validations[name](value, values);
setErrors(prev => ({
...prev,
[name]: error || null
}));
}
}, [values, validations]);
const validateAll = useCallback(() => {
const newErrors = {};
let isValid = true;
Object.keys(validations).forEach(name => {
const error = validations[name](values[name], values);
if (error) {
newErrors[name] = error;
isValid = false;
}
});
setErrors(newErrors);
setTouched(
Object.keys(values).reduce((acc, key) => ({ ...acc, [key]: true }), {})
);
return isValid;
}, [values, validations]);
const reset = useCallback(() => {
setValues(initialValues);
setErrors({});
setTouched({});
}, [initialValues]);
return {
values,
errors,
touched,
handleChange,
handleBlur,
validateAll,
setValues,
reset
};
}
// 使用示例
function LoginForm() {
const validations = {
email: (value) => {
if (!value) return '邮箱不能为空';
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return '邮箱格式不正确';
}
return null;
},
password: (value) => {
if (!value) return '密码不能为空';
if (value.length < 6) return '密码至少6位';
return null;
}
};
const {
values,
errors,
touched,
handleChange,
handleBlur,
validateAll,
reset
} = useForm({ email: '', password: '' }, validations);
const handleSubmit = (e) => {
e.preventDefault();
if (validateAll()) {
console.log('表单数据:', values);
// 提交逻辑
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
name="email"
type="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
placeholder="邮箱"
/>
{touched.email && errors.email && (
<span style={{ color: 'red' }}>{errors.email}</span>
)}
</div>
<div>
<input
name="password"
type="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
placeholder="密码"
/>
{touched.password && errors.password && (
<span style={{ color: 'red' }}>{errors.password}</span>
)}
</div>
<button type="submit">登录</button>
<button type="button" onClick={reset}>重置</button>
</form>
);
}
六、状态管理最佳实践
1. 状态提升
// 当多个组件需要共享状态时,将状态提升到最近的共同父组件
function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
return (
<div>
<ChildA count={count} onIncrement={() => setCount(c => c + 1)} />
<ChildB text={text} onTextChange={setText} />
<ChildC count={count} text={text} />
</div>
);
}
2. 使用useReducer管理复杂状态
// 当状态逻辑复杂时,使用useReducer
function ComplexForm() {
const [state, dispatch] = useReducer(formReducer, {
step: 1,
personalInfo: { name: '', email: '' },
address: { city: '', street: '' },
errors: {},
isSubmitting: false
});
const nextStep = () => {
if (validateStep(state.step)) {
dispatch({ type: 'NEXT_STEP' });
}
};
return (
<div>
{state.step === 1 && (
<Step1
data={state.personalInfo}
errors={state.errors}
onChange={(data) => dispatch({ type: 'UPDATE_PERSONAL', payload: data })}
/>
)}
{state.step === 2 && (
<Step2
data={state.address}
onChange={(data) => dispatch({ type: 'UPDATE_ADDRESS', payload: data })}
/>
)}
<button onClick={nextStep}>下一步</button>
</div>
);
}
3. Context + useReducer 模式
// 创建全局状态
const StoreContext = createContext();
function StoreProvider({ children }) {
const [state, dispatch] = useReducer(rootReducer, initialState);
return (
<StoreContext.Provider value={{ state, dispatch }}>
{children}
</StoreContext.Provider>
);
}
function useStore() {
const context = useContext(StoreContext);
if (!context) {
throw new Error('useStore必须在StoreProvider内使用');
}
return context;
}
七、性能优化
1. useMemo - 记忆计算结果
import { useMemo } from 'react';
function ExpensiveComponent({ list, filter }) {
// 只有当list或filter变化时才重新计算
const filteredList = useMemo(() => {
console.log('计算过滤列表...');
return list.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [list, filter]); // 依赖数组
// 复杂计算
const stats = useMemo(() => {
return {
total: filteredList.length,
average: filteredList.reduce((sum, item) => sum + item.value, 0) / filteredList.length,
max: Math.max(...filteredList.map(item => item.value))
};
}, [filteredList]);
return (
<div>
<p>总数: {stats.total}</p>
<p>平均值: {stats.average.toFixed(2)}</p>
<p>最大值: {stats.max}</p>
</div>
);
}
2. useCallback - 记忆函数
import { useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
// 使用useCallback避免子组件不必要的重渲染
const increment = useCallback(() => {
setCount(c => c + 1);
}, []); // 依赖数组为空,函数只创建一次
const fetchData = useCallback(async (id) => {
const response = await fetch(`/api/data/${id}`);
return response.json();
}, []); // 依赖数组为空
return (
<div>
<Child onIncrement={increment} fetchData={fetchData} />
</div>
);
}
// 使用React.memo避免不必要的重渲染
const Child = React.memo(function Child({ onIncrement, fetchData }) {
console.log('Child渲染了');
return (
<button onClick={onIncrement}>增加</button>
);
});
八、第三方状态管理库(Zustand示例)
import create from 'zustand';
// 创建store
const useStore = create((set, get) => ({
// 状态
count: 0,
user: null,
todos: [],
loading: false,
// Action
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
// 异步action
fetchUser: async (id) => {
set({ loading: true });
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
set({ user, loading: false });
} catch (error) {
console.error('获取用户失败:', error);
set({ loading: false });
}
},
// 在action中访问当前状态
asyncAdd: async (amount) => {
const { count } = get(); // 获取当前状态
set({ loading: true });
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1000));
set(state => ({
count: state.count + amount,
loading: false
}));
},
// 复杂更新
addTodo: (text) =>
set(state => ({
todos: [...state.todos, {
id: Date.now(),
text,
completed: false
}]
})),
toggleTodo: (id) =>
set(state => ({
todos: state.todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
}))
}));
// 在组件中使用
function Counter() {
const { count, increment, decrement, reset } = useStore();
return (
<div>
<h1>计数: {count}</h1>
<button onClick={increment}>增加</button>
<button onClick={decrement}>减少</button>
<button onClick={reset}>重置</button>
</div>
);
}
// 选择器:只订阅需要的数据
function TodoList() {
const todos = useStore(state => state.todos);
const addTodo = useStore(state => state.addTodo);
const toggleTodo = useStore(state => state.toggleTodo);
return (
<div>
<button onClick={() => addTodo('新任务')}>添加任务</button>
<ul>
{todos.map(todo => (
<li
key={todo.id}
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
onClick={() => toggleTodo(todo.id)}
>
{todo.text}
</li>
))}
</ul>
</div>
);
}
关键要点
-
useState:用于组件内部状态
-
useEffect:处理副作用(API调用、订阅、DOM操作)
-
useContext:跨组件传递数据
-
useReducer:复杂状态逻辑
-
自定义Hook:封装和复用逻辑
-
性能优化:useMemo、useCallback、React.memo
-
状态管理策略:
-
局部状态用useState
-
组件间共享用状态提升
-
复杂状态用useReducer
-
全局状态用Context或第三方库
-
练习建议
-
创建一个计数器应用,使用所有状态管理方法实现
-
实现一个Todo应用,支持添加、删除、完成、过滤
-
创建一个用户管理面板,集成API调用
-
实现一个购物车,使用Context + useReducer
-
用Zustand重构上述应用,对比体验差异