React 单一职责原则:优化组件设计与提高可维护性

fileOf7174.png

单一职责原则(SRP)

在 React 中,组件是构建 UI 的核心单位,而良好的组件设计是保证应用质量和可维护性的关键。单一职责原则是一种设计原则,也适用于 React 组件的开发。它强调每个组件应该只关注一个职责,这样可以提高组件的可维护性、复用性以及降低代码的复杂度。

什么是单一职责原则

单一职责原则的定义是每个类应该只有一个职责,   也就是只做一件事。这个原则是最容易解释的,因为我们可以简单地将其理解为“每个功能/模块/组件都应该只做一件事”。

在 React 组件中,单一职责原则要求将组件的功能分解为更小的部分,每个部分只负责一个特定的职责。这样做的好处是,当需求发生变化时,只需要修改与该职责相关的部分,而不会影响整个组件。这提高了代码的可维护性和可测试性。

在所有这些原则中,单一职责原则是最容易遵循的,也是最有影响力的一项,因为它极大提高了代码的质量。为了确保组件只做一件事,可以这样:

  • 将功能较多的大型组件拆分为较小的组件;

  • 将与组件功能无关的代码提取到单独的函数中;

  • 将有联系的功能提取到自定义 Hooks 中。

如何应用单一职责原则在 React 中

在 React 中应用单一职责原则主要通过组件的拆分和组合来实现。以下是一些建议:

  1. 拆分大型组件:当一个组件变得庞大且复杂时,考虑将其拆分为更小的组件,每个组件关注不同的职责。这样可以提高可维护性和复用性。

  2. 提取可复用的功能:识别重复使用的功能,将其提取为单独的组件,并通过 props 进行参数化。这样可以减少重复代码,提高复用性。

  3. 保持组件的简洁和独立性:确保每个组件只关注自己的职责,并且不包含与其他职责无关的代码。这样可以降低代码的复杂度和维护成本。

  4. 使用高阶组件(Higher-Order Components):高阶组件是一种函数,接受一个组件作为参数,并返回一个新的增强组件。通过使用高阶组件,可以将与特定职责相关的逻辑与组件分离,提高代码的可维护性。

  5. 使用自定义钩子(Custom Hooks):自定义钩子是一种将组件逻辑进行复用的方式。通过将与特定职责相关的逻辑封装在自定义钩子中,可以使组件更加简洁和独立。

下面来看一个显示活跃用户列表的组件:

const ActiveUsersList = () => {const [users, setUsers] = useState([])useEffect(() => {const loadUsers = async () => {  const response = await fetch('/some-api')const data = await response.json()setUsers(data)}loadUsers()}, [])const weekAgo = new Date();weekAgo.setDate(weekAgo.getDate() - 7);return (<ul>{users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo).map(user => <li key={user.id}><img src={user.avatarUrl} /><p>{user.fullName}</p><small>{user.role}</small></li>)}</ul>    )
}

这个组件虽然代码不多,但是做了很多事情:获取数据、过滤数据、渲染数据。来看看如何分解它。

首先,只要同时使用了  useState  和  useEffect,就可以将它们提取到自定义 Hook 中:

const useUsers = () => {const [users, setUsers] = useState([])useEffect(() => {const loadUsers = async () => {  const response = await fetch('/some-api')const data = await response.json()setUsers(data)}loadUsers()}, [])return { users }
}const ActiveUsersList = () => {const { users } = useUsers()const weekAgo = new Date()weekAgo.setDate(weekAgo.getDate() - 7)return (<ul>{users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo).map(user => <li key={user.id}><img src={user.avatarUrl} /><p>{user.fullName}</p><small>{user.role}</small></li>)}</ul>    )
}

现在,useUsers Hook 只关心一件事——从 API 获取用户。它使我们的组件代码更具可读性。

接下来看一下组件渲染的 JSX。每当我们对对象数组进行遍历时,都应该注意它为每个数组项生成的 JSX 的复杂性。如果它是一个没有附加任何事件处理函数的单行代码,将其保持内联是完全没有问题的。但对于更复杂的 JSX,将其提取到单独的组件中可能是一个更好的主意:

