【React Hooks】封装的艺术:如何编写高质量的 React 自-定义 Hooks

所属专栏: 《前端小技巧集合:让你的代码更优雅高效》
上一篇: 【React State】告别 useState 滥用:何时应该选择 useReducer
作者: 码力无边


引言:你的组件里,是否藏着一个“代码克隆人”?

嘿,各位在 React 世界里追求代码之美的道友们,我是码力无边

随着我们对 useStateuseEffectuseReducer 等基础 Hooks 的运用日渐纯熟,我们的组件功能也变得越来越强大。但与此同时,一个新的“心魔”开始悄然滋生——重复的逻辑

请审视一下你写的组件,是否也曾遇到过这样的场景:

  • 组件 A:需要从 localStorage 读取一个值,并在用户修改时写回。
  • 组件 B:也需要从 localStorage 读取另一个值,并在用户修改时写回。
  • 于是你把那段包含 useStateuseEffect 的逻辑,在 A 和 B 中复制粘贴了一遍。

又或者:

  • 组件 C:需要监听窗口的宽度变化,以实现响应式布局。
  • 组件 D:也需要监听窗口的宽度,来决定显示不同的内容。
  • 于是你又把那段包含 useStateuseEffect 来绑定 resize 事件的逻辑,在 C 和 D 中又复制粘贴了一遍。

这种“代码克隆”的行为,就像在你的项目中制造了一堆长得一模一样的“克隆人”。他们分散在各个角落,一旦你需要修改他们的行为逻辑(比如,给 localStorage 加上异常处理),你就必须找到所有的“克隆人”,逐一进行修改,极其繁琐且容易遗漏,是 bug 的温床。

在 Class Component 时代,我们用高阶组件 (HOC)渲染属性 (Render Props) 这些模式来解决逻辑复用问题。它们很强大,但也带来了“包装地狱 (Wrapper Hell)”和代码可读性下降等问题。

而 Hooks 的出现,为我们带来了一种更优雅、更直观、更强大的逻辑复用范式——自定义 Hooks (Custom Hooks)

自定义 Hook 不是什么新奇的魔法,它就是一个普通的 JavaScript 函数,其名称以 use 开头,函数内部可以调用其他的 Hooks (如 useState, useEffect 等)。它的出现,让我们能够将组件的状态逻辑从 UI 中抽离出来,变成一个独立的、可复用的单元。

今天,码力无边就将带你进入 Hooks 的封装艺术殿-堂,手把手教你如何编写高质量的自定义 Hooks,将你项目中的那些“代码克隆人”彻底消灭,让你的代码库变得干净、优雅、且充满“智慧”。

一、自定义 Hook 的“开光仪式”:命名与规则

在开始创造之前,我们必须先了解自定义 Hook 的两条“天规”:

  1. 名称必须以 use 开头:比如 useLocalStorage, useWindowSize。这不是一个随意的约定,而是 React Linter 用来检查 Hooks 规则(比如,不能在条件语句中调用 Hooks)的重要依据。不遵守这个规则,React 就无法判断你的函数是否是一个 Hook。
  2. 只能在 React 函数组件或其他的自定义 Hook 中调用:你不能在普通的 JavaScript 函数(非组件或非 Hook)中调用它。

好了,“开光仪式”结束,让我们开始创造第一个属于自己的 Hook!

二、实战一:打造你的“本地存储神器”——useLocalStorage

这是最经典、最实用的自定义 Hook 之一。

需求: 创建一个 Hook,它的用法和 useState 几乎一样,但它能自动将状态持久化到 localStorage 中。

第一步:识别重复逻辑
在没有自定义 Hook 之前,我们的组件可能是这样写的:

