本文作者是 react-css-modules 和 babel-plugin-react-css-modules 的作者。并不是对 CSS in JavaScript: The future of component-based styling,或是使用样式组件的反对,而是一种补充,web 开发者要了解自己的需求,明白自己使用 styled-components 的真正原因。
9 个谎言
CSS 不应随意放置。许多项目选择将样式写在 JavaScript 中的理由不对。本文列出了常见的误解,以及解决问题的现存 CSS 方案。
本文的任何言论都没有对某个项目或人进行人身攻击的意思。styled-components 是 React 的目前趋势,所以我将 styled-components 定义为“JavaScript 中的 CSS”。
styled-components 的发起人(Max Stoiber、Glen Maddern 以及所有的贡献者)都很聪明、想法独特,出发点也是好的。
为了完全透明,我还要指出我是 react-css-modules 和 babel-plugin-react-css-modules 的作者。
小红帽
CSS 和 JavaScript 历史
层叠样式表(CSS)是为描述标记语言文档的展现样式而出现的。JavaScript 是为了组合图片、插件等组件而创造的一种“胶水语言”。随着发展,JavaScript 拓展、转变,有了新的应用场景。
Ajax 的出现(2005)是一个重要的里程碑。这时 Prototype、jQuery、MooTools 等库已经吸引了大量的拥护者,共同解决后台跨浏览器数据获取问题。这又引发了新的问题:如何管理数据?
到了 2010 年,Backbone.js 出现,成为了应用状态管理的行业标准。不久后,Knockout 和 Angular 双向绑定的特点吸引了所有人。之后,React 和 Flux 出现,开启了单页应用(SPA)的新纪元,组件构造应用。
那么 CSS 呢?
借用 styled-components 文档中的话:
纯 CSS 的问题在于它产生的那个时代,网站由文档组成。1993 年,网站产生,主要用于交换科学文献,CSS 是设计文献样式的解决方案。但是如今我们构建的是丰富的、面向用户的交互应用,而 CSS 并不是为此而生的。
我不这么认为 。
CSS 已经发展到可以满足现代 UI 的需求了。过去十年中出现的新特性数不胜数(pseudo-classes、pseudo-elements、CSS variables、media queries、keyframes、combinators、columns、flex、grid、computed values 等等)。
从 UI 的角度看,“组件”是文档中一个独立的片段(<button /> 就是个组件)。CSS 被设计用来样式化文档,包括所有组件。问题在哪?
俗话说:“工欲善其事必先利其器”。
Styled-components
styled-components 可以用标记模板字面量在 JavaScript 中写 CSS。这样就省去了组件和样式间的匹配 ——组件由细粒度的样式结构组成,比如:
- import React from 'react';
- import styled from 'styled-components';
- // Create a <Title> react component that renders an <h1> which is
- // centered, palevioletred and sized at 1.5em
- const Title = styled.h1`
- font-size: 1.5em;
- text-align: center;
- color: palevioletred;
- `;
- // Create a <Wrapper> react component that renders a <section> with
- // some padding and a papayawhip background
- const Wrapper = styled.section`
- padding: 4em;
- background: papayawhip;
- `;
- // Use them like any other React component – except they're styled!
- <Wrapper>
- <Title>Hello World, this is my first styled component!</Title>
- </Wrapper>
结果:
Live demo(https://www.webpackbin.com/bins/-KeeZCr0xKfutOfOujxN)
styled-components 目前是 React 的 趋势 。
我们要理清一件事情:styled-components 只是 CSS 层面的高度抽象。它只是解析定义在 JavaScript 中的 CSS,然后生成对应 CSS 的 JSX 元素。
我不喜欢这个趋势,因为存在很多误解。
我在 IRC、Reddit 和 Discord 上调查了大家使用 styled-components 的原因,整理了一份选择使用 styled-components 常见原因的列表 。我称之为 myths。
Myth #1:避免全局命名空间和样式冲突
我把这条算作 myth 是因为它听起来就像之前这些问题没有得到解决一样。CSS Modules、Shadow DOM 还有很多命名协议(比如 BEM)已经早就在社区中解决了这个问题。
styled-components(就像 CSS modules)只是替人完成了命名的任务。人总会犯错,计算机犯错少点而已。
但就本身而言,这并不是使用 styled-components 的好理由。
Myth 2:styled-components 可以简明代码
通常伴随着如下的例子:
- <TicketName></TicketName>
- <div className={styles.ticketName}></div>
首先——关系不大。差异基本可以忽略。
其次,说的也不对。字符数量取决于样式命名。
- <TinyBitLongerStyleName></TinyBitLongerStyleName>
- <div className={styles.longerStyleName}></div>
这同样适用于本文之后的构造样式(Myth 5:给组件设置条件样式更简单)。styled-components 只是在多数基本组件的情况下稍胜一筹。
Myth 3:styled-components 使人更关注语义化
前提就不对。样式和语义化代表着不同的问题,需要不用的应对方案。引用 Adam Morse(mrmrs)的话:
内容语义化和视觉样式 没有半点关系。当我用乐高建造东西时,我从来不会想“这是引擎的一部分”,我想着“这是个 1×4 的蓝色乐高,我用来随便做什么都行”。不论水下潜水基地还是飞机——我清晰地知道怎么用这个乐高块。
– http://mrmrs.io/writing/2016/03/24/scalable-css/
(强烈建议读一读 Adam 关于 可拓展 CSS 的文章)
我们还可以举个例子看看两者是否相关。
示例:
- <PersonList>
- <PersonListItem>
- <PersonFirstName>Foo</PersonFirstName>
- <PersonLastName>Bar</PersonLastName>
- </PersonListItem>
- </PersonList>
语义化是要使用正确的标签构造标记。你能知道这些组件会渲染成什么 HTML 标签吗?不,你不知道。
和下面这段代码比较下:
- <ol>
- <li>
- <span className={styles.firstName}>Foo</span>
- <span className={styles.lastName}>Bar</span>
- </li>
- </ol>
Myth 4:拓展样式更容易
v1 版本可以用 styled(StyledComponent) 拓展样式;v2 引进了 extend 方法来拓展已存在的样式,比如:
- const Button = styled.button`
- padding: 10px;
- `;
- const TomatoButton = Button.extend`
- color: #f00;
- `;
这挺好。但是你可以在 CSS 中完成(或者使用 CSS 模块组合 或 SASS 继承混合 @extend)。
- button {
- padding: 10px;
- }
- button.tomato-button {
- color: #f00;
- }
难道不比 JavaScript 简单?
Myth 5:给组件设置条件样式更简单
这点是说你可以根据组件属性给组件设置样式,比如:
- <Button primary />
- <Button secondary />
- <Button primary active={true} />
这在 React 中很有用。毕竟组件行为就是由属性控制的。给属性值直接绑定样式有意义吗?可能吧。但是来看看组件的实现代码:
- styled.Button`
- background: ${props => props.primary ? '#f00' : props.secondary ? '#0f0' : '#00f'};
- color: ${props => props.primary ? '#fff' : props.secondary ? '#fff' : '#000'};
- opacity: ${props => props.active ? 1 : 0};
- `;
利用 JavaScript 按条件创造样式表是挺强大的,但是这也意味着样式难以理解,对比以下 CSS:
- button {
- background: #00f;
- opacity: 0;
- color: #000;
- }
- button.primary,
- button.seconary {
- color: #fff;
- }
- button.primary {
- background: #f00;
- }
- button.secondary {
- background: #0f0;
- }
- button.active {
- opacity: 1;
- }
这样 CSS 更简短(229 VS 222 字符),(个人认为)也更容易理解。此外,还可以用预处理器使 CSS 分组、更短:
- button {
- background: #00f;
- opacity: 0;
- color: #000;
- &.primary,
- &.seconary {
- color: #fff;
- }
- &.primary {
- background: #f00;
- }
- &.secondary {
- background: #0f0;
- }
- &.active {
- opacity: 1;
- }
- }
Myth 6:有利于代码组织
有些人告诉我他们喜欢 styled-components,因为它可以让样式和 JavaScript 在一个文件中。
我理解同一组件有许多文件很烦,但是把样式和标记塞进一个文件的方法很糟糕。这样不仅版本控制难以回溯,而且所有组件都需要滚动很长一段距离,而不是简单地点下按钮。
如果一定要把 CSS 和 JavaScript 放在一个文件中, 可以考虑使用 css-literal-loader。它可以在 build 时用 extract-text-webpack-plugin 提取 CSS,用标准 loader 配置处理 CSS。
Myth 7:DX 很方便,这工具太棒了!
很明显你没用过 styled-components。
- 一旦样式写错了,整个 app 会崩溃,并输出长长的调用栈错误(v2 更奇葩)。相比之下,CSS “style error” 只是元素渲染地不对而已。
- 元素没有 className,所以调试时不得不去对比 React 元素树和 DevTools DOM 树(v2 可以用 babel-plugin-styled-components 定位)。
- 没有语法检查(有一款 样式检查插件 正在开发中)。
- 不合法的样式会被忽略(比如:clear: both; float left; color: #f00; 不会报 error 或 warning,只能祈祷调试好运了,即使看了 styled-components 源码,还是花了我 15 分钟查看调用栈。最后我在聊天中把代码粘出来寻求帮助,才有人提醒是少了:。你注意到了吗?)
- 支持语法高亮、代码补全以及其它 IDE 细节的 IDE并不多。如果你在金融或政府机构工作,很可能无法使用 Atom IDE。
Myth 8:性能更好,bundle 更小
- 事实是,styled-components 无法提取静态 CSS 文件(比如使用 https://github.com/webpack-contrib/extract-text-webpack-plugin)。这意味着浏览器无法开始解释样式直到 styled-components 解析、加载到 DOM上。
- 缺少文件分离意味着无法分开缓存 CSS 和 JavaScript。
- 所有样式化的组件都会额外包装一层 HoC。这是不必要的性能损耗。因为类似的结构缺陷,我终止了 https://github.com/gajus/react-css-modules(但创建了 https://github.com/gajus/babel-plugin-react-css-modules)。
- 因为 HOC,如果在服务端渲染,会导致标记文档大很多。
- 有 keyframes, 我也不需要用动态样式值做动画。
Myth 9:它可以开发响应式组件
这说的是依据环境给组件设置样式的能力,比如父容器偏移量、子元素数量等。
首先,styled-components 和响应式没什么关系。这已经超出了这个主题的范围。这种情况最好直接设置组件的 style,以避免额外的成本。
但是,元素查询是个有趣的问题,也逐渐成为 CSS 中的一个高热话题,主要是 EQCSS 等类似项目。元素查询和 @media queries 在语法上很相似,只是元素查询操作具体某些元素。
- <a href="http://www.jobbole.com/members/feiguohai46">@element</a> {selector} and {condition} [ and {condition} ]* { {css} }
{selector} 是 CSS 选择器对应着一或多个元素。例如:#id 或 .class
{condition} 由尺寸和值组成。
{css} 可以包含:任何合法的 CSS 规则。(例如:#id div { color: red })
元素查询可以用 min-width、max-width、min-height、max-height、min-characters、max-characters、min-children、max-children、min-lines、max-lines、min-scroll-x、max-scoll-x 等 (详见 http://elementqueries.com/)条件给元素设置样式。
总有一天类似 EQCSS 的内容也会出现在 CSS 标准中的(希望如此)。
等下!
大部分内容都长期有效,无论是社区、React 变更或 styled-components 本身。但意义何在?CSS 已被广泛支持,有大量的社区,也确实行之有效。
本文的目的并不是阻止读者在 JavaScript 中使用“CSS”或是 styled-components。styled-components 一个很棒的使用场景是:更好的跨平台支持性。不要因为错误的理由使用它。
那么我们应该用什么呢?
使用 Shadow DOM v1 还为时尚早(51% 支持率)。CSS 应遵循命名协议(建议 BEM),如果担心类名冲突(或懒得用 BEM),可以用 CSS modules。如果你在开发 React web,考虑用 babel-plugin-react-css-modules。如果在开发 React Native,styled-components 更好。