首页
开发技巧正文内容

如何在React应用程序中应用SOLID原则

2023年10月25日
阅读时长 2 分钟
阅读量 4
如何在React应用程序中应用SOLID原则

让我们从React应用程序的角度来讨论SOLID原则。如果你不确定SOLID原则是什么,你可能需要先阅读以下内容:

以图片解释S.O.L.I.D原则

用例子解释SOLID原则

第一章:单一职责原则

第一个原则通常是最容易理解的:你的组件应该只有一个职责,或者换句话说,这个组件存在的唯一原因是什么

然而,如果你将思维从消费模式切换到思考模式,你可能会立即注意到一个问题:鉴于你的应用程序中的任何非叶子组件都是一个子树的根,几乎可以肯定地说,你的一些组件将具有多个职责 - 组件有五个子组件,并控制其中三个的状态。我敢打赌你的根App组件严重违反了这个原则!

那么我们该怎么办?如何应用单一职责原则?如果你仔细观察,你可能会发现两组组件:一些很容易根据单一职责原则进行设计的组件;另一些则是一些有很多职责(或控制)的协调者。你可以称它们为智能/哑组件,或容器/展示组件,我不在乎,只要你承认这种分离即可。

我将它们称为管理者工作者,我声称通过引入一套简单的规则,你可以使应用程序的所有组件都遵守单一职责原则!让我们定义它们:

  1. 管理者不应该做工作者的工作,换句话说,它们的唯一职责是管理和组合工作者(或其他管理者)。

  2. 工作者不应该意识到业务逻辑,也不应该将工作委托给管理者,只能委托给其他工作者。

让我们仔细思考一下这两个规则。"管理者不应该做工作者的工作"是什么意思?这意味着管理者不应该有自己的JSX内容,它们返回的内容只应该由工作者和/或其他管理者组成,或者简单地说,它们不应该有自己的HTML元素。因此,它们也不会有CSS,因为没有东西可以进行样式设置。你可以放心地说,管理者代表你的应用程序的(Java)脚本部分 - 它们承载所有的业务逻辑并控制大多数用户交互。

那么工作者"不应该意识到业务逻辑"是什么意思呢?嗯,恰恰相反!它们将承载所有的内容和样式(或应用程序的HTML和CSS部分),几乎没有JavaScript - 只在它们自己的独立存在所需时使用。

我必须指出,样式方面存在一个相当大的警告(太大了,无法在这里包含),并且根据你阅读这篇文章的时间,可能会有或可能没有另一篇我写的文章,标题为"样式如何破坏你的React应用程序"(或类似的标题)。

还要注意,工作者不能将工作委托给管理者并不意味着管理者不能是工作者在组件树中的子组件,我们将在最后一章中讨论这个问题😉

第二章:开闭原则

这个原则有一个听起来很简单的定义:你的组件应该对扩展开放,对修改关闭,然而理解扩展和修改之间的区别可能并不容易。从组件本身的角度来看,任何扩展都是一种修改。因此,我们可以推断出这个原则纯粹依赖于你组件的外部消费者的观点:

"我不在乎你对组件做什么,只要不需要我改变使用它的方式!"

这听起来很熟悉!那是因为这个原则是向后兼容性的基础,而且它无处不在。Web API和设计趋势将不断发展,你应该始终期望组件的演变,并且这种演变应该始终*尊重现有的消费者。让我们通过一个简单的例子来看看开闭原则在实践中是如何工作的:

你被要求创建一个Button组件,如果提供了图标,它将显示一个图标,所以你这样做了:

过了一段时间,产品设计师告诉你:“伙计,我们需要一个图标在右边的按钮!”你会怎么做?试着停下来思考一下你会怎么做。

有多种方法可以实现这种新的行为,例如通过引入一个新的iconPlacement属性,其默认值为left/start,它将控制图标的位置。只要消费者满意,我不会反对这样做,但是个人而言,我实际上会引入两个新的属性并弃用一个:

