使用 TypeScript 构建 React 组件库的经验教训

2025-06-10

使用 TypeScript 构建 React 组件库的经验教训

组件库风靡一时。Shopify、Salesforce、IBM,甚至美国政府都加入了无数其他组织和企业的行列,致力于构建组件库。它们已经成为博客文章、播客和 YouTube 教程的主题。如今,只剩下Ken Burns 的一部纪录片了。

事实上,我是一名软件架构师和高级工程师,目前正在领导一个 React 组件库的开发,该库将成为美国一家知名政府机构 UI 的基础。我想与大家分享我在项目管理、沟通、可访问性、工程和测试方面的经验教训,以打造一个将影响数百万人生活的项目。以及其中的点点滴滴。

那么组件库到底有什么大不了的呢?

设计系统

它不是从组件库开始的,而是从设计系统开始的。尼尔森诺曼集团对设计系统的定义如下

设计系统是一套完整的标准,旨在使用可重复使用的组件和模式大规模管理设计。

设计系统列举了构成品牌消费者卓越用户体验的标准和实践。它阐明了每个团队在沟通中应使用的术语,以打破信息孤岛,避免康威定律的冲动。它包含关于颜色、字体、间距等基本规则。所有这些核心原则都成为更大组件的基础——从明确的组件(例如按钮和日期选择器)到更细微的组件(例如网格系统)。

我们的用户体验团队负责开发和维护我们的设计系统。就像软件一样,它会不断发展、版本化,并且需要协作。用户体验设计师之间,以及我和其他项目架构师和工程师之间,会讨论哪些设计合理、哪些设计可行。嵌套下拉菜单是否有必要?我们是否有时间自行设计完美的下拉菜单Datepicker?或者,我们应该尝试定制一些开源软件吗?我们对禁用按钮有何看法?如果我们认为它们合理,那么我们该如何克服诸如对比度低之类的常见缺陷?

诸如此类。我们使用原子设计语言,将网页界面解构为从“原子”到“页面”的实体,作为描述设计系统目标的通用术语。

对我们来说,构建组件库的挑战,或许也是最难的部分,在于工具。部分原因是用户体验团队的偏好,部分原因是我们工作敏感性导致的开发环境限制,我们无法简化 UX 线框图的版本控制自动化,也无法将其转化为工程师可以用来构建的工件。结果,我们处理的线框图难以理解。即使只是为了查看它们,我们要么需要在机器上安装工具,但这会花费更多许可证费用并给开发者体验 (DX) 带来负担,要么需要使用自定义浏览器插件浏览数百个静态资源文件。这两种方式都不是最佳体验。除此之外,随着设计系统和组件库的不断发展,我们还需要手动跟踪两者之间的一致性。

我从来没有说过它很漂亮,但它也不全是坏的。

组件库的价值

设计系统是一套独立于实现细节的核心原则。您可以选择使用任何技术来实现这些原则,并让 UI 工程师能够将其变为现实。

对我们来说,这就是 React。我们的 React 组件为程序创造了很多价值。

一致性

我们的组件库在整个开发团队中强化了我们的设计系统。使用这些组件几乎可以保证 UI 与我们的品牌保持一致,并为用户提供最佳、最直观的体验。开发者可以放心,他们使用的组件经过了 UX 团队的审核,这使得他们可以专注于服务的具体用例,而不必担心与设计系统的一致性等交叉问题。

该库还能最大限度地提高我们的 UI 通过 UX 团队视觉测试的可能性。这一点至关重要,因为违规行为会降低我们的交付节奏和获取反馈的能力。

可访问性

与一致性相关的是可访问性,这是我们组件库的首要任务。可访问性,通常称为#a11y,不仅仅意味着赋能视障人士。它还意味着赋能那些在听力、运动、灵活性或其他方面有障碍的人。它意味着赋能每个人

根据合同和法律规定,该项目必须提供
可访问的用户界面——具体来说是符合508条款。话虽如此,可访问性远不止是一项职业义务;这是我的个人优先事项。对我来说,确保我构建的所有东西对每个用户来说都是直观的,这一点非常重要。

我稍后会详细说明这一点,但我们的组件库专为无障碍设计。开发团队可以信赖各个组件的无障碍功能,并且正如我之前所说,专注于他们自己的用例。当然,您可能正在考虑我们已有的可访问下拉菜单、自动完成和日期选择器,但我们也提供辅助语义 HTML组件。例如,库功能包括Section,它代表您想象中的section HTML 元素SectionGrid,以及,它是section我们设计系统网格赋予的元素。

