发布于 2026-01-05 10 阅读
0

领域驱动设计和函数式纯 UI 组件 morphonent morphonent-test 总结 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

领域驱动设计和函数式纯 UI 组件

形态学

形态学测试

概括

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

随着用户交互方式和体验数量的激增,应用程序的开发变得越来越具有挑战性。用户现在希望获得丰富、自然、快捷的交互体验,并且需要可靠的应用程序。

现在,随着我们在构建这些复杂应用程序时的需求不断发展,我们拥有了ReactVueSvelteAngular等尖端库和框架,仅举几例。

此外,应用程序状态管理本身就是一个挑战,社区为此开发了各种解决方案,例如ReduxMobX。当涉及到与后端进行 HTTP 请求的异步状态时,这个问题会变得更加复杂。

我个人对分布式架构和模式很感兴趣,但我发现系统前端编程的复杂性也很有意思,因为它本质上就是为了满足用户需求。当我们处理后端每分钟大量的请求,每分钟交换数千兆字节的信息时,很容易忽略用户,而只关注系统本身。

然而,由于前端应用程序的特性,你有机会专注于单个用户,因此你会尝试了解他们的多种需求。尽管我们有很大的学习机会,但遗憾的是,这种情况并不常见,我们往往基于一些基本的 UI 模式来构建应用程序,这些模式对用户和我们开发者来说效率都很低。

如今构建用户界面比以往更加便捷、经济且自动化。然而,大多数用户界面对用户而言成本仍然很高(看看你为一个网页下载的 JavaScript 代码量就知道了),对开发者来说也是如此,因为网页一旦构建完成,更改其结构就非常困难。

我一直在研究如何降低用户界面的修改成本、使其更易于组合和测试。我得出了以下结论,这些结论将有助于简化用户界面的修改:

  • 应用程序需要像黏土一样具有可塑性。
  • 转场效果需要合理且易于追踪。尽量使用一对一转场,尽可能避免使用扇形展开。
  • 默认情况下是异步的,同步代码只是速度非常快的异步代码。
  • 自动测试应用程序应该像在浏览器中渲染它一样简单。

因此,基于敏捷开发和极限编程,我想到了以下用于降低用户界面成本的库或框架的要求。

  • 要使应用程序具有可塑性,就需要经常改变其结构。
  • 让过渡自然流畅,过渡应该是应用程序运行方式的基本组成部分。
  • 库应该以相同的方式理解异步和同步业务逻辑。
  • 应用程序的每个组件都应该能够独立测试且测试速度快。

我编写了一个名为 `<library_name>` 的库morphonent来实现这些理念。然而,我认为这些模式和设计决策如果真的有用,可以构建在其他更强大、更可靠的库之上,例如上面提到的那些库。这里重要的不是我为了实现这些模式而构建的库,而是这些模式本身。

GitHub 标志 kmruiz / morphonent

用于构建 Web 用户界面的 JavaScript 库

形态学

构建状态 Coveralls GitHub npm npm 包大小 npm npm GitHub 问题 GitHub 拉取请求

Morphonent 是一个用于构建 Web 用户界面的 JavaScript 库。

  • 功能齐全,无副作用,结构简单,您的组件将保持合理性。
  • 默认情况下不使用特殊语法。使用普通函数,无需特殊语法。
  • 体积小,无运行时依赖。
  • 默认异步加载。旨在异步加载组件。

有关 Morphonent 任务的更多信息,请查看这篇dev.to 帖子。

安装

morphonent 是一个简单的 npm 包,您可以使用 yarn 安装:

$> yarn add morphonent

或 npm:

$> npm install --save morphonent

morphonent 被打包成一个 ES 模块,可以从现代浏览器或用 babel 编译的应用程序中导入。

入门

使用 webpack,您可以在几分钟内搭建一个简单的 Morphonent 应用。您可以在我们 wiki 上的入门指南中找到具体步骤。

它长什么样?

如果您想查看一个简单的待办事项清单应用程序示例,请点击这里

你好世界

如果我们可以看一下代码,可能会更容易理解。我们来看一个Hello World例子。

如您所见,我们的组件只是一个函数,就像React 的函数组件一样。该库默认不支持 JSX,但README文件中有关于如何启用 JSX 的教程。为了便于展示,我将使用纯 JavaScript 函数。

互动

组件交互是通过事件实现的,事件由事件处理程序处理。不同之处在于,函数不包含可变状态,就像……一样React hooks。那么,组件如何改变呢?

事件处理程序需要定义下一个要渲染的组件。这使我们能够专注于行为,而不是状态。例如,让我们来映射一下切换按钮的交互过程:

替代文字

实现这种模式的代码实际上与图片类似:

如您所见,我们并没有改变按钮的状态,这与我们在可变组件中使用钩子或属性的做法类似。在事件处理程序中,我们返回的是将要处理该事件的函数,而该函数又会返回一个new component用于处理后续交互的组件。DOM 差异比较和优化将由库本身处理。

这使我们能够定义交互和组件,而无需关心它们的具体实现。组件的挂载不再是一个必须执行的决定。

