# React Hooks
# Hooks 简介
Hooks 是 React 16.8 引入的特性,它允许在函数组件中使用状态和其他 React 特性,而无需编写类组件。Hooks 的出现解决了类组件存在的一些问题,并使函数组件的功能得到了极大的增强。
# 为什么需要 Hooks
类组件的问题:
- 组件逻辑难以复用(高阶组件和 render props 模式冗长复杂)
- 复杂组件难以理解(生命周期方法混杂业务逻辑和副作用)
- this 关键字增加理解负担(绑定事件处理器需要额外处理)
Hooks 的优势:
- 无需学习类的复杂概念(如 this)
- 更好的逻辑复用(通过自定义 Hooks)
- 将相关逻辑组织在一起(按功能分组而非生命周期分散)
- 函数式编程风格(更清晰、更容易测试)
# 基础 Hooks
# useState - 状态管理
import { useState } from 'react';
function Counter() {
// 声明一个叫 count 的 state 变量,初始值为 0
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useState 的特点:
- 返回一个状态值和一个更新函数
- 更新函数可以接收新值或函数(函数接收先前的值并返回新值)
- 初始状态可以是简单值或复杂对象
- 多次调用 useState 可以使用多个状态变量
函数式更新:
// 不依赖于之前的 state
setCount(count + 1);
// 依赖于之前的 state(推荐方式)
setCount(prevCount => prevCount + 1);
使用对象状态:
const [user, setUser] = useState({ name: '', age: 0 });
// 更新部分状态(需要手动合并)
setUser(prevUser => ({ ...prevUser, name: 'John' }));
# useEffect - 副作用处理
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 类似于 componentDidMount 和 componentDidUpdate
useEffect(() => {
document.title = `You clicked ${count} times`;
// 返回清理函数(类似 componentWillUnmount)
return () => {
document.title = 'React App';
};
}, [count]); // 仅在 count 更改时执行
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect 的特点:
- 默认情况下,每次渲染后都会执行
- 第二个参数是依赖数组,只有依赖变化时才会重新执行
- 返回一个清理函数,在组件卸载或下一次 effect 执行前调用
- 可以在同一组件中多次使用
常见用例:
- 数据获取
- 事件监听
- DOM 操作
- 订阅设置
依赖数组说明:
[]
:仅在挂载和卸载时执行(类似 componentDidMount/componentWillUnmount)[dep1, dep2]
:在 dep1 或 dep2 变化时执行- 不提供依赖数组:每次渲染后执行
# useContext - 上下文管理
import { createContext, useContext } from 'react';
// 创建上下文
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return <ThemedButton />;
}
function ThemedButton() {
// 使用上下文
const theme = useContext(ThemeContext);
return <button className={theme}>I am styled by theme context!</button>;
}
useContext 的特点:
- 接收一个 Context 对象作为参数
- 返回该 Context 的当前值
- 当 Provider 更新时,使用该 Context 的组件会重新渲染
- 可以消费多个 Context
# 额外的 Hooks
# useReducer - 复杂状态逻辑
import { useReducer } from 'react';
// 定义 reducer 函数
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
// 使用 reducer
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
useReducer 的特点:
- 适合处理复杂的状态逻辑
- 基于 action 的状态更新方式,类似 Redux
- 可以将状态逻辑与 UI 分离
- 当状态逻辑较复杂时,相比 useState 更适用
# useCallback - 记忆回调函数
import { useState, useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
// 只有当 count 变化时,handleClick 才会重新创建
const handleClick = useCallback(() => {
console.log(`Clicked, count is: ${count}`);
}, [count]);
return <ChildComponent onClick={handleClick} />;
}
// 使用 React.memo 避免不必要的重渲染
const ChildComponent = React.memo(({ onClick }) => {
console.log('ChildComponent rendered');
return <button onClick={onClick}>Click me</button>;
});
useCallback 的特点:
- 返回一个记忆化的回调函数
- 只有依赖项变化时才会重新创建函数
- 用于优化传递给子组件的回调函数
- 通常与 React.memo 一起使用效果更佳
# useMemo - 记忆计算结果
import { useState, useMemo } from 'react';
function ExpensiveCalculation({ a, b }) {
// 只有当 a 或 b 变化时,result 才会重新计算
const result = useMemo(() => {
console.log('Computing result...');
return a * b * 1000; // 假设这是一个复杂计算
}, [a, b]);
return <div>Result: {result}</div>;
}
useMemo 的特点:
- 记忆计算结果,避免每次渲染时重复计算
- 只有依赖项变化时才会重新计算
- 适用于计算量大的操作
- 可以用来缓存对象,避免不必要的渲染
# useRef - 引用值/DOM
import { useRef, useEffect } from 'react';
function TextInputWithFocusButton() {
// 创建一个 ref
const inputRef = useRef(null);
// 点击按钮时聚焦输入框
const focusInput = () => {
inputRef.current.focus();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</>
);
}
useRef 的两个主要用途:
引用 DOM 元素:
- 访问 DOM 节点进行操作(如聚焦、测量等)
- 与第三方 DOM 库集成
保存可变值:
function Timer() { const intervalRef = useRef(null); useEffect(() => { intervalRef.current = setInterval(() => { // 执行某些操作 }, 1000); return () => { clearInterval(intervalRef.current); }; }, []); // ... }
# React 18 中的新 Hooks
# useTransition - 非阻塞更新
import { useState, useTransition } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
// 立即更新输入框(高优先级)
setQuery(e.target.value);
// 将搜索结果更新标记为低优先级
startTransition(() => {
// 这里可能是一个耗时的操作
const searchResults = performSearch(e.target.value);
setResults(searchResults);
});
};
return (
<>
<input value={query} onChange={handleChange} />
{isPending ? <div>Loading...</div> : (
<ul>
{results.map(result => (
<li key={result.id}>{result.name}</li>
))}
</ul>
)}
</>
);
}
useTransition 的特点:
- 将状态更新标记为非紧急
- 允许其他更新打断当前更新
- 提供 isPending 标志指示过渡状态
- 用于改善大型状态更新的用户体验
# useDeferredValue - 延迟值
import { useState, useDeferredValue } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
// 创建查询的延迟版本
const deferredQuery = useDeferredValue(query);
// 使用延迟值渲染结果
// 只有当 deferredQuery 变化时才会重新计算
const results = useMemo(
() => performSearch(deferredQuery),
[deferredQuery]
);
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
<Results results={results} />
</>
);
}
useDeferredValue 的特点:
- 创建一个延迟版本的值
- 当原始值变化时,会先保持旧值,然后在后台更新
- 适用于防止大型列表或复杂组件的渲染阻塞用户输入
- 与 useTransition 类似,但更适合处理接收到的 props
# 自定义 Hooks
自定义 Hooks 是 React 中重用状态逻辑的主要方式。它们是普通的 JavaScript 函数,名称以 "use" 开头,可以调用其他 Hooks。
# 例子:useLocalStorage
import { useState, useEffect } from 'react';
// 自定义 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 {
// 允许函数式更新
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// 使用自定义 Hook
function App() {
const [name, setName] = useLocalStorage('name', 'Bob');
return (
<div>
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
/>
</div>
);
}
# 例子:useFetch
import { useState, useEffect } from 'react';
// 自定义 Hook 用于数据获取
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
const signal = abortController.signal;
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const json = await response.json();
if (!signal.aborted) {
setData(json);
setError(null);
}
} catch (err) {
if (!signal.aborted) {
setError(err.message);
setData(null);
}
} finally {
if (!signal.aborted) {
setLoading(false);
}
}
};
fetchData();
return () => {
abortController.abort();
};
}, [url]);
return { data, loading, error };
}
// 使用自定义 Hook
function UserProfile({ userId }) {
const { data, loading, error } = useFetch(`https://api.example.com/users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h1>{data.name}</h1>
<p>Email: {data.email}</p>
</div>
);
}
# Hooks 最佳实践
只在顶层调用 Hooks
- 不要在循环、条件或嵌套函数中调用 Hooks
- 确保 Hooks 的调用顺序保持一致
只在 React 函数中调用 Hooks
- 在 React 函数组件中调用 Hooks
- 在自定义 Hooks 中调用 Hooks
- 不要在普通 JavaScript 函数中调用 Hooks
依赖数组的处理
- 包含所有 effect 中使用的外部变量
- 使用 ESLint 插件
eslint-plugin-react-hooks
检查依赖 - 考虑使用函数式更新或 useReducer 减少依赖
自定义 Hooks 设计
- 以 "use" 开头命名
- 函数签名设计要清晰直观
- 返回值设计简洁(数组或对象)
- 处理好内部的错误和加载状态
性能优化技巧
- 使用 useMemo 缓存复杂计算
- 使用 useCallback 记忆事件处理器
- 拆分状态,避免不必要的渲染
- 考虑使用 React.memo 包装函数组件
# 从类组件迁移到 Hooks
类组件 | Hooks |
---|---|
constructor | useState |
componentDidMount | useEffect(() => {}, []) |
componentDidUpdate | useEffect(() => {}) |
componentWillUnmount | useEffect(() => { return () => {} }, []) |
shouldComponentUpdate | React.memo |
静态方法和属性 | 常规函数和变量 |
# 结论
Hooks 彻底改变了 React 的组件编写方式,使函数组件成为了首选方案。通过 Hooks,开发者可以将相关逻辑组织在一起,而不是分散在各个生命周期方法中。自定义 Hooks 还提供了一种干净、强大的方式来重用状态逻辑,而不需要依赖复杂的高阶组件或 render props 模式。
随着 React 的发展,Hooks 已经成为 React 生态系统中不可或缺的部分,掌握它们对于任何 React 开发者来说都至关重要。