当然,组件库只能帮助开发人员实现完全可访问性的一部分,但不必从 0 开始还是不错的。

可重用性

我们竭尽全力为组件提供直观的 API,但这项任务比您想象的要棘手得多。这些 API 需要充分体现用户的意见,确保用户不会违反设计系统,同时又能为组件提供足够的自由度,以支持广泛的用例。对于我们的Button组件来说,这很容易做到。但对于像Card和 这样的布局组件来说Page,这则更加困难。由此带来的可复用性,让各个团队乃至整个项目都更加高效。

我们也竭尽全力为组件赋予尽可能精简的功能。组件 API 提供 props,使开发团队中的库使用者能够提供行为。举个明显的例子,开发者onClick为组件提供行为Button。我们有一些更复杂的组件需要维护自己的状态,
但我们尽可能地减少维护状态的需要。这提供了清晰的关注点分离,使组件的测试更加容易,任何经验丰富者都知道,强大的可测试性会带来强大的可复用性。

封装

稍后会详细介绍,但我们并非从零开始构建组件。相反,我们会定制现有的开源组件,并将我们的 API 映射到它们的 API 上。这样一来,组件的实现细节就从我们的开发团队那里抽象出来了。例如,我们使用react-datepicker作为我们自己的组件的基础DatePicker,但如果我们决定将其替换为其他组件,我们的用户将一无所知。

组件堆栈

正如我所提到的,我们使用 React 构建我们的组件库,这是我们推荐的,但对于我们规避风险的政府客户来说,考虑到它得到 Facebook 的支持、市场渗透率受欢迎程度,这也是安全的选择。

但 React 是比较简单的部分。让我们看看组件栈的其他部分。

TypeScript

当我们开始构建组件库时,我认为 TypeScript 至关重要,原因有二。通过在开发和构建过程中强制执行类型安全,我们可以更快地发现错误,从项目管理的角度来看,这要便宜得多。更重要的是,使用 TypeScript 构建 API 对应用程序开发团队的库使用者来说非常有帮助,因为它可以方便他们在 IDE 中完成代码补全,并在构建过程中进行类型检查

我还要提一下,如果我们无法从其他 props 中自行获取某些 TypeScript API,则它们需要ARIA值来提升可访问性。

Chakra 用户界面

我之前提到过,我们的组件是基于开源组件构建的,而其中大部分都是基于Chakra UI构建的。市面上有很多其他开源组件库,但 Chakra UI 是迄今为止我最喜欢的。主要原因是它对可访问性的一流承诺,以及使用 TypeScript 构建的组件的直观 API。正如你可能已经猜到的那样,Chakra UI 为我构建我们自己的
组件库提供了灵感。

Chakra UI 还提供了强大的主题自定义 API,我们充分利用了它,通过专用的主题文件将我们设计系统的原则应用于 Chakra 组件,这些文件将样式与功能分离。这种关注点分离使我们的代码推理更加容易,文件本身也更加轻量。

Chakra UI 还具有一些有用的钩子,例如useDisclosure,非常方便。

如果您将 Chakra UI 用于自己的组件库,则可能需要导入一些别名来处理名称冲突。例如,我们的按钮组件名为 ,这并不令人意外,Button但 Chakra UI 也如此。因此,我们这样做:

import { Button as ChakraButton } from "@chakra-ui/react"
Enter fullscreen mode Exit fullscreen mode

工程

当然,最有趣的部分是构建一个 React 组件库。这篇文章很长,所以我无法详述所有细节。但我确实想讨论一些你在构建自己的组件库时可能需要考虑的关键方面。

工作流程

刚开始构建组件库时,我们需要快速行动,因为开发团队正在等我们
开始构建他们的UI。管理层要求我和几位开发人员在几个冲刺阶段内,几乎投入全部时间来完成这项工作。

我们从用户体验团队获得了初始设计系统规范,然后开始工作。在最初的几个冲刺之后,我们构建了足够多的组件,可以让团队开始工作。问题是,我们所有人都恢复了日常工作,没有时间分配给库。这意味着,每当用户体验团队设计新组件,或者开发人员发现现有组件中的错误时,都会出现瓶颈,因为没有人专门负责升级库。我和其他人尽可能地完成了这项工作,但缺乏一个专门的团队是一个问题。

