写在前面

        在函数式组件主导的 React 项目中,高阶组件(HOC)并非首选推荐,更建议优先使用 Hooks来实现复用逻辑。核心原因是 HOC 存在固有的设计缺陷,而 Hooks 能更优雅、简洁地解决相同问题,同时避免 HOC 的痛点。

        本章节我们将分别介绍二者,并重点体会 Hooks 在函数式组件项目中的优势。

目录

HOC

一、是什么?

二、HOC 在函数组件项目中的 “不推荐原因”:痛点

1. “包装地狱”(Wrapper Hell):组件层级冗余

2. 逻辑复用 “不够灵活”:强耦合于组件结构

Hooks

一、是什么?

二、判断标准

三、核心作用

四、使用时注意事项

代码对比:HOC vs Hooks

1. HOC

2. Hooks

总结


HOC

一、是什么?

HOC(Higher-Order Component,高阶组件)是 React 早期(Class 组件时代)实现逻辑复用的核心方案,本质是 “一个接收组件、返回新组件的函数”。

它的核心价值是 “抽离通用逻辑(如权限、数据请求、状态管理)”,让多个组件复用这些逻辑。

例如:

// 一个封装“登录状态判断”的 HOC
const withAuth = (WrappedComponent) => {return (props) => {const isLogin = localStorage.getItem('token');if (!isLogin) return <Redirect to="/login" />;// 给被包裹组件注入 props 或增强逻辑return <WrappedComponent isLogin={isLogin} {...props} />;};
};// 使用:给需要登录的组件注入登录逻辑
const Profile = withAuth(({ isLogin }) => <div>欢迎回来</div>);

(参考文章:React 高阶组件-CSDN博客)

二、HOC 在函数组件项目中的 “不推荐原因”:痛点

HOC 的设计是为 Class 组件服务的,在函数组件 + Hook 的生态中,其缺陷会被放大,导致代码复杂度提升:

1. “包装地狱”(Wrapper Hell):组件层级冗余

每使用一个 HOC,就会给组件套一层 “容器组件”(如上面的 withAuth 会返回一个匿名函数组件)。如果多个 HOC 叠加(如 withAuth(withData(withTheme(Component)))),最终的组件树会变得异常冗余:

withAuth → withData → withTheme → 目标组件

这种层级不仅增加 React DevTools 调试难度(需要层层穿透才能找到目标组件),还可能导致 props 透传问题,导致状态来源不清晰,props 混叠风险(若 HOC 未正确转发 props,会丢失上层传递的属性)。

2. 逻辑复用 “不够灵活”:强耦合于组件结构

HOC 是 “组件级别的复用”—— 它只能将逻辑封装到 “整个组件” 中,无法针对组件内的某部分逻辑(如一个按钮的点击处理、一段数据的格式化)进行复用。

例如:若两个组件都需要 “格式化时间” 的逻辑,用 HOC 只能将 “时间格式化” 封装成一个 HOC,再包裹整个组件;但用自定义 Hook(如 useFormatTime),可以直接在组件内调用,只复用这一段逻辑,无需包装整个组件:

// 自定义 Hook:复用“时间格式化”逻辑(更灵活)
const useFormatTime = (time) => {return new Date(time).toLocaleString();
};// 组件内直接使用,无需包装
const Card1 = ({ createTime }) => {const formatTime = useFormatTime(createTime);return <div>创建时间:{formatTime}</div>;
};const Card2 = ({ updateTime }) => {const formatTime = useFormatTime(updateTime);return <div>更新时间:{formatTime}</div>;
};

Hooks

一、是什么?

React 官方文档对自定义 Hook 的定义是:

自定义 Hook 是一个函数,其名称以 "use" 开头,函数内部可以调用其他的 Hook(内置 Hook 或其他自定义 Hook)。

注意这里是 “可以调用”,不是 “必须调用” 内置 Hook。

二、判断标准

判断一个函数是不是自定义 Hook,关键看两个点:

  1. 名称是否以 use 开头(强制规则);
  2. 是否用于复用 React 组件的逻辑(核心目的)。

至于是否包含 useState 等内置 Hook,只是 “自定义 Hook 能实现的功能范围” 的区别 ——

  • 有内置 Hook,说明它能处理状态 / 副作用;
  • 没有内置 Hook,说明它处理的是纯计算逻辑,但依然符合自定义 Hook 的定义,而且可以为未来扩展留空间:如果后续逻辑需要添加状态(useState)、副作用(useEffect)或缓存(useMemo),无需重构调用方式,直接在函数内部添加即可,组件使用时完全无感知。

三、核心作用

Hook 是 React 16.8 引入的特性,本质是让函数组件能够使用状态(State)和其他 React 特性(如生命周期、上下文等)的函数,核心价值体现在两方面:

  1. 逻辑复用更简洁
    解决了 Class 组件中 “逻辑复用需依赖高阶组件(HOC)或 render props 导致的层级冗余” 问题。通过自定义 Hook,可将组件间的通用逻辑(如数据请求、表单处理、定时器管理等)抽离成独立函数,直接在多个组件中复用,无需嵌套组件。

    例如:用 useFetch 封装数据请求逻辑,在任何函数组件中直接调用即可复用,无需通过 HOC 包装。

  2. 函数组件功能完善化
    让函数组件从 “纯展示” 升级为 “可拥有状态和副作用” 的完整组件,无需再编写 Class 组件。函数组件的代码更简洁、可读性更强,避免了 Class 组件中 this 指向混乱、生命周期函数逻辑混杂等问题。

四、使用时注意事项

React 对 Hook 的使用有严格规则,违反规则可能导致组件状态异常或逻辑错误,需特别注意:

1. 只能在函数组件或自定义 Hook 中调用

原因:Hook 依赖 React 内部的 “调用栈” 追踪状态归属,只有在函数组件 / 自定义 Hook 中调用,才能确保状态与组件正确关联。

  • 禁止在 Class 组件中使用 Hook;
  • 禁止在普通 JavaScript 函数(非 Hook)中调用 Hook(如事件处理函数、定时器回调等)。


2. 只能在函数的顶层调用

禁止在条件判断(if)、循环(for)、嵌套函数(如 map 回调)中调用 Hook。

示例(错误):❌

const MyComponent = () => {if (someCondition) {const [count, setCount] = useState(0); // ❌ 不能在条件中调用}// ...
};

原因:React 依赖 Hook 的调用顺序来识别和关联状态。如果在条件 / 循环中调用,每次渲染时 Hook 的调用顺序可能变化,导致 React 无法正确匹配状态与 Hook。

3. 自定义 Hook 必须以 use 开头命名
例如 useFetchuseTimer,而非 fetchDatatimer

原因:这是 React 的强制约定,便于开发者识别 Hook,同时让 ESLint 插件(如 eslint-plugin-react-hooks)能自动检查 Hook 使用规则,避免错误。

4. 依赖数组的准确性(针对 useEffectuseMemo 等)
对于带依赖数组的 Hook(如 useEffect(fn, deps)),需确保依赖数组包含所有在 Hook 内部使用的 “外部变量”( props、状态、组件内定义的函数等)。

示例(错误):❌

const MyComponent = ({ id }) => {const [data, setData] = useState(null);useEffect(() => {fetch(`/api/${id}`).then(res => setData(res)); }, []); // ❌ 遗漏依赖 id,id 变化时不会重新请求
};

原因:依赖数组决定了 Hook 何时重新执行。遗漏依赖会导致 Hook 捕获旧值,引发逻辑错误;多余依赖则会导致不必要的重复执行,浪费性能。

5. 避免在 Hook 内部定义组件
禁止在 Hook 中定义函数组件,否则每次 Hook 调用都会创建新的组件类型,导致 React 卸载旧组件、重新挂载新组件(而非更新),丢失组件状态。

示例(错误):❌

const useCustomHook = () => {const InnerComponent = () => <div>Hello</div>; // ❌ 不应在 Hook 中定义组件return InnerComponent;
};

代码对比:HOC vs Hooks

1. HOC

当使用 withAuth(withData(withTheme(UserProfile))) 时,最终的代码会是这样的:

import React from 'react';// 1. 第一个HOC:处理主题
const withTheme = (Component) => {return (props) => {const theme = { color: 'blue', background: 'white' };return <Component {...props} theme={theme} />;};
};// 2. 第二个HOC:处理数据加载
const withData = (Component) => {return (props) => {const data = { user: 'John', age: 30 }; // 模拟API数据return <Component {...props} data={data} />;};
};// 3. 第三个HOC:处理权限验证
const withAuth = (Component) => {return (props) => {const isAuthenticated = true; // 模拟登录状态if (!isAuthenticated) {return <div>请先登录</div>;}return <Component {...props} isAuthenticated={isAuthenticated} />;};
};// 原始业务组件
const UserProfile = (props) => {return (<div style={{ color: props.theme.color }}>{props.isAuthenticated && (<div><h1>用户信息</h1><p>姓名:{props.data.user}</p><p>年龄:{props.data.age}</p></div>)}</div>);
};// 多个HOC叠加使用
const EnhancedUserProfile = withAuth(withData(withTheme(UserProfile)));// 最终渲染组件
function App() {return (<div><EnhancedUserProfile /></div>);
}

2. Hooks

import React, { useState } from 'react';// 1. 自定义Hook:处理主题
const useTheme = () => {const theme = { color: 'blue', background: 'white' };return theme;
};// 2. 自定义Hook:处理数据加载
const useData = () => {const data = { user: 'John', age: 30 }; // 模拟API数据return data;
};// 3. 自定义Hook:处理权限验证
const useAuth = () => {const [isAuthenticated] = useState(true); // 模拟登录状态return isAuthenticated;
};// 业务组件(直接使用Hook)
const UserProfile = () => {// 直接在组件中调用Hook获取所需功能const theme = useTheme();const data = useData();const isAuthenticated = useAuth();if (!isAuthenticated) {return <div>请先登录</div>;}return (<div style={{ color: theme.color }}><div><h1>用户信息</h1><p>姓名:{data.user}</p><p>年龄:{data.age}</p></div></div>);
};// 最终渲染组件
function App() {return (<div><UserProfile /></div>);
}

总结

维度HOC(高阶组件)Hooks(钩子函数)
优点1. 兼容 Class 组件和函数组件;
2. 逻辑封装边界清晰(基于组件隔离)
1. 代码更简洁,无组件嵌套冗余;
2. 逻辑与组件结合更紧密,无需通过 props 传递数据;
3. 支持细粒度逻辑拆分(一个组件可调用多个 Hook);
4. 学习成本更低(无需理解 “组件嵌套”“闭包陷阱” 等复杂概念)
缺点1. 易产生 “组件层级嵌套地狱”(多个 HOC 叠加导致 DevTools 中组件树混乱);
2. 逻辑传递依赖 props,易出现 “props 透传”(多层组件需手动传递 props);
3. 可能引发 “闭包陷阱”(HOC 捕获旧的 props/state);
4. 无法在组件内部动态切换 HOC 逻辑
1. 仅支持函数组件,不兼容 Class 组件;
2. 需严格遵循使用规则(如只能在顶层调用、依赖数组需准确);
3. 复杂逻辑的 Hook 可能存在 “依赖管理复杂” 问题(需精准维护 useEffect 依赖)

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

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

相关文章

【 苍穹外卖 | Day2】

1. 相关视频 Day2的全部视频集数 2. 学习记录 2.1 对象属性拷贝 当DTO与实体类或者VO对象之间的一个装换的时候&#xff0c;如果通过new创建对象&#xff0c;然后调用set方法进行属性赋值&#xff0c;不够方便&#xff0c;代码不够简洁。当属性过多时候&#xff0c;代码就会…

焊接自动化测试平台图像处理分析-模型训练推理

1、使用技术栈&#xff1a;jdk17/springboot/python/opencv/yolov8 2、JAVA环境搭建 JDK17下载安装&#xff1a;wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz 解压软件 tar -xf jdk-17.0.16_linux-x64_bin.tar.gz 配置全局变量 vim /etc/p…

【python实用小脚本-205】[HR揭秘]手工党逐行查Bug的终结者|Python版代码质量“CT机”加速器(建议收藏)

1. 场景故事 “作为HR&#xff0c;我曾用2小时逐行审阅50份Python简历项目&#xff0c;直到发现候选人的代码复杂度超标导致线上事故…” → 转折点&#xff1a;用麦凯布&#xff08;McCabe&#xff09;圈复杂度检测脚本&#xff0c;30秒扫描全仓库&#xff0c;现可100%拦截“高…

LeetCode - 1089. 复写零

题目 1089. 复写零 - 力扣&#xff08;LeetCode&#xff09; 思路 这道题我首先想到的是从前往后双指针&#xff0c;但是这样做会造成数据的覆盖&#xff0c;比如说下面的这个情况 所以解决的方法就是从后往前去复写&#xff0c;但是从后往前的话就要知道最后一个有效元素是…

c#中public类比博图

简单来说&#xff0c;**public 定义了“接口”或“引脚”**&#xff0c;就像你的FB块上的 Input, Output, InOut 管脚一样。它决定了外部的其他代码&#xff08;如另一个FB或OB1&#xff09;可以看到和操作这个块里的什么东西。让我用你最熟悉的博图概念来详细类比一下。---###…

K8s基于节点软亲和的高 CPU Pod 扩容与优先调度方案

场景与目标 集群节点&#xff1a;master&#xff08;4 核&#xff09;、node1&#xff08;16 核&#xff09;、node2&#xff08;16 核&#xff09;。目标&#xff1a;将一个高 CPU 消耗的工作负载横向扩展到 4 个实例&#xff0c;并通过**节点亲和性&#xff08;软亲和&#…

MySQL InnoDB 的锁机制

引言 锁是数据库管理并发访问的另一种核心机制&#xff0c;与 MVCC 相辅相成。本文将系统梳理 MySQL InnoDB 中锁的粒度、类型和工作原理&#xff0c;并深入探讨它如何与事务隔离级别配合&#xff0c;共同保障数据的一致性和完整性。 一、 锁的粒度&#xff1a;由粗到细 InnoD…

状态模式(State Pattern)——网络连接场景的 C++ 实战

一、为什么要用状态模式&#xff1f;在开发中&#xff0c;经常遇到“对象在不同状态下行为不同”的情况。最常见的写法是用一堆 if/else 或 switch 来判断状态&#xff0c;然后在不同分支里写逻辑。这样做有两个问题&#xff1a;状态增多后&#xff0c;条件分支会变得臃肿。修改…

使用csi-driver-nfs实现K8S动态供给

文章目录一、部署NFS二、k8s环境部署csi-nfs三、测试动态供给补充应用服务器IPnfs-server192.168.1.5k8s-master01192.168.1.1k8s-node01192.168.1.2k8s-node02192.168.1.3 一、部署NFS 1、在NFS服务端和k8s所有节点部署nfs-utils 因为客户端去挂载nfs服务端的共享目录时&…

【开题答辩全过程】以 基于ssm的房屋中介管理系统为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

MySQL主从复制之进阶延时同步、GTID复制、半同步复制完整实验流程

1.主从同步1.1主从同步原理是指将主库的DDL和DML操作通过二进制日志(binlog)传到从库服务器&#xff0c;然后在从库上对这些日志进行重新执行&#xff0c;从而使从库和主库数据保持一致1.2环境设置库名ip地址操作系统mysql版本主库msyql-master192.168.31.228rhel7.9源码安装my…

织信低代码:用更聪明的方式,把想法变成现实!

你有没有过这样的时刻&#xff1f;想亲手做一个应用&#xff0c;却因为“不会编码”而迟迟没有开始&#xff1b;或曾无奈地目睹公司里一个看似简单的需求&#xff0c;硬是耗费数月、投入大量人力反复开发……现在&#xff0c;有一类工具正在改变这一切。它叫低代码。而今天我们…

【序列晋升】28 云原生时代的消息驱动架构 Spring Cloud Stream的未来可能性

目录 一、Spring Cloud Stream是什么&#xff1f; 二、诞生背景与设计动机 2.1 微服务架构的挑战 2.2 Spring生态的发展 2.3 Spring Integration的演进 三、架构设计与核心组件 3.1 分层架构设计 3.2 核心组件详解 3.3 编程模型 四、解决的问题与优势 4.1 解决的核心…

内网后渗透攻击--linux系统(权限维持)

用途限制声明&#xff0c;本文仅用于网络安全技术研究、教育与知识分享。文中涉及的渗透测试方法与工具&#xff0c;严禁用于未经授权的网络攻击、数据窃取或任何违法活动。任何因不当使用本文内容导致的法律后果&#xff0c;作者及发布平台不承担任何责任。渗透测试涉及复杂技…

C++笔记之同步信号量、互斥信号量与PV操作再探(含软考题目)

C++笔记之同步信号量、互斥信号量与PV操作再探(含软考题目) code review! 参考笔记: 1.C++笔记之同步信号量、互斥信号量与PV操作再探(含软考题目) 2.C++笔记之信号量、互斥量与PV操作 参考链接 1.嵌入式基础知识-信号量,PV原语与前趋图 2.信号量、PV操作及软考高级试题解析…

布隆过滤器:快速判断某个元素是否存在

特点&#xff1a;高效、空间占用小、允许一定误判 布隆过滤器在 Redis 里的实现机制&#xff0c;核心就是&#xff1a;用一个大位图&#xff08;bitmap&#xff09;来表示集合 位图长度 m 初始值都是 0 插入元素时通过 k 个不同的哈希函数&#xff0c;对元素做哈希 每个哈希结…

C# 修改基类List中某一元素的子类类型

描述&#xff1a;基类&#xff1a;BaseClass子类1&#xff1a;A子类2&#xff1a;B然后我有一个List<BaseClass>类型的链表:list&#xff0c;我先往list中添加了两个元素&#xff1a;第一个元素为A类型&#xff0c;第二个元素为B类型&#xff0c;然后我想改变第一个元素类…

基于STM32智能阳台监控系统

基于STM32智能阳台监控系统&#xff08;程序&#xff0b;原理图元件清单&#xff09;功能介绍具体功能&#xff1a;1.采用STM32作为主控芯片实现检测和控制&#xff1b;2.通过光敏电阻采集光线&#xff0c;将当前光线值在LCD1602显示&#xff0c;低于50%控制LED亮&#xff0c;高…

动态维护有效区间:滑动窗口

右指针不断移动获取解&#xff0c;左指针不断移动缩小解范围 左指针的意义非常重要&#xff0c;相当于一个标兵&#xff0c;不断与这个标兵进行比较&#xff0c;如果符合要求&#xff0c;这左指针进行移动&#xff0c;并进行操作&#xff0c;如果不符合要求&#xff0c;则左指针…

嵌入式学习---(单片机)

1.UART的概念通用异步收发器&#xff0c;2个串口&#xff08;1个串口被用于ISP下载程序&#xff0c;1个串口被用于和主机之间的通信&#xff09;&#xff0c;RXD(接收信号线) TXD(发送信号线)2、单工、半双工、全双工概念对比维度单工&#xff08;Simplex&#xff09;半双工&am…