例如,当我们点击按钮 10 次时,我们可以完全改变 DOM,将按钮变成 span 元素。

现在重要的是交互,而不是组件。组件只是实现细节,决定了事物的渲染方式,而交互不再局限于组件的结构。这使得应用程序更具可塑性。我们可以处理更复杂的场景,例如待办事项列表以及删除条目的功能。

当然,这里的逻辑很简单,但这种思维方式和模式能让我们以更强大的方式构建用户界面。因为之后,我们可以轻松地将交互与不同类型的用户角色绑定,并基于这些角色渲染完全不同的应用程序。

异步组件和转换

通常,交互需要从外部服务收集用户信息,而这些服务可能速度较慢或容易出错。为了解决这个问题,我们的应用程序需要理解交互可能需要缓慢的过渡。为此,我们需要一个更高级别的组件transition

交互过程中会发生转换,转换需要两个不同的参数:

  • 过渡期间要展示什么
  • 长期互动的结果。

我们可以通过以下应用程序来了解它是如何通过查询 GitHub API 获取用户仓库的:

用户画像和动态布局

现在我们可以进一步迭代用户界面,并在仓库列表足够大(15 个仓库)时彻底改变列表布局。如果仓库少于 15 个,我们将只显示一个有序列表ol。如果超过 15 个,我们将div使用 flex-box 布局显示多个仓库。这样,贡献较大的用户和贡献较小的用户看到的仓库列表将完全不同。

您可以利用这些信息进行测试:

  • 小额贡献者:kmruiz
  • 主要贡献者:vlingo

您可以使用按钮来查看示例。

你会发现,根据用户信息完全改变布局非常容易,因为这正是整个框架的工作原理。其背后的理念正是如此:组件只是实现细节,重要的是用户交互的方式。

测试

现在到了最后一步:可测试性。我们如何才能让交互和组件易于测试呢?我们可以利用以下几个特性来简化代码的测试:

  • 副作用由用户交互处理。
  • 我们的组件都是纯函数。
  • 交互绑定是在最抽象的层面上完成的。

我个人认为 Enzyme 和 React Test 的工作方式其实不错。主要问题是它们速度相对较慢,因为它们需要处理大量的差异比较和状态逻辑。我为 Morphonent 开发了一个示例测试库,它实现了类似的 Fluent API,但专门针对 Morphonent。使用这个库进行测试,对于小型组件和交互,每个测试通常耗时不到 1 毫秒。

GitHub 标志 kmruiz / morphonent-test

吗啡测试盒

形态学测试

构建状态 Coveralls GitHub npm npm 包大小 npm npm GitHub 问题 GitHub 拉取请求

morphonent-test 是一个用于验证用morphonent编写的组件的测试库。

  • 简单易用,并附带合理的默认设置。
  • 速度快。完全无需在 DOM 中挂载组件即可运行,便于快速轻松地进行检查。
  • 默认异步。旨在让异步组件的使用与同步组件的使用一样便捷。

安装

morphonent-test 是一个简单的 npm 包,您可以使用 yarn 安装:

$> yarn add -D morphonent-test

或 npm:

$> npm install --save-dev morphonent-test

入门

morphonent-test 提供了一个组件封装器,它完全用于通过流畅的 API 进行内省并与真实组件交互。我们还将该库设计得对 TDD 友好,因此可以轻松地根据您的需求进行更改和调整。

让我们来看一个使用 jest 作为测试运行器的示例测试。

import { testing, click } from 'morphonent-test';
describe('Counter component', () => {
  describe('counting upwards', ()
Enter fullscreen mode Exit fullscreen mode

由于测试是在 Node 上运行的,这次我无法分享 Codepen,但我会分享一些代码示例。

如何测试交互

// fake data
const firstId = faker.internet.userName()
const secondId = faker.internet.userName()
const text = faker.internet.userName()

// example components (clicking on firstComponent will render secondComponent)
const secondComponent = () => element('div', { id: secondId }, text)
const firstComponent = () => element('button', { id: firstId, onclick: secondComponent })

// interactions
const result = await testing(firstComponent) // wrap into a test container
                 .findById(firstId) // find the button
                 .trigger(click()) // click it (will return the result of the handler)
                 .findById(secondId) // find the div with the new content
                 .textContent() // get the text content

// expectations
expect(result).toBe(text)
Enter fullscreen mode Exit fullscreen mode

概括

我相信这些模式使我们能够专注于用户交互,并将 DOM 视为易于修改的黏土。如果我们能够实现这些模式,我们就能做到很多令人惊叹的事情,例如:

  • 在运行时根据不同的用户角色调整我们的应用程序,并专注于满足他们的需求。
  • 将我们的应用程序编写成函数。
  • 实验和 A/B 测试更容易(顾名思义)。
  • 我们的应用程序由普通函数组成,因此更容易进行测试。

你觉得怎么样?我很想了解其他人的想法和意见。

谢谢!

文章来源:https://dev.to/kmruiz/domain-driven-design-and-functions-pure-ui-components-29a7