在 React 中应用 SOLID 原则
在 React 中应用 SOLID 原则
S.O.L.I.D.
最初发表于 https://medium.com/dailyjs/applying-solid-principles-in-react-14905d9c5377
随着软件行业的发展和犯错,最佳实践和良好的软件设计原则出现并被概念化,以避免在未来重复同样的错误。尤其是面向对象编程 (OOP) 世界是这些最佳实践的金矿,而 SOLID 无疑是最具影响力的之一。
SOLID 是一个首字母缩略词,其中每个字母代表以下五个设计原则之一:
- 小号 单一责任原则(SRP)
- ○ 闭笔原理(OCP)
- 大号 伊斯科夫替换原则 (LSP)
- 我 接口隔离原则 (ISP)
- D 依赖倒置原则(DIP)
在本文中,我们将讨论每个原则的重要性,并了解如何将 SOLID 的学习成果应用到 React 应用程序中。
然而,在我们开始之前, 有一个很大的警告 . SOLID 原则的构思和设计都考虑到了面向对象的编程语言。这些原则及其解释在很大程度上依赖于类和接口的概念,而 JS 也不是。我们通常认为 JS 中的“类”只是使用其原型系统的类似类,接口不是语言的一部分(尽管添加 TypeScript 会有所帮助)。更重要的是,我们编写现代 React 代码的方式远非面向对象,它似乎更具功能性。
不过好消息是,像 SOLID 这样的软件设计原则与语言无关,并且具有高度抽象性,这意味着如果我们足够关注,我们将能够将它们应用到我们的代码中。
所以让我们自由一些。
单一责任原则 (SRP)
最初的定义是“每个班级应该只有一个职责”,即只做一件事。这个原则是最容易解释的,因为我们可以简单地将定义推断为“每个功能/模块/组件必须完全做一件事”。
在所有五项原则中,SRP 是最容易遵循的,但它也是最有影响力的,因为它极大地提高了我们的代码质量。为了确保我们的组件做一件事,我们可以:
将功能强大的大型组件分解为较小的组件
将与核心组件功能无关的代码提取到单独的有用函数中
将插入的功能封装在自定义挂钩中
现在让我们看看如何应用这个原则。我们将首先考虑以下显示活动用户列表的示例组件:
虽然这个组件现在相对较短,但它已经做了一些事情,比如获取数据、过滤、渲染组件本身以及单独列出每个用户。让我们看看如何分解它。
首先,每当我们连接钩子时 使用状态
e 使用效果
, 是在自定义钩子中提取它们的好机会:
现在我们的钩子 使用用户
它只关心一件事——获取 API 用户。它还使我们的主要组件更具可读性,不仅因为它更短,而且还因为我们用域挂钩替换了您需要用来破译目的的结构挂钩,其目的从名称中立即显而易见。
接下来,让我们看看我们的组件渲染的 JSX。每当我们在对象数组上进行循环映射时,我们必须注意它为数组中的各个项目生成的 JSX 的复杂性。如果您没有附加任何事件处理程序,则可以将其保持内联,但对于更复杂的标记,将其提取到单独的组件中可能是个好主意:
与之前的更改一样,我们通过提取逻辑以在单独的组件中呈现用户项目,使我们的主要组件更小且更具可读性。
最后,我们有逻辑从我们从 API 获得的所有用户列表中过滤非活动用户。这个逻辑是相对孤立的,可以在应用程序的其他地方重用,所以我们可以很容易地将它提取到一个实用函数中:
在这一点上,我们的主要组件足够简短和直接,我们可以停止分解它并收工。但是,如果我们仔细观察,我们会发现它仍然做得比它应该做的更多。我们的组件当前正在获取数据,然后对其应用过滤,但理想情况下,我们只想获取数据并渲染它,而不需要任何进一步的操作。因此,作为最后的改进,我们可以将此逻辑封装在一个新的自定义钩子中:
在这里我们创建了钩子 使用ActiveUsers
处理搜索和过滤逻辑(我们还记住过滤后的数据以便更好地衡量),而我们的主要组件只做最低限度的工作——渲染它从钩子中获取的数据。
现在,根据我们对“仍然缺失”的解释,我们可以争辩说组件仍然是先获取数据然后渲染它,这还不是“缺失”。我们可以通过在一个组件上调用一个钩子然后将结果作为道具传递给另一个组件来进一步分解它,但是我发现在现实世界的应用程序中这实际上是有益的情况很少,所以让我们原谅定义并接受“从组件获取的数据中渲染”为“仍然缺失”。
总而言之,遵循单一职责原则,我们有效地采用了大量单体代码并使其更加模块化。模块化很棒,因为它使我们的代码更容易推理,更小的模块更容易测试和修改,我们不太可能引入无意的代码重复,因此我们的代码变得更易于维护。
必须说,我们在这里看到的是一个人工示例,在它自己的组件中,您可以发现不同运动部件之间的依赖关系更加交织在一起。在许多情况下,这可能表明设计选择错误——使用错误的抽象、创建一体化通用组件、数据范围不正确等,因此可以与更广泛的重构解开。
开闭原理(OCP)
OCP 声明“软件实体应该对扩展开放,对修改关闭”。由于我们的 React 组件和函数是软件实体,我们不需要重复定义,而是可以采用其原始形式。
开闭原则提倡以一种允许在不改变其原始源代码的情况下扩展它们的方式来构建我们的组件。要查看它的实际效果,让我们考虑以下场景——我们正在开发一个使用组件的应用程序 标题
在不同的页面上共享,具体取决于我们所在的页面, 标题
应该呈现稍微不同的 UI:
在这里,我们根据我们所在的当前页面呈现指向不同页面组件的链接。如果您考虑当我们开始添加更多页面时会发生什么,很容易看出这种实现很糟糕。每次创建新页面时,我们都需要返回到我们的组件 标题
并调整其实现以确保它知道要呈现哪个动作链接。这种方法使我们的组件 标题
脆弱且与使用它的上下文紧密耦合,违反了开闭原则。
为了解决这个问题,我们可以使用组件组合。我们的组件 标题
不必担心它会在内部渲染什么,而是可以将该责任委托给将使用它的组件使用它 孩子们
:
通过这种方法,我们完全删除了我们在内部的变量逻辑 标题
现在我们可以使用组合将我们想要的任何内容放入其中,而无需修改组件本身。考虑这一点的一个好方法是我们在可以连接的组件中提供一个占位符。我们也不限于每个组件一个占位符——如果我们需要有多个扩展点(或者如果属性 孩子们
已经用于不同的目的),我们可以使用任意数量的道具。如果我们需要从 标题
对于使用它的组件,我们可以使用 默认渲染道具 .如您所见,组合可以非常强大。
遵循开闭原则,我们可以减少组件之间的耦合,使它们更具可扩展性和可重用性。
里氏替换原则 (LSP)
过于简化,LSP 可以定义为对象之间的一种关系类型,其中“子类型对象必须可以被超类型对象替换”。这个原则严重依赖类继承来定义超类型和子类型关系,但在 React 中不太适用,因为我们几乎从不处理类,更不用说类继承了。虽然远离类继承不可避免地会将这一原则完全转变为其他东西,但使用继承编写 React 代码会故意创建糟糕的代码(React 团队 劝阻 ),所以让我们跳过这个原则。
接口隔离原则(ISP)
根据 ISP 的说法,“客户不应该依赖他们不使用的接口。”为了 React 应用程序,让我们将其翻译为“组件不应该依赖于它们不使用的属性”。
我们在这里扩展了 ISP 的定义,但它并不太大——属性和接口都可以定义为对象(组件)和外部世界(使用它的上下文)之间的契约,所以我们可以在两者之间进行比较.归根结底,这不是关于定义的僵化和僵化,而是关于应用通用原则来解决问题。
为了更好地说明 ISP 正在解决的问题,我们将在下一个示例中使用 TypeScript。让我们考虑呈现视频列表的应用程序:
我们的组件 缩略图
它用于每个项目的可能看起来像这样:
组件 缩略图
它非常小而且简单,但它有一个问题——它期望一个完整的视频对象作为道具传递,同时有效地只使用它的一个属性。
要了解为什么会出现问题,请想象一下,除了视频之外,我们还决定显示直播流的缩略图,两种类型的媒体资产混合在同一个列表中。
我们将介绍一种定义直播流对象的新类型:
这是我们的组件 视频列表
更新:
如您所见,这里有一个问题。我们可以轻松区分视频和直播对象,但不能将后者传递给组件 缩略图
为什么 视频
e 现场直播
不兼容。首先,它们有不同的类型,所以 TypeScript 会立即抱怨。其次,它们在不同的属性中包含缩略图 URL — 视频对象将其称为 coverUrl,直播流
让我们重构我们的组件 缩略图
确保它仅取决于所需的属性:
通过此更改,我们现在可以使用它来渲染视频和直播流的缩略图:
接口隔离原则提倡最小化系统组件之间的依赖关系,使它们的耦合度降低,因此可重用性更高。
依赖倒置原则(DIP)
依赖倒置原则指出“我们必须依赖抽象,而不是具体”。换句话说,一个组件不能直接依赖于另一个组件,而两者都必须依赖于一些共同的抽象。在这里,“组件”指的是我们应用程序的任何部分,无论是 React 组件、实用程序函数、模块还是第三方库。这个原理在抽象上可能很难理解,所以让我们直接看例子。
下面我们有组件 登录表单
在提交表单时将用户的凭据发送到某个 API:
在这段代码片段中,我们的组件 登录表单
直接引用模块 api
,因此,它们之间存在紧密耦合。这很糟糕,因为这种依赖性使得对我们的代码进行更改变得更具挑战性,因为对一个组件的更改会影响其他组件。依赖倒置原则主张打破这种耦合,那么让我们看看我们如何实现这一点。
首先,让我们删除对模块的直接引用 api
从里面 登录表单
而是允许通过道具注入所需的功能:
有了这个改变,我们的组件 登录表单
不再依赖于模块 api
.向 API 发送凭证的逻辑是通过 onSubmit 回调抽象出来的,现在由父组件负责提供该逻辑的具体实现。
为此,我们将创建一个连接版本的 登录表单
这会将表单提交逻辑委托给 api 模块:
组件 连接登录表单
充当 API 和 LoginForm 之间的连接器,同时它们彼此完全独立。我们可以单独测试它们,而不必担心会破坏相关的运动部件,因为它们没有。而且自从 登录表单
它在 api
遵循约定的通用抽象,整个应用程序将继续按预期工作。
过去,许多第三方库也使用这种创建“哑”表示组件然后将逻辑注入其中的方法。最著名的例子是 Redux,它将组件中的“回调”返回道具绑定到使用高阶(HOC)“连接器”组件的“调度”函数。随着钩子的引入,这种方法变得不那么重要了,但是通过 HOC 注入逻辑在 React 应用程序中仍然有用。
总而言之,依赖倒置原则旨在最小化应用程序不同组件之间的耦合。您可能已经注意到,最小化是贯穿 SOLID 原则的一个反复出现的主题——从最小化单个组件的职责范围到最小化跨组件知识和它们之间的依赖关系。
结论
SOLID 原则的应用范围远不止于此。在本文中,我们看到了如何在解释这些原则方面具有一定的灵活性,我们能够将它们应用到我们的 React 代码中,并使其更易于维护和健壮。
然而,重要的是要记住,教条和虔诚地遵循这些原则可能是有害的,并导致过度设计的代码,因此我们必须学会识别何时分解或解耦组件会引入复杂性或没有好处。
最初发表于 https://medium.com/dailyjs/applying-solid-principles-in-react-14905d9c5377
Github
[
纽顿 - 概述
软件工程师。 newerton 有 192 个可用的存储库。在 GitHub 上关注他们的代码。
github.com
](https://github.com/newerton)
领英
https://www.linkedin.com/in/newerton/pt/
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明