跳到主要内容

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 规则

  1. 只在顶层调用 Hook:不要在循环、条件或嵌套函数中调用 Hook
  2. 只在 React 函数中调用 Hook:不要在普通 JavaScript 函数中调用
  3. 自定义 Hook 以 use 开头:这是一个约定,便于 lint 插件检查

小结

本章我们学习了:

  1. useState - 管理组件状态
  2. useEffect - 处理副作用
  3. useContext - 访问上下文
  4. useRef - 获取 DOM 和存储值
  5. useMemo - 缓存计算结果
  6. useCallback - 缓存函数
  7. 自定义 Hook - 封装可复用逻辑

练习

  1. 创建一个计数器组件,支持增加、减少、重置功能
  2. 创建一个自动搜索组件,使用 useDebounce 延迟搜索
  3. 创建一个主题切换组件,使用 useContext 管理主题状态
  4. 创建一个数据获取组件,处理加载状态和错误状态