以下是我在如何不构建 React 组件方面学到的两件事。这些是我在过去几个月里遇到的问题,如果您正在构建设计系统,特别是那些包含大量遗留技术决策和大量技术债务的设计系统,您可能会对它们感兴趣。
第一课:尽可能避免子组件
在大型设计系统中使用大量组件时,以下模式最终会很快变得有問題
<Card>
<Card.Header>Title</Card.Header>
<Card.Body><p>This is some content</p></Card.Body>
</Card>
有问题的部分是这些子组件,Card.Body
和 Card.Header
。这个例子并不糟糕,因为事情相对简单——当组件变得更复杂时,事情可能会变得很混乱。例如,每个子组件都可以有一系列复杂的 props,这些 props 会相互干扰。
我最大的痛点之一是我们的表单组件。比如这个
<Form>
<Input />
<Form.Actions>
<Button>Submit</Button>
<Button>Cancel</Button>
</Form.Actions>
</Form>
当然,我简化了很多东西,但是每次工程师想要将两个按钮并排放置时,他们都会导入Form.Actions
,即使页面上没有Form
。这意味着Form
组件中的所有内容都会被导入,这最终不利于性能。它碰巧也是糟糕的系统设计实现。
这在编写组件文档时也会使事情变得更加困难,因为现在您还必须确保这些子组件也都有文档。
因此,与其将Form.Actions
作为子组件,不如将其作为一个全新的组件,简单地命名为:FormActions
(或者可能是更好的名称,例如ButtonGroup
)。这样,我们就不必一直导入Form
,并且可以将基于布局的组件与其他组件分开。
我已经吸取了教训。从现在开始,我会尽可能避免使用子组件。
第二课:确保您的 props 不会相互冲突
Mandy Michael 撰写了一篇关于props 如何相互碰撞并导致各种令人困惑的冲突的优秀文章,例如以下 TypeScript 示例
interface Props {
hideMedia?: boolean
mediaIsEdgeToEdge?: boolean
mediaFullHeight?: boolean
videoInline?: boolean
}
Mandy 写道
这些 props 的目的是更改卡片中图像或视频的渲染方式,或者是否渲染媒体。单独定义它们的问题在于,您最终会得到许多切换组件功能的标志,其中许多是互斥的。例如,如果图像也隐藏了,则不能使其填充边距。
这绝对是我们团队设计系统中继承的大量组件存在的问题。有很多组件,其中布尔型 props 会使组件以各种奇怪且意想不到的方式运行。在开发过程中,我们的Card
组件甚至出现过各种错误,因为工程师不知道要为任何给定效果打开和关闭哪些 props!
Mandy 提供了以下解决方案
type MediaMode = 'hidden'| 'edgeToEdge' | 'fullHeight'
interface Props {
mediaMode: 'hidden'| 'edgeToEdge' | 'fullHeight'
}
简而言之:如果我们将所有这些新兴选项组合在一起,那么我们将拥有一个更简洁的API,该 API 易于扩展,并且不太可能在将来造成混淆。
就是这样!我只想快速记录一下这两点。我的问题是:在构建组件或构建设计系统时,您学到了什么?
喜欢这篇文章!实际上,我们已经使子组件(我们称之为子组件)在我们的系统中运行良好,但是我们通过制定有关何时使用它们的规则避免了一些您提到的问题——子组件只能作为父级根组件的子级进行渲染。这往往与 React 上下文的使用很好地吻合。
例如,我们的 SegmentedControl 组件(https://sproutsocial.com/seeds/components/segmented-control),它以
SegmentedControl.Item
子组件的形式呈现一组选项。父组件跟踪控件的选中值,而项目从父级上下文中读取该值以确定其活动状态。API 最终看起来像这样对我来说感觉非常好。遵循永远不要在父组件外部使用子组件的规则也有助于我们避免“仅为子组件导入完整组件”的问题。
我学到的一件事是保持组件 API 简单——即传递的 props 数量。
以 Button 组件为例……
您可以拥有一个单独的 Button 组件,其 props 确定它是纯文本按钮,还是同时具有图标(以及所有其他图标定位变体),或者它仅仅是图标按钮。此外,您还需要所有标准的 props,例如大小、颜色、点击处理程序、禁用状态等。很多 props。
或者使用单独、更简单的组件……例如 TextButton、TextIconButton、IconButton。
然后,它们可以共享/扩展大小、颜色、处理程序、禁用状态的类型。
我想将纯文本按钮与图标和文本按钮分开,但在我的情况下,有时我需要仅在移动设备上显示图标,而在较大设备上显示文本和图标……或者在移动设备上将图标浮动到左侧,但在纵向和向上时,使用相同的图标,但将其自身块并向下推文本。因此,如果我将组件分开,我必须告诉任何将要处理它们的人根据断点隐藏/显示另一个组件。我所做的是使用一个 icon prop,它可以接受一个配置对象来设置所有这些内容,或者在他们不需要任何这些内容的情况下仅接受图标本身。
Enrique,当然您可以使用 CSS(媒体查询)轻松隐藏移动设备上的文本,并使用 flexbox 和 flex-direction 更改图标位置(如果需要)。
如果您不想使用 CSS,请在 React 中检测移动设备(通过屏幕宽度),并在 JSX 中有条件地渲染图标(如果尺寸不是移动设备等)。
这实际上是我在幕后所做的事情,在大多数情况下。或者只是设置/删除原子类,这些类在特定屏幕尺寸下隐藏或显示某些元素,或者有条件地从 DOM 中删除它们。我的组件库的用户只需决定在预定义的一组断点上隐藏/显示、向上/向下移动等的大小。
这些框架仍然一团糟。如果我们需要限制子组件的数量,那有什么意义?这意味着它们设计得很糟糕,就是这样。
哇,我真的很喜欢这篇文章!老实说,我以前从未知道拥有子元素会影响代码的运行方式。我刚开始学习编码,在大型复杂项目方面没有太多经验。我从未想过我的编码可能比较粗糙,并可能导致以后出现问题。感谢您提供这个非常有用的技巧。