不要再在JavaScript中写 CSS了

开发 前端
CSS 不应随意放置。许多项目选择将样式写在 JavaScript 中的理由不对。本文列出了常见的误解,以及解决问题的现存 CSS 方案。

本文作者是 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 的作者。

[[195644]] 

小红帽

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。这样就省去了组件和样式间的匹配 ——组件由细粒度的样式结构组成,比如:

  1. import React from 'react'
  2.  
  3. import styled from 'styled-components'
  4.  
  5. // Create a <Title> react component that renders an <h1> which is 
  6.  
  7. // centered, palevioletred and sized at 1.5em 
  8.  
  9. const Title = styled.h1` 
  10.  
  11.   font-size: 1.5em; 
  12.  
  13.   text-align: center; 
  14.  
  15.   color: palevioletred; 
  16.  
  17. `; 
  18.  
  19. // Create a <Wrapper> react component that renders a <sectionwith 
  20.  
  21. // some padding and a papayawhip background 
  22.  
  23. const Wrapper = styled.section
  24.  
  25.   padding: 4em; 
  26.  
  27.   background: papayawhip; 
  28.  
  29. `; 
  30.  
  31. // Use them like any other React component – except they're styled! 
  32.  
  33. <Wrapper> 
  34.  
  35.   <Title>Hello World, this is my first styled component!</Title> 
  36.  
  37. </Wrapper>  

结果:

[[195645]] 

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 可以简明代码

通常伴随着如下的例子:

  1. <TicketName></TicketName> 
  2.  
  3. <div className={styles.ticketName}></div>  

首先——关系不大。差异基本可以忽略。

其次,说的也不对。字符数量取决于样式命名。

  1. <TinyBitLongerStyleName></TinyBitLongerStyleName> 
  2.  
  3. <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 的文章)

我们还可以举个例子看看两者是否相关。

示例:

  1. <PersonList> 
  2.  
  3.   <PersonListItem> 
  4.  
  5.     <PersonFirstName>Foo</PersonFirstName> 
  6.  
  7.     <PersonLastName>Bar</PersonLastName> 
  8.  
  9.   </PersonListItem> 
  10.  
  11. </PersonList>  

语义化是要使用正确的标签构造标记。你能知道这些组件会渲染成什么 HTML 标签吗?不,你不知道。

和下面这段代码比较下:

  1. <ol> 
  2.  
  3.   <li> 
  4.  
  5.     <span className={styles.firstName}>Foo</span> 
  6.  
  7.     <span className={styles.lastName}>Bar</span> 
  8.  
  9.   </li> 
  10.  
  11. </ol>  

Myth 4:拓展样式更容易

v1 版本可以用 styled(StyledComponent) 拓展样式;v2 引进了 extend 方法来拓展已存在的样式,比如:

  1. const Button = styled.button` 
  2.  
  3.   padding: 10px; 
  4.  
  5. `; 
  6.  
  7. const TomatoButton = Button.extend` 
  8.  
  9.   color: #f00; 
  10.  
  11. `;  

这挺好。但是你可以在 CSS 中完成(或者使用 CSS 模块组合 或 SASS 继承混合 @extend)。

  1. button { 
  2.  
  3.   padding: 10px; 
  4.  
  5.  
  6. button.tomato-button { 
  7.  
  8.   color: #f00; 
  9.  
  10.  

难道不比 JavaScript 简单?

Myth 5:给组件设置条件样式更简单

这点是说你可以根据组件属性给组件设置样式,比如:

  1. <Button primary /> 
  2.  
  3. <Button secondary /> 
  4.  
  5. <Button primary active={true} />  

这在 React 中很有用。毕竟组件行为就是由属性控制的。给属性值直接绑定样式有意义吗?可能吧。但是来看看组件的实现代码:

  1. styled.Button` 
  2.  
  3.   background: ${props => props.primary ? '#f00' : props.secondary ? '#0f0' : '#00f'}; 
  4.  
  5.   color: ${props => props.primary ? '#fff' : props.secondary ? '#fff' : '#000'}; 
  6.  
  7.   opacity: ${props => props.active ? 1 : 0}; 
  8.  
  9. `;  

利用 JavaScript 按条件创造样式表是挺强大的,但是这也意味着样式难以理解,对比以下 CSS:

  1. button { 
  2.  
  3.   background: #00f; 
  4.  
  5.   opacity: 0; 
  6.  
  7.   color: #000; 
  8.  
  9.  
  10. button.primary
  11.  
  12. button.seconary { 
  13.  
  14.   color: #fff; 
  15.  
  16.  
  17. button.primary { 
  18.  
  19.   background: #f00; 
  20.  
  21.  
  22. button.secondary { 
  23.  
  24.   background: #0f0; 
  25.  
  26.  
  27. button.active { 
  28.  
  29.   opacity: 1; 
  30.  
  31.  

这样 CSS 更简短(229 VS 222 字符),(个人认为)也更容易理解。此外,还可以用预处理器使 CSS 分组、更短:

  1. button { 
  2.  
  3.   background: #00f; 
  4.  
  5.   opacity: 0; 
  6.  
  7.   color: #000; 
  8.  
  9.    
  10.  
  11.   &.primary
  12.  
  13.   &.seconary { 
  14.  
  15.     color: #fff; 
  16.  
  17.   } 
  18.  
  19.   &.primary { 
  20.  
  21.     background: #f00; 
  22.  
  23.   } 
  24.  
  25.   &.secondary { 
  26.  
  27.     background: #0f0; 
  28.  
  29.   } 
  30.  
  31.   &.active { 
  32.  
  33.     opacity: 1; 
  34.  
  35.   } 
  36.  
  37.  

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 在语法上很相似,只是元素查询操作具体某些元素。

  1. <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 更好。 

责任编辑:庞桂玉 来源: 前端大全
相关推荐

2020-12-01 11:18:34

对外接口枚举

2016-05-04 09:45:01

CSS开发不要

2021-10-27 09:10:50

CSS 技巧else

2012-06-21 13:56:59

Web

2017-03-21 13:03:28

编程体系结构

2010-09-30 14:55:23

Javascriptfloat

2023-02-15 08:17:20

VSCodeTypeScrip

2020-04-07 08:34:00

===开发语言

2021-12-17 15:05:55

CSSwhenelse

2018-10-17 11:20:55

SQL数据库程序员

2017-11-20 09:00:43

跳槽职场精英年终奖

2010-09-01 15:28:11

CSSexpression

2017-02-09 08:21:04

ARAR游戏

2019-11-18 10:16:37

工程师开发网络

2019-11-18 10:05:43

程序员技能开发者

2020-04-03 08:30:44

RabbitMQKafka软件

2013-09-16 10:19:08

htmlcssJavaScript

2011-03-07 09:41:10

JavaScript

2016-12-07 08:21:28

HTMLCSSJS

2009-12-01 13:31:49

点赞
收藏

51CTO技术栈公众号