React Hooks 详解
什么是 Hooks
Hooks 是 React 16.8 引入的新特性,它允许你在函数组件中使用状态和其他 React 特性,而无需编写类组件。
内置 Hooks
useState - 状态管理
基本用法
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}函数式更新
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(prevCount => prevCount + 1);
};
const incrementBy = (amount) => {
setCount(prevCount => prevCount + amount);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
<button onClick={() => incrementBy(5)}>+5</button>
</div>
);
}对象状态更新
function Form() {
const [formData, setFormData] = useState({
name: '',
email: '',
age: 0
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
return (
<form>
<input
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Name"
/>
<input
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
</form>
);
}useEffect - 副作用处理
基本用法
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 每次渲染后执行
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}只在挂载时执行
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Component mounted');
}, []); // 空依赖数组
return <div>Count: {count}</div>;
}依赖项变化时执行
function Example() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
useEffect(() => {
console.log('Count changed:', count);
}, [count]); // 只有 count 变化时执行
useEffect(() => {
console.log('Name or count changed');
}, [name, count]); // name 或 count 变化时执行
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
</div>
);
}清理函数
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
// 清理函数
return () => {
clearInterval(timer);
};
}, []);
return <div>Seconds: {seconds}</div>;
}数据获取
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
const fetchUser = async () => {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
if (!cancelled) {
setUser(userData);
}
} catch (error) {
if (!cancelled) {
console.error('Error fetching user:', error);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
};
fetchUser();
return () => {
cancelled = true;
};
}, [userId]);
if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return <div>{user.name}</div>;
}useContext - 上下文共享
创建 Context
import React, { createContext, useContext } from 'react';
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
backgroundColor: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff'
}}
>
Toggle Theme
</button>
);
}
// 使用
function App() {
return (
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
}useReducer - 复杂状态管理
基本用法
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}复杂状态管理
const initialState = {
todos: [],
filter: 'all',
loading: false
};
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, action.payload]
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case 'SET_FILTER':
return {
...state,
filter: action.payload
};
case 'SET_LOADING':
return {
...state,
loading: action.payload
};
default:
return state;
}
}
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, initialState);
const addTodo = (text) => {
dispatch({
type: 'ADD_TODO',
payload: { id: Date.now(), text, completed: false }
});
};
const toggleTodo = (id) => {
dispatch({ type: 'TOGGLE_TODO', payload: id });
};
return (
<div>
{/* 组件内容 */}
</div>
);
}useMemo - 性能优化
import React, { useState, useMemo } from 'react';
function ExpensiveComponent({ items, filter }) {
const expensiveValue = useMemo(() => {
console.log('Computing expensive value...');
return items
.filter(item => item.category === filter)
.reduce((sum, item) => sum + item.value, 0);
}, [items, filter]); // 只有 items 或 filter 变化时才重新计算
return <div>Total value: {expensiveValue}</div>;
}
function App() {
const [items, setItems] = useState([
{ id: 1, category: 'A', value: 10 },
{ id: 2, category: 'B', value: 20 },
{ id: 3, category: 'A', value: 30 }
]);
const [filter, setFilter] = useState('A');
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<ExpensiveComponent items={items} filter={filter} />
</div>
);
}useCallback - 函数缓存
import React, { useState, useCallback, memo } from 'react';
const ChildComponent = memo(({ onClick, name }) => {
console.log('ChildComponent rendered');
return <button onClick={onClick}>{name}</button>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []); // 空依赖数组,函数永远不会重新创建
const handleNameChange = useCallback((e) => {
setName(e.target.value);
}, []);
return (
<div>
<input value={name} onChange={handleNameChange} />
<ChildComponent onClick={handleClick} name="Click me" />
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
</div>
);
}useRef - DOM 引用和可变值
DOM 引用
import React, { useRef, useEffect } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<div>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</div>
);
}保存可变值
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef();
useEffect(() => {
intervalRef.current = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
const stopTimer = () => {
clearInterval(intervalRef.current);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={stopTimer}>Stop</button>
</div>
);
}自定义 Hooks
数据获取 Hook
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(url);
const result = await response.json();
if (!cancelled) {
setData(result);
}
} catch (err) {
if (!cancelled) {
setError(err);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
};
fetchData();
return () => {
cancelled = true;
};
}, [url]);
return { data, loading, error };
}
// 使用
function UserProfile({ userId }) {
const { data: user, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return <div>User not found</div>;
return <div>{user.name}</div>;
}本地存储 Hook
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// 使用
function App() {
const [name, setName] = useLocalStorage('name', '');
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
</div>
);
}表单处理 Hook
function useForm(initialValues) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setValues(prev => ({
...prev,
[name]: value
}));
// 清除对应字段的错误
if (errors[name]) {
setErrors(prev => ({
...prev,
[name]: ''
}));
}
};
const handleSubmit = (onSubmit) => (e) => {
e.preventDefault();
onSubmit(values);
};
const setError = (field, message) => {
setErrors(prev => ({
...prev,
[field]: message
}));
};
const reset = () => {
setValues(initialValues);
setErrors({});
};
return {
values,
errors,
handleChange,
handleSubmit,
setError,
reset
};
}
// 使用
function ContactForm() {
const { values, errors, handleChange, handleSubmit, setError } = useForm({
name: '',
email: '',
message: ''
});
const onSubmit = (data) => {
console.log('Form submitted:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
name="name"
value={values.name}
onChange={handleChange}
placeholder="Name"
/>
{errors.name && <span>{errors.name}</span>}
<input
name="email"
value={values.email}
onChange={handleChange}
placeholder="Email"
/>
{errors.email && <span>{errors.email}</span>}
<button type="submit">Submit</button>
</form>
);
}Hooks 规则
1. 只在顶层调用 Hooks
// ❌ 错误 - 在条件语句中调用
function Example() {
if (someCondition) {
const [count, setCount] = useState(0); // 错误!
}
}
// ✅ 正确 - 在顶层调用
function Example() {
const [count, setCount] = useState(0);
if (someCondition) {
// 其他逻辑
}
}2. 只在 React 函数中调用 Hooks
// ✅ 正确 - 在 React 函数组件中
function MyComponent() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
// ✅ 正确 - 在自定义 Hook 中
function useCustomHook() {
const [count, setCount] = useState(0);
return [count, setCount];
}
// ❌ 错误 - 在普通函数中
function regularFunction() {
const [count, setCount] = useState(0); // 错误!
}性能优化技巧
避免不必要的重新渲染
import React, { memo, useMemo, useCallback } from 'react';
const ExpensiveChild = memo(({ data, onClick }) => {
console.log('ExpensiveChild rendered');
const processedData = useMemo(() => {
return data.map(item => item * 2);
}, [data]);
return (
<div>
{processedData.map(item => (
<div key={item} onClick={() => onClick(item)}>
{item}
</div>
))}
</div>
);
});
function Parent() {
const [count, setCount] = useState(0);
const [data] = useState([1, 2, 3, 4, 5]);
const handleClick = useCallback((item) => {
console.log('Clicked:', item);
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<ExpensiveChild data={data} onClick={handleClick} />
</div>
);
}