另一个问题是用户体验团队内部以及用户体验团队、开发人员和我之间最初缺乏沟通。他们一心追求创意,经常向一些开发人员提供的线框图与其他开发人员提供的线框图不一致,或者提供的线框图中包含库中没有的组件。开发团队假设这些组件库中,并据此进行估算。正如你所料,当他们发现这些组件并不存在时,他们很不高兴,这影响了他们按时交付的能力。他们告诉了我这个情况,坦白说,他们完全有理由不高兴。我知道我们必须改进流程。

为此,我们做出了一些改变。我们建立了一个 Microsoft Teams 频道,通过消除会议甚至电子邮件的繁琐程序来促进沟通。我们还决定,开发团队将首先构建新的组件,如果其他团队受益,该库将吸收这些组件,并根据需要对 API 或实现进行调整,以支持整个项目更广泛的适用性。然后,首先构建组件的团队将在准备就绪后用库的实现替换他们的实现。虽然这意味着团队必须投入更多时间来开发组件,但这是透明的,并且不会造成瓶颈。

这是一个不断发展的工作流程,总有改进的空间。

组件结构

TypeScript 中的组件有三种形式。

最简单的组件如下所示:

export const TimePicker = (p: TimePickerProps) => {
    ...
}
Enter fullscreen mode Exit fullscreen mode

我们的TimePicker组件没有子组件,所以它非常简单。它只是一个函数!

如果组件有子组件,情况仍然不会太糟糕:

export const Card: React.FC<CardProps> = p => {
    ...
}
Enter fullscreen mode Exit fullscreen mode

React 的FC类型 (for FunctionComponent) 隐式地包含一个childrenprop。我们也可以像上面那样声明它,TimePicker但显式地添加一个tochildren类型的 prop 。我更喜欢这种方式,因为它非常清楚地向库使用者表明了 的存在,并且类型参数让我可以进行一些类型推断。注意,我不需要指定 的类型,因为它是类型参数 隐式指定的ReactNodeCardPropsFCchildrenpCardProps

不过,还不算太糟,对吧?

最后一种组件稍微复杂一些——表单组件。我们的开发人员使用React Hook Form,和我用过的其他表单库一样,它使用refs 来维护表单状态。这意味着我们的组件需要提供一种方法来接受 sref并将其委托给它们的子组件。

大多数 React 工程师不知道这一点,因为他们不需要知道,但 React 提供了一个名为的函数来实现这个目的forwardRef,我们像这样使用它:

export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(p, ref) {
    ...
}
Enter fullscreen mode Exit fullscreen mode

让我尝试来分析一下这一点。

高阶函数指接受函数作为参数或返回函数的函数。这里forwardRef接受Button渲染组件的函数作为参数。得益于forwardRef,开发团队可以将引用传递给我们库中的表单组件,然后我们通过该函数参数将其传递给渲染的实现。类型参数用于forwardRef提供类型安全和推断。 的类型pButtonProps,并且ref将被连接到HTMLButtonElement

最后,它有点复杂,也有点繁琐,但结果却非常简单——一个ref从调用者那里接受的表单组件,因此表单库可以根据需要使用它。

目录结构

在考虑如何布局源代码时,这取决于团队的偏好,但正如我最近在推特上所说:

关于如何在#React中布局源代码,有很多评论。如果你拿两个“东西”(函数、类、#TypeScript接口等)来说,其中一个改变另一个的频率越高,它们之间的距离就应该越近。

— Neil Chaudhuri(@realneilc2021 年 9 月 29 日

这在实践中到底意味着什么?

很简单。对于我们的组件库来说,这意味着将特定组件的代码组织到同一个目录中,有时甚至放在同一个文件中。这就是我们在高层的做法。

按钮组件目录布局

我们的组件Button.tsx包含ButtonProps界面、相关类型,当然还有组件本身。同时,我喜欢 Chakra UI 允许我们将主题与行为分离,因此我们设计系统定义的颜色、间距、字体系列、图标大小、焦点行为和其他按钮细节都位于ButtonTheme.ts同一目录中的另一个文件中。

最后,虽然我们可以把测试和故事(稍后会详细介绍)放在同一个目录中,但我们更喜欢将它们组织到各自的子目录中。我猜我看近藤麻理惠太多了。

TypeScript 配置

我拥有 Java 和 Scala 等静态和强类型编程语言的背景。虽然我理解资深 JavaScript 工程师对类型感到畏惧,但我发现类型能让我效率极高。因此,我们的 TypeScript 配置非常严格。特别是从我们的tsconfig.json

{
...
  "compilerOptions": {
    ...
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitAny": true,
    ...
  },
...
}
Enter fullscreen mode Exit fullscreen mode

对于为应用程序开发团队构建库,我们的范围tsconfig.json如下:

{
...
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "**/__stories__/*",
    "**/__test__/*"
  ],
...
}
Enter fullscreen mode Exit fullscreen mode

