理解 React 自定义钩子:无需共享状态即可重用逻辑
了解什么是 React 自定义钩子,它们如何在不同组件之间提取和共享带状态的逻辑,以及在构建自定义钩子时应当避免哪些常见错误。
简介
你是否曾发现自己在三个不同的组件中反复编写相同的 useState 和 useEffect 组合,每次只更改变量名?几乎每位 React 开发者都会遇到这种情况。虽然代码能够正常运行,但这意味着完全相同的逻辑在代码库中被重复存在,而且随着时间推移这些副本会在各自被独立修改后逐渐出现差异。
这正是 React 中自定义钩子旨在解决的场景。React 相关文档指出,将组件的行为提取到专门的、可重用的函数中,可以让应用程序的不同部分共享相同的状态行为,而无需反复复制底层实现。本文的这一部分将详细讲解自定义钩子究竟是什么、在实际代码中的运作方式,以及在你创建自己的自定义钩子之前需要了解的优势与陷阱。
什么是 React 自定义钩子
从根本上说,自定义钩子只不过是一个遵循严格命名规则的 JavaScript 函数:其名称必须以 use 开头。在函数内部,它可以调用诸如 useState 或 useEffect 这样的其他钩子,并将调用组件所需的任何值或函数返回出去。React 本身并不会对这类函数进行任何特殊的运行时处理——use 前缀纯粹是一种约定,但它非常重要,因为它能让 React 正确地执行钩子规则,同时让其他开发者能够立即明白该函数的用途。
自定义钩子与普通函数
标准的实用函数虽然能完成许多有用的工作,但由于位于 React 的渲染机制之外,无法调用其他钩子。而自定义钩子则没有这种限制。这正是它的优势:你可以将那些需要管理状态的行为——比如跟踪加载状态、监听浏览器事件或类似操作——封装成可复用的组件,同时还能顺利融入 React 的组件生命周期中。
React 自定义钩子的实际应用方式
一个具体的例子比任何抽象的解释都能更快地阐明这一点。
将逻辑从组件中提取出来
想象有三个互不相关的组件,它们都需要检测浏览器当前是否具有网络连接。如果没有共享的钩子,每个组件都得自行实现 useState 和 useEffect 来完成这一检测功能:
function StatusBadge() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const goOnline = () => setIsOnline(true);
const goOffline = () => setIsOnline(false);
window.addEventListener('online', goOnline);
window.addEventListener('offline', goOffline);
return () => {
window.removeEventListener('online', goOnline);
window.removeEventListener('offline', goOffline);
};
}, []);
return <span>{isOnline ? 'Online' : 'Offline'}</span>;
}
相反,你可以将那段逻辑提取到一个自定义钩子中,此后任何需要该状态的组件只需调用它即可:
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const goOnline = () => setIsOnline(true);
const goOffline = () => setIsOnline(false);
window.addEventListener('online', goOnline);
window.addEventListener('offline', goOffline);
return () => {
window.removeEventListener('online', goOnline);
window.removeEventListener('offline', goOffline);
};
}, []);
return isOnline;
}
// Usage in any component:
function StatusBadge() {
const isOnline = useOnlineStatus();
return <span>{isOnline ? 'Online' : 'Offline'}</span>;
}
各个组件不再需要自行设置事件监听器——它们只需调用 useOnlineStatus 来获取当前值即可。
无需共享状态即可共享状态逻辑
这一点常常让许多新手感到意外:自定义钩子共享的是行为,而非状态本身。如果两个独立的组件各自调用 useOnlineStatus,那么每个组件都会拥有该状态的独立副本。虽然它们执行的是相同的逻辑,但每个组件中的实际值是完全独立的——它们之间不存在共享或关联的状态。
使用 React 自定义钩子的核心优势
- 组件间的复用性:一旦逻辑被放入钩子函数中,应用中的任何组件都无需重复编写任何代码即可使用它,同时能确保该逻辑在所有使用场景下表现一致。
- 更简洁、更易理解的组件:将带状态的逻辑从组件中分离出来后,剩余的组件代码几乎无需处理状态管理相关的问题,因此新手能够更快地一目了然地理解其结构。
- 更易于独立测试:被提取出的逻辑可以完全独立于使用它的任何组件进行测试,这意味着只需验证一次,而无需在每个使用该逻辑的组件发生变化时都重新进行间接测试。
use前缀而已——就需要真正熟悉React的底层设计模式。那些希望从一开始就妥善处理这一问题的团队,有时会寻求外部力量,聘请专门的ReactJS开发人员,这些专家的经验有助于确保随着项目的扩展,钩子设计依然稳固。应避免的常见错误
即便出于良好意图编写的钩子,也可能会以几种可预见的方式出问题。
- 违反钩子使用规则:在条件语句中、循环内部,或从普通函数而非组件或其他钩子中调用钩子,会干扰 React 在多次渲染时保持状态一致的能力。这通常会导致一些难以追踪根源的异常且间歇性的错误。
- 在一个钩子中承担过多功能:将表单验证、网络请求和 UI 状态管理等功能都塞进同一个自定义钩子中,会使得该钩子更难测试、更难复用,也较难理解其逻辑,而如果将这些功能拆分到两个或三个更小、职责更明确的钩子中则会有所改善。
useState调用都值得单独成为一个钩子。如果某段逻辑只在一个地方被使用,将其抽离出来只会增加一层额外的间接调用,而不会带来实际好处。结论
React中的自定义钩子不仅有助于避免代码重复,更是维持日益庞大的代码库协调一致性的重要机制。一旦团队养成了识别值得提取为独立逻辑的习惯,组件的复杂性就会降低,错误也更容易被定位,整个应用的行为表现会更加统一,而不会在本应功能相同的组件之间逐渐出现差异。随着React应用规模和复杂度的不断提升,掌握何时以及如何构建可靠的自定义钩子已逐渐成为一项基础技能,而不再是仅限于高级开发者的专长。
如果您的团队正在处理一个充斥着重复逻辑的代码库,并希望为其建立真正的结构,那么咨询一家曾为其他大型项目实施过类似重构的React js开发公司可能是值得的。
相关阅读
- React Hooks的类型定义:useState、useEffect、useReducer及自定义Hooks — 了解如何在TypeScript中正确地为useState、useEffect、useReducer及自定义Hooks定义类型,以及何时选择TypeScript而非纯JavaScript会更有效。