我希望这能对开闭原则有一点点清晰,并且能够在你的React应用程序中实现它。请注意,这个原则不是万能的(提示:没有什么是万能的),扩展不可避免地会导致更大的文件,并且在某个时候变得适得其反。有时你可能希望通过扩展组件并创建它们的子类型来解决问题:

你能在这个组件中发现一个潜在的问题吗?如果没有,不要担心 - 我们将在下一章中讨论它。

第三章:里氏替换原则

里氏替换原则基于协变性:你应该能够用其子类型替换任何超类型。对于那些在学校没有学过计算机科学课程的人来说,这个命名可能听起来有点奇怪。如果我通过扩展类型A创建新类型B,将类型B称为类型A的子类型,将类型A称为类型B的超类型,这是不符合直觉的,但事实就是如此 - 记住,计算机科学中的树也是向下生长的。

那么我在上面的IconButton组件中犯了什么错误呢?IconButton扩展了Button组件,换句话说,IconButtonButton的子组件。现在,我有两个问题要问你,第一个你可能已经猜到了,第二个是一个棘手的问题:

  1. 我可以用其子类型组件IconButton替换所有Button组件的出现吗?

  2. 这种替换合理吗?

对于第一个问题的答案是NO!通过省略Button组件的icon属性,我实际上违反了里氏替换原则。我的IconButton组件更像是一个"孤儿子",而不是Button的经典子类型。这两种类型是独立的,不可替代的,或者用花哨的技术术语来说,它们是彼此的不变量。

对于第二个问题的答案也是NO!因为我从一开始就没有打算创建一个子类型,换句话说,IconButton从来没有打算替代Button

这是编程中组合与继承的一个很好的例子:里氏替换原则基于继承,而React基于组合(我们稍后会看到)。然而,React基于组合并不意味着继承没有用武之地。事实上,如果你看一下上面的Button组件,我故意将它作为HTMLButtonElement的子类型,通过启用两个关键功能:

  • extends React.PropsWithChildren:它接受内容

  • extends React.HTMLAttributes<HTMLButtonElement>:它接受属性

由于Button组件遵守了里氏替换原则**,我可以放心地将所有button HTML元素的出现都安全地替换为它在我的应用程序中,这正是计划中的。

那么为什么不让IconButton遵守里氏替换原则呢?你可能会问。这当然是可能的,但很快你的代码将变成一个混乱的烂摊子,有着不必要复杂的组件和一长串可能给你的生活带来很多麻烦的事情。

如果你是在开发React应用程序的早期阶段,我建议你忽略这个原则。如果你对React感到满意,你应该始终从这个问题开始:这个组件是否打算替代它扩展的基本组件或元素? 如果是,确保你遵守里氏替换原则,通过正确地转发props、children、属性和引用。如果不是,不要这样做!## 第一章:接口隔离原则

如果我需要选择一个可以盲目遵循的SOLID原则,毫无疑问,那肯定是接口隔离原则:一个组件不应该依赖它不使用的属性

起初,这个原则可能看起来奇怪地简单:为什么会有人引入组件不使用的属性呢?如果我们使用适当的代码检查配置,我们甚至无法故意这样做,对吧?有什么问题呢?

问题在于这个原则的递归性质,考虑下面的组件:

起初,这里似乎没有未使用的属性,我们确实使用了user,这是我们唯一的属性。然而,递归地User类型带来了一堆未使用的属性,比如emailid,这些属性对于我们的组件来说完全不需要,这就是违反了接口隔离原则。这与我们上面关于Worker组件的讨论密切相关,它们不应该知道业务逻辑。事实上,可以说它们根本不应该知道任何外部信息(并非总是可能的)。

让我们来修复它!