我们所有的组件、故事和测试都在这个src目录中,但我们在构建库时只需要这些组件。这就是为什么我们在每个组件目录中排除了__stories____test__目录。

静态分析和代码格式化

和大家一样,我们也依赖 eslint 和 Prettier,并没有做什么特别的事情。不过,我还是想提几件事。

首先是eslint-plugin-jsx-a11y。我们使用这个 eslint 插件来自动验证组件库的可访问性。它会检查组件的 JSX 代码是否存在明显的违规行为。目前为止,我们只能实现自动化,但我们会eslint-plugin-jsx-a11y在 Storybook 中补充手动审核,稍后我会讨论。

经验丰富的工程师读到这里可能会感到有些困惑。上面tsconfig.json我们排除了故事和测试,因为它们不属于构建。不过,您知道我们应该对故事代码和测试代码应用与生产代码相同的质量标准。代码就是代码。

为此,我们扩展 tsconfig.json了一个名为 的文件tsconfig.eslint.json,将字段
替换exclude为空数组,并配置eslint使用。这会告诉eslint(因此 Prettier)在其分析中包含文件夹中的所有内容src,并使用相同的 TypeScript 配置。这意味着,例如,我们any也不能通过在故事或测试中使用隐式引用来作弊。

构建

我们使用Vite进行构建。这看起来可能有点违反直觉,因为 Vite 是Vue的构建工具,而我们的库是用 React 构建的,但 Vite 实际上是不可知的。事实上,我惊讶于我们只需要如此少的配置。它基本上就能正常工作。我们的 Vite 配置几乎与文档中的示例相同。就像示例一样,我们的构建生成两种 bundle 格式——esumd——而且运行速度很快。

您可能知道,TypeScript 构建包含两个阶段:类型检查和转译为 JavaScript。TypeScripttsc编译器的类型检查非常慢,因此虽然它非常重要,但您应该尽量少做。我们只在编写代码时或构建用于生产的库时通过 IDE 实时进行类型检查——如果类型检查失败,构建过程就会中断。

我们有一个专门的typecheck脚本,package.json如下所示:

