[译]停止使用 CSS-in-JS 的九个理由

原文: [https://medium.com/@gajus/stop-using-css-in-javascript-for-web-development-fa32fb873dcc]

原文发表于: 2016-04-27

最近社区上 CSS-in-JS 与 CSS-Module 的争论持续不断, 上一篇译文统一样式语言 介绍了关于 CSS-in-JS 的优点. 本篇则是站在反对者的观点上, 反驳了一些(作者认为上的) CSS-in-JS 误区.

关于 CSS

styled-components 的文档说

原始 CSS 是用于文档形式的 web 的. 在1993年, 网页都是一些静态的文档, 那时用 CSS 来解决文档的样式问题. 如今, 我们构建的是更加丰富, 可交互, 面向用户的 APP. CSS 已经不适合构建这样的项目

我不赞同. CSS 也在与时俱进加入了许多新功能. (伪类, 伪元素, CSS 变量, \@media, 帧动画, 组合, 列, flex, 网格, calc, etc…)

从 UI 的角度上, “组件”就是独立的文档碎片. (比如 <button /&rt;就是一个组件) CSS 用来描述文档, 也就是所有组件的集合, 有何不妥呢. 正所谓物尽其用.

Styled-components

styled-components 允许使用模板文本的方式在 JS 中定义 CSS. 通过一种底层构建的方法取消了组件与样式组件之间的关系. demo

这成为了 React 中一种新型定义组件样式的趋势.

先明确一点: styled-components 组件是 CSS 的高阶抽象. 它在 JS 中定义 CSS, 并在 JSX 解析时绑定在对应元素上.

我不喜欢这种趋势, 它充满了太多误解. 调查了IRC, reddit, discord等地方, 人们用 styled-components 的理由. 我把他们称为”幻想” (myths)

幻想1: 解决了全局命名空间冲突

我称之为幻想(myths) 是因为问题根本就不存在. CSS Modules, Shadow DOM, 以及数不清的命名规范约束(比如 BEM) 很早之前就已经解决了这个问题.

styled-components 和 CSS modules 一样, 从人类手中拿走了命名 className 的权利. 人会犯错, 程序的错误要少的多.

总之, 这不是一个用 styled-components 的好理由

幻想2: styled-components 会让代码更简洁

一个例子是

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

首先, 这差别几乎微不足道, 这也不完全是正确的.

1
2
<TinyBitLongerStyleName></TinyBitLongerStyleName>
<div className={styles.longerStyleName}></div>

这例子还会在 “幻想5: 有益于样式组件条件化”出现. styled-components 只在一些基础的组件中有优势.

幻想3: styled-components 能加强语义化

这个前提是错的. 样式和语义是不同的问题, 需要不同的解决方案. 引用Adam Morse的一段话

1
内容的语义和样式无关. 用乐高时, 我不会想"哦, 这是一个引擎砖块", 我只会想, "哦, 这是一个1x4的砖块". 不管我是建造一个水下基地还是飞机, 我都知道怎么用乐高砖块.

比如说

1
2
3
4
5
6
<PersonList>
<PersonListItem>
<PersonFirstName>Foo</PersonFirstName>
<PersonLastName>Bar</PersonLastName>
</PersonListItem>
</PersonList>

这里用的标记更有语义化, 但你知道组成 HTML 的标签是什么么? 当然不.

编译后是这样

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

幻想4: 便于样式继承

styled-componentsv1 中继承需要 styled(StyledComponent) v2 则是通过 extend 进行继承.

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

这很好啊, 但是用 CSS 也一样可以做到 (用CSS-Module的组合, 或者 SASS 的 mixin \@extend.

幻想5: 有益于样式组件条件化

基础例子是

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

React 中很常见, 通过 props 去定义组件的行为. 这让组件风格更加直观了吗? 或许吧, 但看看组件的定义部分:

1
2
3
4
5
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};
`;

直接用 CSS 定义要方便的多, 并且这种定义通过解释器会更复杂,对比生成出的 CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 有更多的优化空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
button {
background: #00f;
opacity: 0;
color: #000;

&.primary,
&.seconary {
color: #fff;
}
&.primary {
background: #f00;
}
&.secondary {
background: #0f0;
}
&.active {
opacity: 1;
}
}

幻想6: 更适合代码组织

一些人告诉我他们喜欢 CSS 和 JS 在同一文件里.

可以理解, 一个组件定义放在不同文件里缺失比较无趣. 但是把所有东西都丢在一个文件里相当糟糕. 不但不容易追踪, 而且稍微比一个简单的按钮复杂一些的组件都会出现很长的滚动条.

如果一定要让 CSS 和 JS 在一起, 也可以用css-literal-loader. 这个 loader 可以允许你在构建的时候提取出 css.

幻想7: 让前端更好了, 工具也很给力

显然你没用过 styled-components

  1. 样式中任何一点错误, 都会导致构建失败, 抛出一个很长的错误栈, 并且无法显示组件.

  2. 没有一个对人类友好的 className, 调试时不能很方便在 React 元素与 DOM 元素之间切换. (v2通过babel-plugin-styled-components 有所改善)

  3. 没有 lint工具 (样式有 style lint)

  4. 一些错误很容易被忽视. (比如 clear: both; float left; color: #f00; float 后面少了一个冒号但却不会抛出错误或警告)

  5. 语法高亮

(译注: 如今上述问题都已经有相应的解决方案, 比如 stylelint-processor-styled-components, vscode 扩展, 等)

幻想8: 更好的性能, 更小的体积

styled-components不能解析出一个静态 CSS 文件. 这意味你的浏览器只有解析完 styled-components 才能开始渲染样式.

分离瓶颈, 没办法很好的缓存分离 CSS 和 JS

所有styled-components生成的组件都会被包裹一层高阶组件(HoC). 这也浪费了性能.

上一点的 HoC, SSR 时样式组件体积都会变大

动画组件

幻想9: 响应式开发

这取决于基于周围环境, 比如父代尺寸, 子代数量, 等.

styled-components 并没有对此做什么. 这种情况下最好直接定义组件样式, 以免出错.


这篇文章也不是说 CSS-in-JS一无是处, 用 styled-components 还是有一个很好的场景的: 跨平台支持.

所以, 现在用什么呢?

Shadow DOM 太过超前 (51% 浏览器支持). 如果害怕样式冲突, 尝试用 BEM 约束命名. 如果还是担心(或者懒得用 BEM), 可以试试 CSS modules. 如果是在写 React 的 web, 还可以用 babel-plugin-react-css-modules. React-Native 的话, styled-components 还是很好用的