介绍
当您开发 React 组件时,您可能会发现文件增长到数百行。当 useState、useEffect、API 查询和验证逻辑与渲染标记混合时,就会发生这种情况,使组件难以阅读和维护。
这种关注点的混合使得编写单元测试变得困难,并且在跨不同视图重用逻辑时增加了复制粘贴错误的风险。
在 React 中将 UI(表示)与业务逻辑(行为)分离的最佳方法是创建 自定义 Hooks。本指南概述了构建可重复使用的自定义挂钩的核心设计指南。
1. 什么是自定义 Hook?
自定义挂钩是一个标准 JavaScript 函数,其名称 以前缀 use 开头。在自定义挂钩中,您可以调用标准 React 挂钩,例如 useState、useEffect 和 useContext。
定制挂钩的优点
- **干净的组件(关注点分离):**组件严格专注于声明可视化 UI 结构(JSX),而数据获取、状态更新和副作用则封装在钩子内。
- 逻辑可重用性: 在多个同级组件之间共享辅助逻辑(如窗口大小观察器、表单管理或获取请求包装器),而无需重复代码。
2.重构示例:提取自定义hook
让我们看一个示例:一个从 API 获取用户列表并将其显示在屏幕上的组件。
重构前(UI 与逻辑混合)
import { useState, useEffect } from 'react';
export function UserList() {
const [users, setUsers] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://api.example.com/users')
.then(res => res.json())
.then(data => {
setUsers(data);
setIsLoading(false);
})
.catch(err => {
setError(err);
setIsLoading(false);
});
}, []);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error occurred.</p>;
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
- 问题: 该组件处理 API URL、请求逻辑、错误跟踪、加载指示器和 JSX 渲染。这使得独立测试 UI 和数据获取逻辑变得困难。
重构后(提取 useUsers)
首先,将数据获取逻辑隔离到一个单独的文件中,作为名为 useUsers 的自定义挂钩:
// useUsers.js
import { useState, useEffect } from 'react';
export function useUsers() {
const [users, setUsers] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let active = true;
fetch('https://api.example.com/users')
.then(res => res.json())
.then(data => {
if (active) {
setUsers(data);
setIsLoading(false);
}
})
.catch(err => {
if (active) {
setError(err);
setIsLoading(false);
}
});
// Clean up to prevent race conditions
return () => { active = false; };
}, []);
// Return only the data and states required by components
return { users, isLoading, error };
}
现在,更新组件以使用自定义挂钩:
// UserList.jsx
import { useUsers } from './useUsers';
export function UserList() {
// Access data and state via the custom hook
const { users, isLoading, error } = useUsers();
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error occurred.</p>;
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
- 结果: 组件现在变得精简且可读。它不需要知道 API 请求是如何配置的,您可以为 UI 组件和自定义挂钩编写独立的测试。
3. 设计自定义 Hook 的三个规则
规则 1:不返回 JSX(HTML 标记)
自定义挂钩应该管理逻辑状态,而不是返回可视组件。返回对象、数组、变量或回调函数而不是 JSX 元素(如 <div />),以保持钩子的灵活性。
规则 2:保持返回接口较小
仅返回组件管理其渲染生命周期所需的变量。避免返回钩子内声明的每个内部状态变量。
规则 3:嵌套和组合 Hook
您可以在您的挂钩中调用其他自定义挂钩。例如,您可以在 useUsers 内部调用通用 useFetch 挂钩,以进一步模块化您的代码。
结论
React 自定义挂钩有助于保持组件的可读性和可测试性:
- 将数据获取和状态管理逻辑提取到以
use为前缀的函数中。 - 封装副作用和获取状态,仅返回 UI 组件需要的变量。
- 处理钩子内的清理逻辑,以防止内存泄漏和竞争条件。
采用这些自定义钩子设计模式来构建更易于维护的 React 应用程序。