const UserItem = ({ user }) => {return (<li><img src={user.avatarUrl} /><p>{user.fullName}</p><small>{user.role}</small></li>)
}const ActiveUsersList = () => {const { users } = useUsers()const weekAgo = new Date()weekAgo.setDate(weekAgo.getDate() - 7)return (<ul>{users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo).map(user => <UserItem key={user.id} user={user} />)}</ul>    )
}

这里将用于呈现用户信息的逻辑提取到了一个单独的组件中,从而使我们的组件更小、更可读。

最后,从 API 获取到的用户列表中过滤出所有非活跃用户的逻辑是相对独立的,可以在其他部分重用,所以可以将其提取到一个公共函数中:

const getOnlyActive = (users) => {const weekAgo = new Date()weekAgo.setDate(weekAgo.getDate() - 7)return users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo)
}const ActiveUsersList = () => {const { users } = useUsers()return (<ul>{getOnlyActive(users).map(user => <UserItem key={user.id} user={user} />)}</ul>    )
}

到现在为止,通过上面三步拆解,组件已经变得比较简单。但是,仔细观察会发现,这个组件还有优化的空间。目前,组件首先获取数据,然后需要对数据进行过滤。理想情况下,我们只想获取数据并渲染它,而不需要任何额外的操作。所以,可以将这个逻辑封装到一个新的自定义 Hook 中,最终的代码如下:

// 获取数据
const useUsers = () => {const [users, setUsers] = useState([])useEffect(() => {const loadUsers = async () => {  const response = await fetch('/some-api')const data = await response.json()setUsers(data)}loadUsers()}, [])return { users }
}// 列表渲染
const UserItem = ({ user }) => {return (<li><img src={user.avatarUrl} /><p>{user.fullName}</p><small>{user.role}</small></li>)
}// 列表过滤
const getOnlyActive = (users) => {const weekAgo = new Date()weekAgo.setDate(weekAgo.getDate() - 7)return users.filter(user => !user.isBanned && user.lastActivityAt >= weekAgo)
}const useActiveUsers = () => {const { users } = useUsers()const activeUsers = useMemo(() => {return getOnlyActive(users)}, [users])return { activeUsers }
}const ActiveUsersList = () => {const { activeUsers } = useActiveUsers()return (<ul>{activeUsers.map(user => <UserItem key={user.id} user={user} />)}</ul>    )
}

在这里,我们创建了useActiveUsers Hook 来处理获取和过滤数据的逻辑,而组件只做了最少的事情——渲染它从 Hook 中获取的数据。

现在,这个组件只剩下两个职责:获取数据渲染数据,当然我们也可以在组件的父级获取数据,并通过 props 传入该组件,这样只需要渲染组件就可以了。当然,还是要视情况而定。我们可以简单地将获取并渲染数据看作是“一件事”。

总结

使用单一职责原则可以获得以下好处:

  1. 提高可维护性:将组件分解为小的职责模块,使其更易于理解和修改。当需要更新或修复特定功能时,可以更快地定位和处理相关代码。

  2. 提高复用性:通过将职责分离为独立的模块,可以更好地重用组件的不同部分。这样可以减少重复代码,提高开发效率。

  3. 降低代码耦合:职责分离使得组件之间的依赖关系更清晰。当一个组件只关注一个职责时,它变得更加独立,减少了与其他组件的耦合性。

通过应用单一职责原则,我们可以优化 React 组件的设计,提高其可维护性和复用性。将组件分解为小的职责模块,提取可复用的功能,保持组件的简洁和独立性,使用高阶组件和自定义钩子等技术,都是实现单一职责原则的有效方法。

总而言之,遵循单一职责原则,我们有效地采用了大量独立的代码并使其更加模块化,模块化的代码更容易测试和维护。

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

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

相关文章

css网格布局Grid