{
  "scripts": {
    ...
    "typecheck": "tsc --p tsconfig.eslint.json --skipLibCheck --sourceRoot src --noEmit",
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

请注意,我们使用类型tsconfig.eslint.json检查来检查所有内容。

同时,将 TypeScript 源代码转译为 JavaScript 比类型检查更快,但阅读托尔斯泰的著作也同样如此。使用 esbuildtsc或 Babel 进行转译仍然不够快。不过,转译器esbuild是用 Go 编写的,Go 是一种专为速度而生的语言,Vite 在底层使用了它。因为我们需要不断地进行转译以了解 Storybook 中的情况,所以确保转译过程快速至关重要。多亏了 esbuild,Vite 才真正满足了我们的需求。

我们的生产版本采用语义版本控制,包含每个组件的声明文件index.d.ts和枚举所有组件的文件。这些文件使开发人员的 IDE 能够快速完成代码,从而改进了 DX。我们还提供用于我们自己组件的主题文件,以便开发人员可以将相同的主题应用于他们的组件。我们的 CI/CD 管道将库发布到私有 NPM 注册表,这允许npm在开发人员机器上进行适当配置的安装,以使用常规方式获取库npm installpackage.json库附带的文件包含使用该库所需的所有对等依赖项,以便开发人员npm可以获取它们,并且为方便起见,它还包含构建时使用的设计系统的版本,以供开发人员跟踪。

它还包含定义在库中打包哪些文件以及消费者如何导入模块的配置:

{
...  
  "files": [
    "dist"
  ],
  "types": "./dist/index.d.ts",
  "main": "./dist/components.umd.js",
  "module": "./dist/components.es.js",
  "exports": {
    ".": {
      "import": "./dist/components.es.js",
      "require": "./dist/components.umd.js"
    }
  }
...
}
Enter fullscreen mode Exit fullscreen mode

关于构建,最后需要注意一点。虽然 Vite 当然提供了压缩和其他生产就绪功能,但我们不会使用它们。我们将组件库完全“原始”地打包。我们发现这有助于开发者调试他们的应用程序并准确报告错误(在极少数情况下我们会犯错误)。当他们运行自己的构建时,他们的工具会将压缩、树状优化和所有其他生产处理应用于他们的所有代码和依赖项,包括组件库。

测试

正如我之前提到的,我们将组件的功能限制在能够增加价值的最低限度。然而,组件本身就是代码,我们的用户对我们的代码抱有期望。这意味着我们需要尽可能多地、在合理的范围内测试组件。

测试是一个颇具争议的话题。在科技推特上,工程师们非常乐意告诉你,为什么你用与他们不同的方式测试代码是错误的。我只能描述一下我们的方法以及我们为什么这么认为,同时也声明,随着我们在这方面的改进,我们的方法可能会有所调整。

我们的方法很大程度上受到了这篇Storybook 博客文章的启发。在文中,Varun Cachar根据几个大型工程团队的经验,描述了不同类型的测试、每种测试的适用情况以及哪些工具适用于哪些类型。

故事书

Storybook 对于我们组件库的开发和测试至关重要,并且对于我们的用户来说它是必不可少的文档。

在开发过程中,我们以几种方式使用它。如果组件很简单,那么最好将代码和 Storybook 放在一起,并通过热重载观察更改的渲染效果。另一方面,当我们不清楚组件的 API 应该是什么时,最好编写几个故事来制定它的 DX。经验丰富的工程师可能会认为这种方法类似于
测试驱动开发 (TDD)

我们将 Chakra UI 中的设计系统自定义主题应用于以下每个故事preview.jsx

export const decorators = [Story => <ChakraProvider theme={theme}>{Story()}</ChakraProvider>]
Enter fullscreen mode Exit fullscreen mode

在测试过程中,我们也以多种方式使用 Storybook。例如,由于我们对组件采取了移动优先的方法,这对于像模态窗口这样的有机体尤其重要,因此我们在 中配置了如下自定义断点preview.jsx

export const parameters = {
    viewport: {
        viewports: {
            xs: {
                name: "XS",
                styles: {
                    height: "568px",
                    width: "320px",
                },
                type: "mobile",
            },
            sm: {
                name: "SM",
                styles: {
                    height: "896px",
                    width: "480px",
                },
                type: "mobile",
            },
            md: {...},
            lg: {...},
            xl: {...},
        defaultViewport: "xs",
    },
}
Enter fullscreen mode Exit fullscreen mode

我提到了一个 CI/CD 流水线,用于构建库并将其发布到私有注册表。事实证明,该流水线还将我们的组件 Storybook 发布到Nginx 容器中,以便 UX 团队可以对组件进行可视化测试,并且切换视口大小的功能非常有用。

对于使用我们组件进行交互的开发团队来说,这也非常有帮助。借助Storybook Controls,他们可以自行配置组件并查看结果。借助Storybook Docs,他们可以查看生成每个故事的代码和 API 属性。因此,Storybook 在整个程序中提供了丰富的文档功能。

我们偶尔也会使用 Storybook 进行组合测试,但频率可能不如 Storybook 团队希望的那么高。例如,我们有一些故事演示了如何将表单组件与 React Hook Form 集成,这暴露了我们在ref代码中遇到的问题。不过,通常情况下,我们不会进行大量的组合测试,除非我们需要重现某个场景来修复 bug(并最终证明我们已经修复了 bug)。

我们大量使用storybook-addon-a11y来测试可访问性。正如Varun Cachar的另一篇文章(他绝对是赚到钱的)中所说, Storybook 提供了许多用于可访问性测试的功能。我们会充分利用它们。正如我之前提到的,尽管我们已尽力jsx-a11y在构建和 Storybook 中对可访问性进行可视化测试,但团队仍然有责任将@axe-core/react添加到他们的构建中并执行他们自己的可视化测试,以便我们尽可能确信我们正在为所有用户提供最佳体验。

最后,虽然 Storybook 对我们来说非常宝贵,我强烈推荐它,但如果不提一些问题,那就太失职了。Storybook 使用了很多我们用于主题、Markdown 和其他功能的库。当你的版本和他们的版本之间存在库冲突时,就会发生糟糕的事情。例如,我们在Emotion上遇到了与GitHub 上这个问题相同的冲突。值得称赞的是,Storybook 团队发布更新的频率很高。如果没有其他问题,请确保你使用相同版本的 Storybook 及其所有插件,并在有更新可用时尽快升级。

Storybook 也深知 JavaScript 构建工具领域的“DivOps”革命,并据此进行了相应的定位。这令人兴奋,因为 Webpack 曾经运行良好,但感觉越来越像过去了,而我们希望将 Vite 与 Storybook 结合使用。我们安装了storybook-builder-vite ,因为我们知道它还处于实验阶段,只是想看看它的效果如何。总的来说,它让我们的 Storybook 构建速度如我们所愿。不过,考虑到 Storybookstorybook-builder-vite还很原始,社区由优秀的工程师主导,他们用有限的时间已经为社区做出了巨大的贡献,无法解决所有问题,再加上我提到的 Storybook 的普遍脆弱性,你的想法可能会有所不同。以下是我们在 中与 Vite 相关的 Storybook 配置main.js

module.exports = {
    ...
    core: {
        builder: "storybook-builder-vite"
    },
    viteFinal: async config => {
        return {
            ...config,
            plugins: ...,
            optimizeDeps: {
                ...config.optimizeDeps,
                entries: [`${path.relative(config.root, path.resolve(__dirname, "../src"))}/**/__stories__/*.stories.@(ts|tsx)`],
            },
        }
    },
}
Enter fullscreen mode Exit fullscreen mode

React 测试库

如果你读过我关于测试的任何文章,你就会知道我认为我们这个行业总体上对测试的理解是错误的。我们对某些东西测试过多,对另一些东西测试过少。我们并不总是清楚测试的目的。最糟糕的是,由于不正当的动机,我们编写测试只是为了勾选一个选项

我之前提到过,我们优先考虑的是赋予组件尽可能少的行为。简洁的代码除了更易于维护和理解之外,还能减少给用户带来的意外,也减少我们需要测试的内容。

或者说我是这么认为的。

我们的项目强制要求应用程序的代码覆盖率至少达到 80%,出于一些我不太理解的原因,组件库也必须达到这个要求。在我看来,只有维护内部状态的组件才会具备超越 Storybook 的复杂度,需要进行正式的测试,可惜的是,规则不是我制定的。

React 测试库已经成为React 中交互测试事实标准,我们当然也会用它来进行自己的测试。但是,如何才能尽快编写测试,以减少代码覆盖率标准的影响呢?

如果您用任何编程语言编写过测试,您就会理解“测试夹具”的概念,即测试的设置。对我们来说,这意味着测试夹具只是配置了不同 props 的组件。

但这不正是 Storybook 中的故事吗?

Storybook 提供了一个我非常喜欢的功能——能够使用
@storybook/testing-react将故事导入到使用 React Testing Library 编写的测试中,作为 Fixture 。如果没有它,我们就需要
在 Storybook 中重复与故事相同的代码,并在测试中重复与 Fixture 相同的代码。由于内置了 TypeScript 支持,它的自动补全功能也非常棒@storybook/testing-react

最后我想提一下,正如我在这篇文章中反复强调的那样,可访问性是关键。React 测试库中的所有测试都使用了getByRole和选择器。我们这样做是因为,正如文档中所述,findByRole这是一种将隐式可访问性测试构建到交互测试中的方法。毕竟,如果我们无法通过 ARIA 角色找到想要测试的组件,那么几乎可以肯定它是不可访问的。如果它不可访问,我不在乎它是否“有效”,因为它并非对所有人都有效。

除此之外,如果您了解 React 测试库,我们的测试工作方式完全符合您的预期。以下是一个简单测试示例,它充分体现了我所描述的一切:

...
import {
    DefaultMediumPrimaryButton,
    ...
} from "../__stories__/Button.stories"

test("Button primary display works", () => {
    const onClickMock = jest.fn()

    render(<DefaultMediumPrimaryButton onClick={onClickMock} />)

    const button = screen.getByRole("button", { name: "Primary" })

    userEvent.click(button)
    expect(onClickMock).toHaveBeenCalledTimes(1)
})
Enter fullscreen mode Exit fullscreen mode

我知道内容有点多,如果做成有声书可能会更有趣一些。不过,我希望我传达了设计系统和组件库的价值,以及我们在项目管理、沟通、可访问性、工程和测试方面的经验教训,从而构建出能够影响数百万人生活的东西。我希望你们也能做到……而且做得更好。

现在去睡一会儿吧。这是你应得的。

继续阅读 https://dev.to/realneilc/lessons-learned-from-building-a-react-component-library-with-typescript-3bkb
PREV
5 个简单的错误将导致你今年无法获得开发工作
NEXT
如何在 Windows 10 上安装 Java JDK 17