React Hooks 是 React 16.8 引入的新特性,它让我们可以在函数组件中使用状态和其他 React 特性。本文将深入探讨 Hooks 的使用方法和最佳实践。
🎯 为什么需要 Hooks?
在 Hooks 出现之前,函数组件被称为"无状态组件",只能接收 props 并返回 JSX。如果需要状态管理或生命周期方法,就必须使用类组件。
类组件的问题
- 代码复杂度高
- 难以复用状态逻辑
- 生命周期方法中逻辑分散
- this 绑定问题
📚 常用 Hooks 详解
useState - 状态管理
jsx1import React, { useState } from 'react'; 2 3function Counter() { 4 const [count, setCount] = useState(0); 5 6 return ( 7 <div> 8 <p>当前计数: {count}</p> 9 <button onClick={() => setCount(count + 1)}> 10 增加 11 </button> 12 </div> 13 ); 14}
关键要点:
- 初始状态只在首次渲染时使用
- setState 是异步的
- 可以传入函数来基于前一个状态更新
useEffect - 副作用处理
jsx1import React, { useState, useEffect } from 'react'; 2 3function UserProfile({ userId }) { 4 const [user, setUser] = useState(null); 5 const [loading, setLoading] = useState(true); 6 7 useEffect(() => { 8 async function fetchUser() { 9 setLoading(true); 10 try { 11 const response = await fetch(`/api/users/${userId}`); 12 const userData = await response.json(); 13 setUser(userData); 14 } catch (error) { 15 console.error('获取用户信息失败:', error); 16 } finally { 17 setLoading(false); 18 } 19 } 20 21 fetchUser(); 22 }, [userId]); // 依赖数组 23 24 if (loading) return <div>加载中...</div>; 25 if (!user) return <div>用户不存在</div>; 26 27 return ( 28 <div> 29 <h2>{user.name}</h2> 30 <p>{user.email}</p> 31 </div> 32 ); 33}
useContext - 上下文消费
jsx1import React, { useContext, createContext } from 'react'; 2 3const ThemeContext = createContext(); 4 5function App() { 6 return ( 7 <ThemeContext.Provider value="dark"> 8 <Header /> 9 </ThemeContext.Provider> 10 ); 11} 12 13function Header() { 14 const theme = useContext(ThemeContext); 15 return ( 16 <header className={`theme-${theme}`}> 17 <h1>我的应用</h1> 18 </header> 19 ); 20}
useReducer - 复杂状态管理
jsx1import React, { useReducer } from 'react'; 2 3const initialState = { 4 todos: [], 5 filter: 'all' 6}; 7 8function todoReducer(state, action) { 9 switch (action.type) { 10 case 'ADD_TODO': 11 return { 12 ...state, 13 todos: [...state.todos, { 14 id: Date.now(), 15 text: action.text, 16 completed: false 17 }] 18 }; 19 case 'TOGGLE_TODO': 20 return { 21 ...state, 22 todos: state.todos.map(todo => 23 todo.id === action.id 24 ? { ...todo, completed: !todo.completed } 25 : todo 26 ) 27 }; 28 case 'SET_FILTER': 29 return { 30 ...state, 31 filter: action.filter 32 }; 33 default: 34 return state; 35 } 36} 37 38function TodoApp() { 39 const [state, dispatch] = useReducer(todoReducer, initialState); 40 41 const addTodo = (text) => { 42 dispatch({ type: 'ADD_TODO', text }); 43 }; 44 45 const toggleTodo = (id) => { 46 dispatch({ type: 'TOGGLE_TODO', id }); 47 }; 48 49 return ( 50 <div> 51 {/* Todo 应用 UI */} 52 </div> 53 ); 54}
🔧 自定义 Hooks
自定义 Hooks 让我们可以提取组件逻辑到可复用的函数中。
useLocalStorage
jsx1import { useState, useEffect } from 'react'; 2 3function useLocalStorage(key, initialValue) { 4 const [storedValue, setStoredValue] = useState(() => { 5 try { 6 const item = window.localStorage.getItem(key); 7 return item ? JSON.parse(item) : initialValue; 8 } catch (error) { 9 console.error('读取 localStorage 失败:', error); 10 return initialValue; 11 } 12 }); 13 14 const setValue = (value) => { 15 try { 16 setStoredValue(value); 17 window.localStorage.setItem(key, JSON.stringify(value)); 18 } catch (error) { 19 console.error('写入 localStorage 失败:', error); 20 } 21 }; 22 23 return [storedValue, setValue]; 24} 25 26// 使用示例 27function Settings() { 28 const [theme, setTheme] = useLocalStorage('theme', 'light'); 29 30 return ( 31 <div> 32 <p>当前主题: {theme}</p> 33 <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> 34 切换主题 35 </button> 36 </div> 37 ); 38}
useFetch
jsx1import { useState, useEffect } from 'react'; 2 3function useFetch(url) { 4 const [data, setData] = useState(null); 5 const [loading, setLoading] = useState(true); 6 const [error, setError] = useState(null); 7 8 useEffect(() => { 9 const fetchData = async () => { 10 try { 11 setLoading(true); 12 setError(null); 13 const response = await fetch(url); 14 if (!response.ok) { 15 throw new Error(`HTTP error! status: ${response.status}`); 16 } 17 const result = await response.json(); 18 setData(result); 19 } catch (err) { 20 setError(err.message); 21 } finally { 22 setLoading(false); 23 } 24 }; 25 26 fetchData(); 27 }, [url]); 28 29 return { data, loading, error }; 30} 31 32// 使用示例 33function UserList() { 34 const { data: users, loading, error } = useFetch('/api/users'); 35 36 if (loading) return <div>加载中...</div>; 37 if (error) return <div>错误: {error}</div>; 38 39 return ( 40 <ul> 41 {users.map(user => ( 42 <li key={user.id}>{user.name}</li> 43 ))} 44 </ul> 45 ); 46}
⚡ 性能优化
useMemo
jsx1import React, { useMemo } from 'react'; 2 3function ExpensiveList({ items, filter }) { 4 const filteredItems = useMemo(() => { 5 console.log('过滤计算执行'); 6 return items.filter(item => 7 item.name.toLowerCase().includes(filter.toLowerCase()) 8 ); 9 }, [items, filter]); 10 11 return ( 12 <ul> 13 {filteredItems.map(item => ( 14 <li key={item.id}>{item.name}</li> 15 ))} 16 </ul> 17 ); 18}
useCallback
jsx1import React, { useCallback, useState } from 'react'; 2 3function TodoList({ todos }) { 4 const [filter, setFilter] = useState(''); 5 6 const handleToggle = useCallback((id) => { 7 // 处理切换逻辑 8 }, []); 9 10 const handleDelete = useCallback((id) => { 11 // 处理删除逻辑 12 }, []); 13 14 return ( 15 <div> 16 {todos.map(todo => ( 17 <TodoItem 18 key={todo.id} 19 todo={todo} 20 onToggle={handleToggle} 21 onDelete={handleDelete} 22 /> 23 ))} 24 </div> 25 ); 26}
📋 Hooks 使用规则
1. 只在顶层调用 Hooks
jsx1// ✅ 正确 2function MyComponent() { 3 const [count, setCount] = useState(0); 4 5 return <div>{count}</div>; 6} 7 8// ❌ 错误 9function MyComponent() { 10 if (someCondition) { 11 const [count, setCount] = useState(0); // 不要在条件语句中调用 12 } 13}
2. 只在 React 函数中调用 Hooks
jsx1// ✅ 正确 - 在函数组件中 2function MyComponent() { 3 const [state, setState] = useState(); 4 return <div />; 5} 6 7// ✅ 正确 - 在自定义 Hook 中 8function useCustomHook() { 9 const [state, setState] = useState(); 10 return state; 11} 12 13// ❌ 错误 - 在普通函数中 14function regularFunction() { 15 const [state, setState] = useState(); // 不要在普通函数中调用 16}
🎯 最佳实践
1. 合理拆分状态
jsx1// ❌ 不推荐 - 将所有状态放在一个对象中 2const [state, setState] = useState({ 3 name: '', 4 email: '', 5 age: 0, 6 loading: false 7}); 8 9// ✅ 推荐 - 按逻辑分组拆分状态 10const [userInfo, setUserInfo] = useState({ name: '', email: '', age: 0 }); 11const [loading, setLoading] = useState(false);
2. 优化依赖数组
jsx1// ❌ 缺少依赖 2useEffect(() => { 3 fetchUser(userId); 4}, []); // userId 变化时不会重新执行 5 6// ✅ 包含所有依赖 7useEffect(() => { 8 fetchUser(userId); 9}, [userId]);
3. 避免不必要的重渲染
jsx1// 使用 React.memo 包装子组件 2const ChildComponent = React.memo(({ title, onClick }) => { 3 return <button onClick={onClick}>{title}</button>; 4}); 5 6function ParentComponent() { 7 const [count, setCount] = useState(0); 8 9 // 使用 useCallback 缓存函数 10 const handleClick = useCallback(() => { 11 console.log('按钮被点击'); 12 }, []); 13 14 return ( 15 <div> 16 <p>{count}</p> 17 <ChildComponent title="点击我" onClick={handleClick} /> 18 </div> 19 ); 20}
🚀 总结
React Hooks 为函数组件带来了强大的能力:
- useState - 简单状态管理
- useEffect - 处理副作用
- useContext - 消费上下文
- useReducer - 复杂状态管理
- useMemo/useCallback - 性能优化
- 自定义 Hooks - 逻辑复用
掌握这些 Hooks 的使用技巧和最佳实践,能够让你写出更简洁、更易维护的 React 代码。
记住 Hooks 的两个基本规则,合理组织状态和副作用,你就能充分发挥 Hooks 的威力!
评论讨论
使用 GitHub 账号登录即可参与讨论