让我们分析一下发生了什么变化。我们所做的最重要的事情是在第一个示例中的line 3移除了导入。通过这样做,我们有效地将UserAvatarUser接口解耦,从而极大地增强了我们组件的可重用性。事实上,我们甚至从命名中删除了对"User"的任何提及 - 现在我们的宠物也可以有头像了! 😻

第四章:依赖倒置原则

现在,让我们迎来最后一章,让我们欢迎唯一无二、React中组合的教父,所有原则的基础 - 依赖倒置原则!官方定义有点难以理解,尤其是乍一看;它也是唯一一个有两部分定义的SOLID原则:

A. 高层模块不应该从低层模块导入任何东西。两者都应该依赖于抽象(例如接口)。 B. 抽象不应该依赖于细节。细节(具体实现)应该依赖于抽象。

如果听起来很沉重,那是因为确实如此。依赖倒置原则,正如你可能从我辉煌的介绍中注意到的那样,很容易值得一篇自己的文章。而且当涉及到React时,我有一个好消息和一个坏消息:

好消息:你默认情况下就在使用依赖倒置原则,甚至没有注意到!

坏消息:你默认情况下就在使用依赖倒置原则,甚至没有注意到!

所以,让我们来拆解它,开始注意到这个迄今为止最强大的SOLID原则,它是所有React事物的基础。让我们从解析名称开始:什么是依赖?它是我在组件中使用的外部代码(模块、包、函数、钩子、组件等)。这个依赖可以以两种方式出现在"我的领土"上:

  1. 导入:我从相应的模块中导入它

  2. 注入:它作为参数(或属性)传递给我

导入是直接依赖,我几乎无法控制它。如果该依赖项更改其行为,我就完蛋了。

注入是反向依赖:尽管我依赖于传递给我的内容,但我通过抽象(属性接口)控制可以传递的内容。

请注意,依赖倒置原则与单一职责原则和接口隔离原则密切相关。记住,在我们上面关于坏的UserAvatar示例中,我们提到组件不应该知道业务逻辑(成为一个Worker);还有我们不应该传递组件不需要的属性/信息;依赖倒置原则增加了这个组件不好的第三个原因 - 我们正在创建一个直接依赖,换句话说,我们的抽象(AvatarProps)依赖于细节(User)。

你必须注意的第二个最重要的事情是,依赖倒置原则不仅仅停留在在组件中传递函数。从字面上看,React中的每个关于这个原则的例子都只谈论事件处理程序,没有注意到依赖倒置原则所能实现的最重要的事情。让我们再次重写我们的Button组件,看看你能否注意到它:

所以,onClick是显而易见的:它不能被内置,否则将违反单一职责原则,它也不能被直接导入,否则将违反依赖倒置原则。你在这里还注意到其他的依赖注入的例子吗?children呢?它们作为属性传递了吗?检查!我控制它们的类型吗?检查!这是本章的高潮:

每当你谈论React中的组合模式时,你就在谈论依赖倒置原则。它们是一样的!

这正是使得第一章中的Workers能够在组件树中比Managers更高的原因 - 它们的依赖关系被倒置,它们实际上成为了容器***,而不知道它们确切包含的内容,从而不违反单一职责原则。

现在,让我们回到我在第十一章中的承诺,向你展示React中的一切都是组合的。在React中传递children的方式实际上只是一种语法糖。仔细看看这些组件及其使用方式:

组件AB是相同的,唯一的区别是:我能够以更自然的方式(从JSX的角度来看)通过使用保留的children属性向组件传递数据。

现在,让我们谈谈类型区分。ReactNode | undefined类型本身并不是一个法则,它只是React能够在不崩溃的情况下处理的最通用的类型。但我们可以自由地控制类型,记住吗?所以,让我们缩小我们的类型范围并重写上面的部分:

正如你所看到的,React中的组合不仅仅适用于children属性,你可以使用任何属性名称来组合你的应用程序;它不仅仅适用于ReactNode类型,你可以使用任何属性类型来组合你的应用程序。在React中,一切都是组合的。

