🎯 什么是 React Hooks?#
React Hooks 是 React 16.8 引入的新特性,它允许你在函数组件中使用状态和其他 React 特性,而无需编写 class 组件。Hooks 让函数组件变得更加强大和灵活。
🪝 基础 Hooks#
✨ Hooks 的优势#
- 简化代码: 减少样板代码,提高可读性
- 逻辑复用: 更容易在组件间共享逻辑
- 学习成本: 无需理解 class 组件的复杂概念
- 性能优化: 更好的 tree-shaking 和代码分割
🪝 基础 Hooks#
1. useState - 状态管理#
useState
是最基本的 Hook,用于在函数组件中添加状态。
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>
增加
</button>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="输入姓名"
/>
</div>
);
}
函数式更新#
// 推荐:使用函数式更新
setCount(prevCount => prevCount + 1);
// 避免:直接使用当前值
setCount(count + 1);
2. useEffect - 副作用处理#
useEffect
用于处理组件中的副作用,如数据获取、订阅、手动修改 DOM 等。
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUser() {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
} catch (error) {
console.error('获取用户信息失败:', error);
} finally {
setLoading(false);
}
}
fetchUser();
}, [userId]); // 依赖数组
if (loading) return <div>加载中...</div>;
if (!user) return <div>用户不存在</div>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
useEffect 的依赖数组#
// 每次渲染后执行
useEffect(() => {
console.log('每次渲染后执行');
});
// 只在组件挂载时执行一次
useEffect(() => {
console.log('组件挂载时执行');
}, []);
// 当 count 变化时执行
useEffect(() => {
console.log('count 变化时执行');
}, [count]);
// 清理函数
useEffect(() => {
const timer = setInterval(() => {
console.log('定时器执行');
}, 1000);
return () => {
clearInterval(timer); // 清理定时器
};
}, []);
3. useContext - 上下文共享#
useContext
用于在组件树中共享数据,避免 props 层层传递。
import React, { createContext, useContext, useState } from 'react';
// 创建上下文
const ThemeContext = createContext();
// 提供者组件
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 使用上下文的组件
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
style={{
backgroundColor: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff',
}}
>
当前主题: {theme}
</button>
);
}
🔧 高级 Hooks#
1. useReducer - 复杂状态管理#
useReducer
适用于管理复杂的状态逻辑,类似于 Redux 的 reducer 模式。
import React, { useReducer } from 'react';
const initialState = {
count: 0,
step: 1,
};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'setStep':
return { ...state, step: action.payload };
case 'reset':
return initialState;
default:
throw new Error(`未知操作: ${action.type}`);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>计数: {state.count}</p>
<p>步长: {state.step}</p>
<button onClick={() => dispatch({ type: 'increment' })}>
增加
</button>
<button onClick={() => dispatch({ type: 'decrement' })}>
减少
</button>
<input
type="number"
value={state.step}
onChange={(e) => dispatch({
type: 'setStep',
payload: parseInt(e.target.value) || 1
})}
/>
<button onClick={() => dispatch({ type: 'reset' })}>
重置
</button>
</div>
);
}
2. useCallback - 性能优化#
useCallback
用于缓存函数,避免不必要的重新渲染。
import React, { useState, useCallback, memo } from 'react';
const ExpensiveComponent = memo(({ onIncrement }) => {
console.log('ExpensiveComponent 重新渲染');
return (
<button onClick={onIncrement}>
增加计数
</button>
);
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(0);
// 使用 useCallback 缓存函数
const handleIncrement = useCallback(() => {
setCount(c => c + 1);
}, []); // 空依赖数组,函数永远不会改变
return (
<div>
<p>计数: {count}</p>
<p>其他状态: {otherState}</p>
<ExpensiveComponent onIncrement={handleIncrement} />
<button onClick={() => setOtherState(s => s + 1)}>
更新其他状态
</button>
</div>
);
}
3. useMemo - 计算值缓存#
useMemo
用于缓存计算结果,避免重复计算。
import React, { useState, useMemo } from 'react';
function ExpensiveCalculation({ numbers }) {
// 使用 useMemo 缓存计算结果
const sum = useMemo(() => {
console.log('执行昂贵的计算...');
return numbers.reduce((acc, num) => acc + num, 0);
}, [numbers]); // 只有当 numbers 变化时才重新计算
return <div>总和: {sum}</div>;
}
function App() {
const [numbers, setNumbers] = useState([1, 2, 3, 4, 5]);
const [otherState, setOtherState] = useState(0);
return (
<div>
<ExpensiveCalculation numbers={numbers} />
<button onClick={() => setNumbers([...numbers, numbers.length + 1])}>
添加数字
</button>
<button onClick={() => setOtherState(s => s + 1)}>
更新其他状态: {otherState}
</button>
</div>
);
}
🎨 自定义 Hooks#
自定义 Hooks 让你可以提取组件逻辑到可重用的函数中。
1. 数据获取 Hook#
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
setLoading(true);
setError(null);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchData();
}, [url]);
return { data, loading, error };
}
// 使用自定义 Hook
function UserList() {
const { data: users, loading, error } = useFetch('/api/users');
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
return (
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
2. 表单处理 Hook#
import { useState, useCallback } from 'react';
function useForm(initialValues = {}) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const handleChange = useCallback((name, value) => {
setValues(prev => ({ ...prev, [name]: value }));
// 清除对应字段的错误
if (errors[name]) {
setErrors(prev => ({ ...prev, [name]: '' }));
}
}, [errors]);
const handleSubmit = useCallback((onSubmit) => {
return (e) => {
e.preventDefault();
onSubmit(values);
};
}, [values]);
const reset = useCallback(() => {
setValues(initialValues);
setErrors({});
}, [initialValues]);
return {
values,
errors,
handleChange,
handleSubmit,
reset,
setErrors,
};
}
// 使用表单 Hook
function LoginForm() {
const { values, handleChange, handleSubmit, reset } = useForm({
email: '',
password: '',
});
const onSubmit = (formData) => {
console.log('表单数据:', formData);
// 处理登录逻辑
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="email"
placeholder="邮箱"
value={values.email}
onChange={(e) => handleChange('email', e.target.value)}
/>
<input
type="password"
placeholder="密码"
value={values.password}
onChange={(e) => handleChange('password', e.target.value)}
/>
<button type="submit">登录</button>
<button type="button" onClick={reset}>重置</button>
</form>
);
}
🚀 最佳实践#
1. Hook 使用规则#
- 只在函数组件顶层调用 Hooks
- 不要在循环、条件或嵌套函数中调用 Hooks
- 只在 React 函数组件或自定义 Hooks 中调用 Hooks
2. 性能优化#
- 使用
useCallback
缓存函数 - 使用
useMemo
缓存计算结果 - 使用
React.memo
避免不必要的重新渲染 - 合理设置
useEffect
的依赖数组
3. 代码组织#
- 将相关的状态和逻辑放在同一个 Hook 中
- 提取可重用的逻辑到自定义 Hooks
- 保持 Hooks 的单一职责原则
🔧 常见问题和解决方案#
Q: 为什么 useEffect 无限循环?#
A: 检查依赖数组,确保包含所有在 effect 中使用的变量。
Q: 如何避免 useCallback 的过度使用?#
A: 只在需要传递给子组件且子组件使用 React.memo
时使用。
Q: useState 和 useReducer 如何选择?#
A: 简单状态用 useState
,复杂状态逻辑用 useReducer
。
📚 学习资源#
- React Hooks 官方文档
- React Hooks 最佳实践
- useHooks - 实用的自定义 Hooks 集合
🎯 总结#
React Hooks 是现代 React 开发的核心,它们让函数组件变得更加强大和灵活。通过掌握基础 Hooks、高级 Hooks 和自定义 Hooks,你可以构建出更加优雅和高效的 React 应用。
记住,Hooks 的学习是一个渐进的过程,建议从基础开始,逐步掌握更高级的用法。实践是最好的老师,多写代码,多尝试不同的场景!
如果你有任何问题或建议,欢迎在评论区讨论!