React Hooks
本章将介绍 React Hooks,这是 React 16.8 引入的新特性,允许在函数组件中使用状态和其他 React 特性。
什么是 Hooks?
Hooks 是让你在函数组件中"钩入"React 状态的函数。它让你在不编写类组件的情况下使用 state 和其他 React 特性。
useState
useState 是最常用的 Hook,用于在函数组件中添加状态。
基本用法
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
使用说明
useState返回一个包含两个元素的数组- 第一个元素是当前状态值
- 第二个元素是更新状态的函数
useState接受初始值作为参数
多个状态
function MultiState() {
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [isActive, setIsActive] = useState(false);
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="输入名字"
/>
<input
type="number"
value={age}
onChange={(e) => setAge(Number(e.target.value))}
/>
<button onClick={() => setIsActive(!isActive)}>
{isActive ? '激活' : '未激活'}
</button>
</div>
);
}
函数式更新
当新状态依赖旧状态时,使用函数式更新:
// 错误写法
setCount(count + 1); // 连续点击可能只增加1
// 正确写法
setCount(prevCount => prevCount + 1);
对象状态
function Form() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
return (
<form>
<input
name="username"
value={formData.username}
onChange={handleChange}
/>
<input
name="email"
value={formData.email}
onChange={handleChange}
/>
</form>
);
}
useEffect
useEffect 用于处理副作用,如数据获取、订阅、手动 DOM 操作等。
基本语法
useEffect(() => {
// 副作用逻辑
return () => {
// 清理函数(可选)
};
}, [依赖数组]); // 依赖项
组件挂载时执行
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then(res => res.json())
.then(result => setData(result));
}, []); // 空数组表示只在挂载时执行
return <div>{data ? JSON.stringify(data) : '加载中...'}</div>;
}
依赖项变化时执行
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// 当 userId 变化时重新获取数据
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]); // 依赖 userId
return user ? <div>{user.name}</div> : <div>加载中...</div>;
}
清理副作用
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
// 组件卸载时清理
return () => clearInterval(interval);
}, []);
return <div>计时器: {count}</div>;
}
常见的 useEffect 场景
数据获取
function ArticleList({ category }) {
const [articles, setArticles] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetch(`/api/articles?category=${category}`)
.then(res => res.json())
.then(data => {
setArticles(data);
setLoading(false);
});
}, [category]);
if (loading) return <div>加载中...</div>;
return (
<ul>
{articles.map(article => (
<li key={article.id}>{article.title}</li>
))}
</ul>
);
}
事件监听
function WindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>窗口大小: {size.width} x {size.height}</div>;
}
订阅
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const subscription = chat.subscribe(roomId, (newMessage) => {
setMessages(prev => [...prev, newMessage]);
});
return () => subscription.unsubscribe();
}, [roomId]);
return <MessageList messages={messages} />;
}
useContext
useContext 用于访问 React 上下文(Context)。
创建 Context
// themes.js
import { createContext } from 'react';
export const ThemeContext = createContext('light');
export const UserContext = createContext(null);
使用 Context
import { ThemeContext, UserContext } from './themes';
function App() {
return (
<UserContext.Provider value={{ name: 'Tom', age: 20 }}>
<ThemeContext.Provider value="dark">
<Layout />
</ThemeContext.Provider>
</UserContext.Provider>
);
}
function Layout() {
const theme = useContext(ThemeContext);
const user = useContext(UserContext);
return (
<div className={theme}>
<h1>欢迎, {user.name}</h1>
</div>
);
}
useRef
useRef 用于获取 DOM 元素或存储可变值。
获取 DOM 元素
function TextInput() {
const inputRef = useRef(null);
const handleFocus = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleFocus}>聚焦输入框</button>
</div>
);
}
存储可变值
function Timer() {
const countRef = useRef(0);
const [display, setDisplay] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
countRef.current += 1;
setDisplay(countRef.current);
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>计数: {display}</div>;
}
保持上次渲染的值
function PreviousValue() {
const [value, setValue] = useState('');
const previousValue = useRef('');
useEffect(() => {
previousValue.current = value;
}, [value]);
return (
<div>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<p>当前: {value}</p>
<p>上次: {previousValue.current}</p>
</div>
);
}
useMemo
useMemo 用于缓存计算结果,避免不必要的重复计算。
function ExpensiveComponent({ data, filter }) {
// 只有当 data 或 filter 变化时才重新计算
const filteredData = useMemo(() => {
return data.filter(item =>
item.name.includes(filter)
);
}, [data, filter]);
return (
<ul>
{filteredData.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
useCallback
useCallback 用于缓存函数,避免不必要的函数重建。
function ParentComponent() {
const [count, setCount] = useState(0);
// 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
console.log('点击');
setCount(c => c + 1);
}, []);
return <ChildComponent onClick={handleClick} />;
}
自定义 Hook
自定义 Hook 是以 use 开头的函数,用于封装可复用的逻辑。
示例:useLocalStorage
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// 使用
function App() {
const [name, setName] = useLocalStorage('name', '');
return <input value={name} onChange={e => setName(e.target.value)} />;
}
示例:useDebounce
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// 使用
function SearchInput() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
// 使用 debouncedQuery 进行搜索
console.log('搜索:', debouncedQuery);
}, [debouncedQuery]);
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}
Hooks 规则
- 只在顶层调用 Hook:不要在循环、条件或嵌套函数中调用 Hook
- 只在 React 函数中调用 Hook:不要在普通 JavaScript 函数中调用
- 自定义 Hook 以
use开头:这是一个约定,便于 lint 插件检查
小结
本章我们学习了:
- useState - 管理组件状态
- useEffect - 处理副作用
- useContext - 访问上下文
- useRef - 获取 DOM 和存储值
- useMemo - 缓存计算结果
- useCallback - 缓存函数
- 自定义 Hook - 封装可复用逻辑
练习
- 创建一个计数器组件,支持增加、减少、重置功能
- 创建一个自动搜索组件,使用 useDebounce 延迟搜索
- 创建一个主题切换组件,使用 useContext 管理主题状态
- 创建一个数据获取组件,处理加载状态和错误状态