从React的角度来看,依赖倒置原则实际上就是将责任上移给父组件。考虑到我们在第一章中讨论的Manager/Worker模式,这是完全有道理的。但是,就像单一职责原则一样,如果你最后一次将你的大脑从消费切换到思考,你会再次注意到一个很大的问题。你不能无限地将责任上移。换句话说,你的应用程序至少需要一个Manager组件;如果只有一个,就像在现实生活中一样,你可以想象这个可怜的家伙将拥有多少责任和层级。从可读性和推理的角度来看,这绝对会导致灾难。

尽管依赖倒置原则通过解耦你的组件为你提供了令人难以置信的优势和稳定性,但组合必须在一定的层次上停止,而Manager组件似乎是一个很好的停止点。通常情况下,遵循依赖倒置原则是一个好主意,或者换句话说,尽量保持应用程序中Manager组件的数量尽可能少,并且只在Manager变得过载时打破功能。例如,如果你开始一个新的Next.js项目,你自然会在每个路由中有两个管理器,layout.tsxpage.tsx。稍后,如果你的页面有很多功能,你可以通过为每个功能引入一个管理器组件来减轻page.tsx的负担,依此类推。

免责声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

相关文章

探索多种软件架构模式及其实用应用
2024年11月22日19:06
本文深入探讨了多种软件架构模式,包括有界上下文、边车模式、发布-订阅模式、应用网关、微服务、命令职责分离(CQRS)等,介绍了它们的优点、使用场景以及具体应用实例。文章强调根据具体项目需求和团队能力选择最合适的架构,以构建高效和可维护的解决方案,同时展示了各架构模式间的综合应用,提供了丰富的案例和技术细节。
15个高级Python快捷键助您更快编程
2024年11月21日07:02
本文分享了 15 个高级的 Python 编程快捷键,包括上下文管理器、行内字典合并、函数参数解包、链式比较、dataclasses、海象运算符、反转列表、备忘录缓存、splitlines、enumerate、字典推导、zip 用于并行迭代、itertools.chain 扁平化列表、functools.partial 部分函数和 os.path 文件路径管理等,帮助开发者提高编程效率和代码简洁性。
揭示网页开发的 11 个迷思:停止相信这些误区
2024年11月19日22:05
网页开发充满误解,这篇博文针对11个常见迷思进行揭秘。包括网站开发后不需更新、需掌握所有技术、AI会取代开发者等。强调持续学习、专业化、用户体验的重要性,澄清误区如多任务处理的必要性和最新技术的必需性。文章提醒开发者注重实用而非追求完美代码,以务实态度面对开发工作。
你知道 CSS 的四种 Focus 样式吗?
2024年11月18日21:41
本文介绍了四种 CSS focus 样式::focus、:focus-visible、:focus-within 以及自定义的 :focus-visible-within,帮助提升网站用户体验。:focus 样式应用于被选中元素;:focus-visible 仅在键盘导航时显示;:focus-within 用于父元素;自定义 :focus-visible-within 结合两者效果。合理运用这些样式能使网站更方便键盘用户导航。
利用 Python 实现自动化图像裁剪:简单高效的工作流程
2024年11月11日20:49
使用 Python 和 OpenCV 自动裁剪图像,轻松实现 16:9 的完美构图。这个指南介绍了如何通过代码进行灰度化、模糊处理和边缘检测,最终识别出最重要的部分进行裁剪。特别适合需要批量处理图像的情况,节省大量时间。
每位资深前端开发人员都应了解的 TypeScript 高级概念
2024年11月11日02:07
资深前端开发者应了解 TypeScript 的高级概念,如联合类型、交叉类型、类型保护、条件类型、映射类型、模板字面量类型和递归类型。这些特性可提升代码的可维护性和可扩展性,确保在开发复杂应用时实现更高的类型安全性和效率。