一、网格布局适应场景 当涉及到的布局是二维布局&#xff08;元素不止一行或者一列&#xff09;且比较复杂的时候&#xff0c;可以用网格布局&#xff0c;看下面的一个例子&#xff1a; 上图上一个四行三列的网格&#xff0c;布局相对比较复杂。如果你用别的布局方案&#xff…

利用Python生成Xilinx FPGA ROM IP核 .coe初始化文件

以下是一个 Python 脚本&#xff0c;用于生成 Xilinx IP ROM 的.coe 格式初始化文件&#xff0c;假设ROM 深度为 1024&#xff0c;数据位宽为 32bit&#xff0c;使用随机的 32 位无符号数进行初始化&#xff1a; import random# 定义ROM的深度和数据位宽 rom_depth 1024 data…

8.2 段落格式

在word里&#xff0c;段落格式包括首行缩进、行间距、段前、段后等。LaTex同样支持这些功能。 段落间距 全局设置 段落间距用setlength命令来指定。如以下代码 \documentclass{article} \usepackage{ctex} \begin{document}\setlength{\parskip}{11em plus 1em minus 1em}\p…

OpenVLA-OFT

TL;DR 2025 年斯坦福提出的 OpenVLA 工作的续作 OpenVLA-OFT&#xff0c;优化 VLA 能够有效适应新的机器人平台和任务&#xff0c;优化的技术主要有并行解码、动作块处理、连续动作、L1 回归和&#xff08;可选的&#xff09;FiLM 语言调节 Paper name Fine-Tuning Vision-La…

SpringBoot 接口国际化i18n 多语言返回 中英文切换 全球化 语言切换

介绍 Spring Boot通过MessageSource接口来实现国际化&#xff0c;它可以加载不同的消息资源文件&#xff0c;通常是.properties格式。通过定义不同的语言文件&#xff08;例如&#xff1a;messages_en.properties、messages_zh.properties等&#xff09;&#xff0c;可以根据用…

一个crackme例子

文件下载地址&#xff1a;https://download.csdn.net/download/m0_37567738/90713354 将cipher.txt文件内容解密后&#xff1a; 恭喜你解出了这一关&#xff0c;flag为 zjwa{36_23121136a28d0d15} 好了现在告诉你最后一层的获取方式&#xff0c; 在系统内找到 手机镜像的 ra…

账户解封无望?3步高效申诉取回亚马逊冻结资金

近年来&#xff0c;随着全球跨境电商市场的飞速扩张&#xff0c;亚马逊&#xff08;Amazon&#xff09;作为其中的巨头&#xff0c;持续强化其平台治理力度。然而&#xff0c;随之而来的是卖家账户因各种原因被冻结、关闭的事件频频发生。根据Marketplace Pulse发布的2024年第一…

【C++ Qt】快速上手 显⽰类控件(Label、LCDNumber、ProcessBar、CalendarWidget)

每日激励&#xff1a;“不设限和自我肯定的心态&#xff1a;I can do all things。 — Stephen Curry” 绪论​&#xff1a; 本文围绕Qt中常用的显示类控件展开&#xff0c;重点讲解了 QLabel&#xff08;文本/图片显示&#xff09;、QLCDNumber&#xff08;数字显示&#xff0…

从困局到破局的AI+数据分析

从困局到破局的AI数据分析 困局&#xff1a;数据分析的四道高墙破局&#xff1a;AI赋能全流程数据分析远见&#xff1a;AI数据分析的革命性意义 数据是新时代的石油&#xff0c;人工智能是炼油厂。当两者强强联合&#xff0c;一场数据分析的革命正悄然发生。 多少次你面对Excel…

IGH 汇川SV660N调试

