我喜欢使用 styled-components。它们允许你在 JavaScript 中编写 CSS,使你的 CSS 非常接近单个组件的 JavaScript。作为一个喜欢剖析网页并将其分解成可重用组件的前端开发者,styled-components 的理念让我感到快乐。这种方法简洁且模块化,我不必在某个巨大的 CSS 文件中搜索我需要的类是否已经存在。我也不必将类添加到那个永无止境的 CSS 文件中,并且为使那个已经巨大的文件变得更大而感到内疚。
然而,当我继续使用 styled-components 的道路时,我开始意识到,虽然将 CSS 样式分离出来使其特定于单个组件是很棒的,但我开始为了基于每个组件组织我的样式而重复自己很多。我一直在为每个组件创建新的 CSS 声明,因此,创建新的 styled-components,并且在我的 CSS 中看到了大量的重复。不,styled-components 并不总是完全相同,但其中三个 CSS 行中的两行会与另一个组件中三个 CSS 行中的两行匹配。我真的需要在我的每个需要这些样式的地方都重复这段代码吗?
以 flexbox 为例。Flexbox 非常适合响应式布局。它会以某种方式对齐项目,并且只需进行最少的更改,就可以调整以在不同的屏幕尺寸上看起来不错。因此,我发现自己经常编写以下代码:
display: flex;
flex-direction: row; /* the default; in react native, column is the default */
几乎同样频繁地,我发现自己编写以下代码:
display: flex;
flex-direction: column;
上面两段代码片段相当常见:第一个获取所有子元素并将其并排(从左到右)排列成一行。第二个获取所有子元素并将其上下(从上到下)排列成一列。每个代码片段都可以变得更具体;但是,我们可以添加不同的属性来进一步指定我们希望子元素如何在页面上布局。例如,如果我们希望元素在可用的屏幕宽度上均匀分布,我们可以将以下行添加到每个代码片段中:
justify-content: space-evenly;
此外,还有其他属性,例如align-items
,我们可以添加它们来进一步自定义这些元素的布局。因此,如果我们有三个不同的组件都需要 flexbox 布局,但具有其他不同的属性,我们如何才能以非重复的方式使用 styled-components 呢?
最初,为每个组件创建三组样式是有意义的,如下所示:
// component one
const ComponentOne = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-start;
`
// component two
const ComponentTwo = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
`
// component three
const ComponentThree = styled.div`
display: flex;
flex-direction: row;
justify-content: space-evenly;
`
上面列出的样式将完成这项工作。所有三个组件的子元素都排成一行——从左到右排列——每个子元素之间的间距不同。但是,将相同的两行代码重复三次会增加 CSS 膨胀。
为了避免重复自己,我们可以扩展基本组件到每个其他组件,然后将我们需要的附加样式添加到这些组件中:
// flex row component
const ExampleFlex = `
display: flex;
flex-direction: row;
`
// component one
const ComponentOne = styled(ExampleFlex)`
justify-content: flex-start;
`
// component two
const ComponentTwo = styled(ExampleFlex)`
justify-content: space-between;
`
// component three
const ComponentThree = styled(ExampleFlex)`
justify-content: space-evenly;
`
感觉好多了。这个版本——我们在其中从<ExampleFlex />
组件扩展——不仅消除了重复代码,而且还将与以行显示项目相关的代码保存在一个位置。如果我们需要将与项目方向相关的代码更新为列,我们可以在一个地方进行操作,而不是三个地方。
重要说明:当你从另一个组件扩展样式时,从该基本组件继承的样式需要像上面示例中那样列在基本组件之后。将<ComponentOne />
放在<ExampleFlex />
上方会导致错误,提示:Uncaught ReferenceError: Cannot access ‘ExampleFlex’ before initialization.
为了更进一步地说明这个想法,下面的演示展示了一个你可能需要在页面上的两个不同的 UI 元素上使用类似样式的情况,但这些元素各自都有其细微的差异。
如你所见,位于页面顶部的导航和位于页面底部的页脚都需要在较大的屏幕上以行方向布局,然后在移动设备上切换到列布局。但是,这两个元素的不同之处在于,页面顶部的导航需要左对齐以在右侧留出空间供徽标使用,而页脚链接需要右对齐。由于这些差异,为每个元素创建两个不同的 styled 组件是有意义的;用于顶部导航的<Navigation />
元素和用于页面底部导航的<Footer />
组件。
顶部导航样式如下所示:
const Navigation = styled.div`
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 10px;
width: calc(100% - 20px);
@media (max-width: 768px) {
flex-direction: column;
}
`
而底部页脚样式如下所示:
const Footer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-end;
padding: 10px;
width: calc(100% - 20px);
@media (max-width: 768px) {
flex-direction: column;
}
`
这两个元素唯一的区别是什么?justify-content
属性。此外,<LeftSideNav />
组件使用display: flex
以及相同的媒体查询。
除了这三个组件共享许多相同的 CSS 之外,<NavItem />
组件和<FooterNavItem />
是非常相似的链接组件,但也存在细微的差异。那么我们如何才能精简它呢?
查看下面的示例,你将看到可以在多个组件中重复使用的 CSS 可以提取到它自己的组件中,然后我们从中扩展以进行可能需要的特定更改,以创建更具体的组件。
通过实施这些更改,包含所有 styled-components 的 JavaScript 文件比之前的版本缩短了 10 行。虽然这看起来像是微不足道的差异,但随着应用程序的增长,这些更改可以帮助最大程度地减少与应用程序一起发送的 CSS,因为样式会不断添加。
还有“as”多态属性!
除了将样式从一个组件扩展到另一个组件之外,styled-components 还为我们提供了一个名为“as”的多态属性,只要指定了新元素,就可以将给定组件的样式应用于另一个元素。这在两个元素的用户界面可能看起来相同但这两个元素背后的底层 HTML 功能不同的情况下很有帮助。
以按钮和样式化为按钮的链接为例。虽然它们乍一看似乎具有类似的功能——它们都可以被点击以触发操作——但两者在功能上具有不同的用途。按钮非常适合提交表单或更改当前页面布局,但链接会将你带到某个资源。
我在下面创建了一个 Pen 来演示这一点。乍一看,这两个按钮看起来一样。但是,右侧的那个实际上是一个<a>
元素,其样式像按钮一样,而左侧的那个是实际的<button>
元素。这两个按钮将来可以成为站点导航的一部分,其中一个需要链接到外部站点。在第一次尝试构建这两个元素时,查看我们查看的第一个示例中的每个组件的代码是有意义的。
如果我们知道这两个元素将具有完全相同的样式,我们可以将每个元素都样式化为<Button />
,将 styled-component 与我们正在创建的按钮的 JavaScript 代码组合在一起。然后我们将完全相同的样式应用于我们指定的<Link />
组件:
查看上面的笔,你可以看到我们已删除了与这两个元素的样式相关的重复 CSS。与其重复用于链接组件的按钮的 CSS,我们可以应用传递给 as 属性的值,如下所示:
<Button as="a" href="#" ... >I am a Link!</Button>
这使我们能够将元素更改为 HTML 标签,同时保留我们已为 Button 定义的样式。
扩展基线样式——也许甚至将其中一些样式放在单个globalStyles.js
文件中——是精简 styled-components 代码的有效方法,使其更易于管理。将 CSS 最小化不仅可以提高网站的性能,还可以避免开发人员将来需要浏览大量 CSS 代码。
是的,但是像你这样一遍又一遍地扩展是一个巨大的性能问题,在我看来应该避免。
很棒的文章!感谢分享你的方法。
是否有与此相关的链接?我记得在某个地方读到过同样的事情,但谷歌搜索却一无所获。
感谢这篇精彩的文章,我不是扩展的忠实粉丝,因为它存在与 Sass 扩展相同的弊端。但它确实有其适用之处。
此外,如果有人热衷于将 styled-components 与 Tailwind 结合使用,请尝试我的 twin.macro 库。
只需在组件基础上使用 tailwind 即可。
或者使用 Chakra UI https://chakra-ui.com/docs/layout/flex。Chakra 感觉非常高效。它包含所有可重用组件,而无需实际编写任何 css。它就像 React 版本的 tailwind。
CSP 如何处理这种情况?
虽然我认为 Flexbox 应该消亡(#TeamCSSGrid),但我真的很喜欢您为 ExampleFlex 扩展基本组件的方式。我认为这种方法非常有用,我明天早上就会开始使用它。
styled-system 与 styled-components 完美搭配,它为扩展组件提供了更大的灵活性。
我在我的主题中添加了许多 CSS 混合器,我可以使用它们,尤其是在 Flexbox 和定位方面。
我的所有排版都在我的主题中,我所有的边框/阴影都在我的主题中,并且我大量使用受 Material 的 React 组件主题启发的生成间距的函数。
这意味着在 80% 的情况下,一旦我添加了注入主题中的一些混合器,我添加的额外样式确实对该组件是唯一的。
仅供参考,我最近也从 Styled Components 切换到 Emotion,我现在倾向于更多地使用 css 属性,而不是将内容包装在
styled
中。来自 Sass/CSS 模块背景,我发现它更熟悉。此外,您不会遇到性能问题和像之前评论者提到的 styled 开发工具混乱。(我认为这种变化与 Emotion 与 SC 没有真正关系,但对我来说,切换是在同一时间发生的)