function UserProfile() {const [name, setName] = useState(() => {// 从 localStorage 初始化 stateconst savedName = window.localStorage.getItem('username');return savedName || 'Guest';});// 当 name 变化时,同步到 localStorageuseEffect(() => {window.localStorage.setItem('username', name);}, [name]);// ... render logic
}

这段“从 localStorage 初始化,并用 useEffect 同步回去”的逻辑,就是我们要抽离的“重复基因”。

第二步:创建自定义 Hook
我们来创建一个 useLocalStorage.js 文件:

import { useState, useEffect } from 'react';function useLocalStorage(key, initialValue) {// 1. 创建一个 state,其初始化逻辑和之前组件里的一样const [storedValue, setStoredValue] = useState(() => {try {const item = window.localStorage.getItem(key);// 如果 localStorage 中有值,就用它;否则,用初始值return item ? JSON.parse(item) : initialValue;} catch (error) {// 如果解析出错,也返回初始值console.error(error);return initialValue;}});// 2. 使用 useEffect 来监听 storedValue 的变化useEffect(() => {try {// 当 storedValue 变化时,将其序列化并存入 localStoragewindow.localStorage.setItem(key, JSON.stringify(storedValue));} catch (error) {console.error(error);}}, [key, storedValue]); // 依赖项是 key 和 value// 3. 返回一个和 useState 签名一样的数组return [storedValue, setStoredValue];
}export default useLocalStorage;

第三步:在组件中使用
现在,我们的组件可以变得极其简洁:

import useLocalStorage from './useLocalStorage';function UserProfile() {// 一行代码,搞定状态和持久化!const [name, setName] = useLocalStorage('username', 'Guest');return (<div><input type="text" value={name} onChange={e => setName(e.target.value)} /><p>Hello, {name}!</p></div>);
}function ThemeSwitcher() {// 在另一个组件中复用!const [theme, setTheme] = useLocalStorage('theme', 'light');return (<div className={theme}><button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Switch to {theme === 'light' ? 'dark' : 'light'} mode</button></div>);
}

看到了吗? 我们成功地将状态管理的复杂逻辑(初始化、try...catchuseEffect 同步)封装进了 useLocalStorage 这个黑盒子里。组件的使用者,只需要像使用 useState 一样,简单地调用它,就能获得“状态 + 持久化”的超能力。这就是自定义 Hook 的魔力!

三、实战二:你的“响应式布局之眼”——useWindowSize

需求: 创建一个 Hook,实时返回当前浏览器窗口的宽度和高度。

第一步:识别重复逻辑
获取窗口尺寸的逻辑通常是:

  1. useState 存储 widthheight
  2. useEffect 在组件挂载时绑定 window.resize 事件监听。
  3. 在事件处理函数中,用 setState 更新尺寸。
  4. 非常重要:在 useEffect清理函数中,移除事件监听,防止内存泄漏。

第二步:创建自定义 Hook
我们来创建一个 useWindowSize.js 文件:

import { useState, useEffect } from 'react';function useWindowSize() {const [windowSize, setWindowSize] = useState({width: undefined,height: undefined,});useEffect(() => {// 1. 定义事件处理函数function handleResize() {setWindowSize({width: window.innerWidth,height: window.innerHeight,});}// 2. 添加事件监听window.addEventListener('resize', handleResize);// 3. 首次调用,以获取初始尺寸handleResize();// 4. 返回一个清理函数,在组件卸载时移除监听return () => window.removeEventListener('resize', handleResize);}, []); // 空依赖数组,确保 effect 只在挂载和卸载时运行return windowSize;
}export default useWindowSize;

第三步:在组件中使用

import useWindowSize from './useWindowSize';function ResponsiveComponent() {// 一行代码,获得响应式的窗口尺寸!const { width, height } = useWindowSize();if (width < 768) {return <div>我是移动端布局</div>;}return (<div><h1>我是桌面端布局</h1><p>当前窗口尺寸: {width} x {height}</p></div>);
}

这个 Hook 将所有关于事件监听、状态更新和内存清理的底层细节都封装了起来,让组件可以专注于如何使用这些数据,而不是如何获取它们。这完美体现了“关注点分离”的原则。

四、编写高质量自定义 Hook 的“心法”

一个好的自定义 Hook,应该像 React 内置的 Hook 一样,具备良好的设计和DX (开发者体验)。

  1. 明确的输入和输出

    • 输入 (参数): 参数应该清晰明了,就像 useLocalStoragekeyinitialValue
    • 输出 (返回值): 返回值的设计很重要。
      • 如果你的 Hook 像 useState 一样,返回一个状态值和一个更新函数,那么返回一个数组 [value, setValue] 是一个很好的约定,因为它允许调用者自由命名。
      • 如果你的 Hook 返回多个独立的值(比如 useWindowSizewidthheight),那么返回一个对象 { width, height } 更具可读性,并且未来更容易扩展(增加新返回值而不会破坏现有用法)。
  2. 保持纯粹和可预测

    • Hook 内部的逻辑应该主要围绕着 React 的状态和生命周期。避免在 Hook 内部执行一些不可预测的、与组件状态无关的副作用。
    • 遵循 Hooks 的规则,不要在循环或条件中调用其他 Hooks。
  3. 通用性和可配置性

    • 设计 Hook 时,思考它是否能被用在更多场景。比如,我们的 useLocalStorage 就可以处理任何可序列化的数据,而不仅限于字符串。
    • 适时地提供配置选项作为参数,让 Hook 更灵活。
  4. 自给自足,不暴露实现细节

    • 一个好的 Hook 应该是一个“黑盒子”。它管理自己的所有内部状态和副作用(比如事件监听的清理)。调用者无需关心其内部实现。

写在最后:自定义 Hook 是你的“超能力工厂”

自定义 Hooks 是 React 赋予我们开发者的一项“超能力”。它让我们能够超越组件的界限,去创造、组合和分享我们自己的“状态逻辑积木”。

当你下一次在不同的组件间复制粘贴一段 useState + useEffect 的代码时,请停下来。这正是你创造一个新 Hook 的信号!

将重复的逻辑封装成一个自定义 Hook,就像是建立了一座“超能力工厂”。从此以后,任何组件想要获得这项“超能力”,只需要去工厂里“领取”(import) 一下即可。你的代码库将因此变得更加模块化、可维护性更高,你的开发效率也会得到质的飞跃。

这,就是封装的艺术,也是 React Hooks 设计哲学的精髓所在。


专栏预告与互动:

我们已经学会了封装可复用的逻辑。但在大型应用中,我们还需要在组件树的“远房亲戚”之间共享状态。一层层地 props drilling (属性钻孔) 显然不是个好主意。

下一篇,我们将深入探讨 React 的官方“跨层传功”解决方案——Context API。你将学习如何使用它来避免 props drilling,并探讨一个经典问题:Context API 是性能杀手吗?我们又该如何正确地优化它?

感觉码力无边的“封装艺术”让你对 Hooks 有了全新的认识?别忘了点赞、收藏、关注,你的每一次支持,都是我建造下一座“超能力工厂”的图纸和动力!

今日挑战: 我们可以结合之前学过的知识,创造一个 useDebounce Hook 吗?这个 Hook 接收一个值和一个延迟时间,返回一个经过防抖处理后的值。把你的实现思路或代码片段分享在评论区,让我们一起打造这个非常实用的 Hook!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/93742.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/93742.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/93742.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为GaussDB的前世今生:国产数据库崛起之路

在数据库领域&#xff0c;华为GaussDB已成为一颗耀眼的明星&#xff0c;为企业核心业务数字化转型提供坚实的数据底座。但这并非一蹴而就&#xff0c;其背后是长达二十余年的技术沉淀、战略投入与持续创新。本文将深入探寻华为GaussDB的历史沿革与核心技术细节&#xff0c;展现…

数据结构初阶(16)排序算法——归并排序

2.4 归并排序 归并排序&#xff08;Merge Sort&#xff09;是基于分治思想的经典排序算法。核心逻辑&#xff1a; 分而治之——把复杂排序问题拆分成简单子问题解决&#xff0c;再合并子问题的结果。联系链表的合并&#xff1a;两个有序链表l1、l2创建新链表l3&#xff08;带头…

MATLAB实现匈牙利算法求解二分图最大匹配

MATLAB实现匈牙利算法求解二分图最大匹配 匈牙利算法&#xff08;也称为Kuhn-Munkres算法&#xff09;是解决二分图最大匹配问题的经典算法。 代码 function [matching, max_match] hungarian_algorithm(adjMatrix)% HUNGARIAN_ALGORITHM 实现匈牙利算法求解二分图最大匹配% 输…

自定义table

更好<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"utf-8"><title>数据表格</title><style>* {margin: 0;padding: 0;box-sizing: border-box;font-size: 14px;}html,body {width: 100%;height: 100%…

面向R语言用户的Highcharts

如果您喜欢使用 R 进行数据科学创建交互式数据可视化&#xff0c;那么请你收藏。今天&#xff0c;我们将使用折线图、柱状图和散点图来可视化资产回报。对于我们的数据&#xff0c;我们将使用以下 5 只 ETF 的 5 年月回报率。 SPY (S&P500 fund)EFA (a non-US equities fun…

【测试工具】OnDo SIP Server--轻松搭建一个语音通话服务器

前言 Ondo SIP Server 是一款基于 SIP(Session Initiation Protocol)协议的服务器软件&#xff0c;主要用于实现 VoIP(Voice over IP)通信&#xff0c;支持语音通话、视频会议等多媒体会话管理&#xff0c;非常适合学习和测试VoIP的基本功能。本文介绍Ondo SIP Server的安装、…

疯狂星期四文案网第42天运营日记

网站运营第42天&#xff0c;点击观站&#xff1a; 疯狂星期四 crazy-thursday.com 全网最全的疯狂星期四文案网站 运营报告 今日访问量 今日搜索引擎收录情况 网站优化点 优化一些发现的seo错误 增加颜文字栏目 增加了一些tag

使用空模型实例调用辅助函数,确定在量化过程中哪些层会被跳过(43)

在Facebook的OPT-350M中,模型的头部(lm_head)与解码器的嵌入标记层(decoder.embed_tokens)共享其权重。 print(model.model.decoder.embed_tokens) print(model.lm_head)输出结果 Embedding(50272, 512

从0-1使用Fastmcp开发一个MCP服务,并部署到阿里云百炼 -持续更新中

目的&#xff1a; 在本地使用fastmcp开发一个mcp,然后注册到阿里云的百炼里面。实现在百炼里面创建智能体的时候直接引用自己开发的MCP 已完成&#xff1a;本地环境安装 待完成&#xff1a; 1.根据需求实现一个MCP中可以调用某应用的多个API即 mcp.tool()、mcp.prompt()、接入大…

设计模式之汇总

设计模式 零、设计原则 0.1 单一职责 0.2 接口隔离 0.3 开闭原则 0.4 依赖倒置0.5 迪米特法则&#xff0c;最小知道原则用户关机 只和朋友通信 朋友条件&#xff1a; 1&#xff09;当前对象本身&#xff08;this&#xff09; 2&#xff09;以参量形式传入到当前对象方法中的对象…

第6章 Decoder与Encoder核心组件

前言 Netty从底层Java通道读取ByteBuf二进制数据&#xff0c;传入Netty通道的流水线&#xff0c;随后开始入站处理。在入站处理过程中&#xff0c;需要将ByteBuf二进制类型解码成Java POJO对象。这个解码过程可以通过Netty的Decoder&#xff08;解码器&#xff09;去完成。 在…

[已解决]当启动 Spring Boot 应用时出现 Using generated security password xxx提示

当启动 Spring Boot 应用时出现 Using generated security password xxx提示当启动 Spring Boot 应用时出现 Using generated security password xxx提示&#xff0c;这是 Spring Security 自动配置的默认行为&#xff0c;通常发生在你​​未自定义安全配置​​但引入了 Spring…

自动分析需求,PRD 生成只需 SOLO 一步!

资料来源&#xff1a;火山引擎-开发者社区 写不清需求&#xff1f;PRD 难产&#xff1f;开发总跑偏&#xff1f;这些痛点&#xff0c;SOLO 来解决。 TRAE SOLO 是行业首个 Context Engineer。它不止协助编码&#xff0c;更能基于精准上下文理解和工具调用&#xff0c;从构思、…

物联网软件开发过程中,数据流图(DFD),用例图,类图,活动图,序列图,状态图,实体关系图(ERD),BPMN(业务流程建模)详解分析

概述软件开发过程中&#xff0c;特别是在物联网&#xff08;IoT&#xff09;场景中&#xff0c;数据流图&#xff08;DFD&#xff09;、UML图&#xff08;包括用例图、类图、活动图、序列图、状态图&#xff09;、实体关系图&#xff08;ERD&#xff09;和业务流程建模&#xf…

Mac(一)常用的快捷键整理

目录1、系统操作与窗口管理2、应用与窗口切换3、常规编辑操作4、文本导航与光标控制✏️5、文本格式与文档功能&#xff08;支持应用中&#xff09;6、截图快捷键7、Safari 浏览器快捷键8、Finder 快捷键&#xff08;文件管理&#xff09;9、Fn / Globe 功能键&#xff08;部分…

HAProxy使用方法以及和LVS区别

HAProxy简介HAProxy是法国开发者 威利塔罗(Willy Tarreau) 在2000年使用C语言开发的一个开源软件 是一款具备高并发(万级以上)、高性能的TCP和HTTP负载均衡器 支持基于cookie的持久性&#xff0c;自动故障切换&#xff0c;支持正则表达式及web状态统计LVS 与 HAProxy 的核心区别…

超越“小作文”:大模型指令设计的进阶之路——优化知识信噪比

文章摘要&#xff1a;你是否认为&#xff0c;给大模型的指令&#xff08;Prompt&#xff09;写得越详细越好&#xff1f;真的是信息越多&#xff0c;模型就越懂你吗&#xff1f;本文将深入探讨一个反直覺的觀點&#xff1a;初級的指令設計專注於資訊的堆砌&#xff0c;而高階的…

elasticsearch-集成prometheus监控(k8s)

一. 简介&#xff1a; 关于elasticsearch的简介和部署&#xff0c;可以参考单独的文章elasticsearch基础概念与集群部署-CSDN博客&#xff0c;这里就不细说了。这里只讲讲如何在k8s中部署export并基于prometheus做es的指标采集。 二. 实现方式&#xff1a; 首先我们需要先部署…

贪心算法(Greedy Algorithm)详解

一、什么是贪心算法&#xff1f; 贪心算法是一种算法设计范式&#xff0c;指在解决问题时&#xff0c;依赖于每次选择最优的局部解&#xff0c;以期最终得到全局最优解。贪心算法的关键特点是&#xff1a; 局部最优选择&#xff1a;每个阶段选择当前看起来最好的选择&#xff0…

电梯的构造|保养|维修视频全集_电梯安全与故障救援(课程下载)

课程下载&#xff1a;https://download.csdn.net/download/m0_66047725/91699586 电梯原理与维修视频教程 相关简介: 电梯现在运用的非常广泛,比如大型商场,建筑工地,特别是现在建造的很多高楼、商品房,基本都是安装了电梯。电梯维保不力是导致电梯运行中安全事故频发的主要原…