EoE 目前的方式是将eoe 关闭, 这需要重新配置编译ec_master sudo ./configure --disable-8139too --enable-generic --enable-r8169 --disable-eoe --enable-coe[426163.348589] EtherCAT 0: Master thread exited. [426163.348592] EtherCAT 0: Stopping EoE thread. [426163.…

Java基础361问第16问——枚举为什么导致空指针?

我们看一段代码 public enum Color {RED, BLUE, YELLOW;public static Color parse(String color) {return null;} }public static void main() {Color color Color.parse("");// 极具迷惑性&#xff0c;大家日常开发肯定这么写过switch (color) {case RED:break;c…

10.Excel:快速定位目标值

一 批量删除 1.如何使用 快捷键 CTRLG 补充&#xff1a;直接选择定位条件。 2.作用 1.批量删除工作表中的图片 补充&#xff1a;无法通过框选的方式选中这些图片进行删除。 这样只框选了表格&#xff0c;无法框选图片。因为图片在excel中被认为是一个对象&#xff0c;对象无法通…

快乐数(双指针解法)

题目链接202. 快乐数 - 力扣&#xff08;LeetCode&#xff09; 题目拆解 1 取一个正整数每一位的平方和为&#xff0c;如果为1那么直接可以判定为快乐数&#xff0c;如果不为1&#xff0c;就重复这个过程&#xff0c;直到出现1 2 实际上&#xff0c;这道题只有两种情况&#xf…

进程控制的学习

进程控制&#xff08;Process Control&#xff09;是指操作系统对进程的创建、执行、暂停、恢复、终止等一系列状态变化进行管理和协调的过程。 简单说&#xff0c;就是系统让各个程序能有序地运行&#xff0c;合理地使用CPU和资源&#xff0c;而不会互相冲突或者出错。 主要包…

818协议知识笔记

一、概念 Fibre CHannel-Audio Vedio standard;FC-AV FC-FS:Fibre channel framing and signaling interface; FC-PI:fibre channel physical interfaces 二、术语 VGA,SVGA,XGA,WXGA,SXGA,SXGA,WSXGA,UXGA,1440P; ICD:interface control document接口控制文档 CRC对帧头和数据…

AI大模型学习十二:‌尝鲜ubuntu 25.04 桌面版私有化sealos cloud + devbox+minio对象存储测试和漫长修改之路

一、说明 前面已经安装完成&#xff0c;这里我们测试对象存储 AI大模型学习十一&#xff1a;‌尝鲜ubuntu 25.04 桌面版私有化sealos cloud devboxminio&#xff0c;实战运行成功-CSDN博客https://blog.csdn.net/jiangkp/article/details/147424823?spm1011.2415.3001.5331 二…

SpringBoot的自动扫描特性-笔记

1.Spring Boot 的自动扫描特性介绍 Spring Boot 的自动扫描&#xff08;Component Scanning&#xff09;是其核心特性之一。通过注解SpringBootApplication 简化了 Bean 的管理&#xff0c;允许框架自动发现并注册带有特定注解的类为 Spring 容器中的 Bean&#xff08;特定注解…

基于nodeJS代码的通过爬虫方式实现tiktok发布视频(2025年4月)

1、将真实的tiktokstudio平台的cookie填到代码里的cookie变量里,修改python代码里的ticket,ts, privateKey,以及videoPath,timing等变量的值,最后运行python脚本即可; 2、运行之前根据import提示安装一些常见依赖,比如node-fetch等; 3、运行时候可能系统需要科学上网…

数据一致性问题剖析与实践(四)——竞态条件竞争导致的一致性问题

一、前言 之前我们讨论了几种场景的一致性问题 冗余数据存储中的一致性问题分布式共识中的一致性问题单机事务中的一致性问题分布式事务中的一致性问题 本文将围绕竞态条件竞争中的一致性问题展开讨论分析。 二、 问题定义 竞态条件&#xff08;Race Condition&#xff09…

PCL点云处理之基于FPFH特征的SAC-IA全局配准算法 (二百四十六)

提示: 有相关点云需求的可以私信 PCL 点云处理之基于 FPFH 特征的 SAC - IA 全局配准算法 一、前言二、相关概念介绍2.1 点云2.2 FPFH 特征2.3 SAC - IA 算法三、SAC - IA 全局配准算法原理3.1 FPFH 特征提取3.2 SAC - IA 配准过程四、代码实现与分析4.1 完整代码4.2 代码分析…