查看原文
其他

不要再在 JavaScript 中写 CSS 了

2017-06-30 伯乐在线 前端大全

(点击上方公众号,可快速关注)

编译:伯乐在线/古鲁伊

如有好文章投稿,请点击 → 这里了解详情


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


感谢 Max Stoiber。



觉得本文对你有帮助?请分享给更多人

关注「前端大全」,提升前端技能

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存