这些 CSS-in-JS 库,谁更适合你

676 阅读8分钟

写在开头

网上有许多关于 CSS-in-JS 和 CSS Modules 孰优孰劣的讨论与文章,这和此篇文章主题相去甚远,因此不会花费笔墨对此展开探讨;本文将通过比较几类流行的 CSS-in-JS 第三方库来帮助大家判断如何选择它们。

从关注点分离(Separation of concerns)到 CSS-in-JS

最初,网页开发推崇“关注点分离”的原则:即 HTML、CSS、JavaScript 三种技术分离,拆分在不同的文件里。简单来说,就是让大家最好不要写 Inline Styles 和 Inline Script。

.dataList-Head{
    align-items: center
    display: flex;
    justify-content: space-between;
    height: 10%;
    margin-left: 3%;
    width: 80%;
}
.dataList-Head .head_left{
    align-items:center;
    display: flex;
    height: 100%;
    width: 100%;
}

.dataList-Head .head_left .title{
    align-items: center;
    background-image: linear-gradient(
        0deg,
        #6cffff 30%,
        #e66465 60%,
        #9198e5 100%
    );
    display: flex;
    font-size: 1.5rem;
    font-stretch: normal;
    font-weight: bold;
    letter-spacing: 2px;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

不过 CSS 本身存在不少缺陷,从上面的代码片段也可以看出一些问题:

  1. 由于不支持嵌套、选择器繁琐冗长,导致写起来费时费力。
  2. 层级结构不够清晰,难以区分它们之间的从属关系,是父子还是兄弟?
  3. 随着项目迭代,且缺乏检查机制,后期维护困难。

同样地还有一些上面代码没有体现出来,却也被大家经常提及的问题,诸如“没有作用域概念,致使样式容易冲突”、“为了避免命名污染,代码难以复用”、“缺少共用变量,无法定义全局的背景颜色、按钮颜色”等等,不一而足。

近年来,随着组件化、模块化概念的流行,特别是为了解决大型应用的 CSS 维护问题,本质在于通过 JavaScript 来声明、维护样式的 CSS-in-JS 成为了可行的解决方案。在组件内部使用 JavaScript 对 CSS 进行抽象,这样就可以做到一个组件对应一个文件、一个文件便是一个组件,一次开发公共使用的目的。

一些常见的 CSS-in-JS 库的对比

CSS-in-JS 兴起以来,有多达几十种基于 CSS-in-JS 思想衍生出来的第三方库,虽然这些库解决的问题大同小异,但是它们的实现方法和语法却大相径庭,从实现方法上区分大体分为两种:唯一 CSS 选择器和标签内联样式(Unique Selector vs. Inline Styles)。让我们先来看一下它们之中较为流行的几个库最近 6 个月的 npm trends

Downloads trends
Stats trends

  • 许多有关 CSS-in-JS 的文章中大都提到过 MicheleBertoli/css-in-js 这个仓库,在这里并不推荐大家作为参考,原因无他:CSS-in-JS 的生态不断在变革,时有新的库出现,也有旧的库停止维护,但该仓库的更新还停留在 2018 年 8 月 9 日。
  • 此外,State of CSS 同样统计了目前较为常见的 6 个 CSS-in-JS 库的相关数据(注:统计中加入了 CSS Modules 和 Styled JSX,可以自行忽略),大家感兴趣的话也可以作为参考。

接下来我们再逐一看下这些比较有代表性的实现:

Aphrodite

Aphrodite 的 GitHub 自述文件中可以看到它所支持的所有特性,这里不再花费篇幅赘述,关于其他库的介绍也是如此。

Aphrodite: Inline Styles that work.

事实上,不是所有人都能接受 JSS(见下文)的写法风格。所以 Aphrodite 提供了一种方案:在尽可能使用 CSS 语法的情况下,使样式只作用于引入它的组件,从而解决作用域的问题。

类名选择器的唯一性

import React, { Component } from "react";
import { css, StyleSheet } from "aphrodite";
import NavBar from "./components/NavBar";

const styles = StyleSheet.create({
    navBarItem: {
        flexDirection: "row",
        fontSize: 16,
        fontWeight: "300",
        // 这是一种覆盖嵌套类的 hack 方法
        ":nth-child(1n) > .testClass": {
            color: "green"
        }
    }
});

export default class NavBarComponent extends Component {
    render() {
        return (
            <NavBar hidden={this.state.navBarHidden}>
                <div className={css(styles.navBarItem)}>
                    <a className="testClass">Action 1</a>
                </div>
                <div className={css(styles.navBarItem)}>Action 2</div>
            </NavBar>
        );
    }
}

不过像 Aphrodite 这样把 HTML 结构嵌套混合着 className={css()} 的写法,应该会有相当多的人不能接受。

Emotion

EmotionStyle Components 几乎具有相同的功能,两个库都可以使用独立的样式组件,也可以封装样式并将其应用于 HTML 标签。如果从性能方面比较,由于 Style Components v5 正式版已经上线,因此它的性能会更好一些,可以通过阅读这篇文章来了解更多关于 v5 版本的信息。

相较于 Style Components,Emotion 的优势是对 source maps 的支持;而在 Style Components 中,这个问题一直悬而未决。

Emotion 的缺点在于相关的文档和教程较少;且 Style Components 基于 GitHub star、activity、npm install 等方面是 Emotion 的 3 倍左右,或许这本身并不能说明 Style Components 比它更好,但是如果两个库在其他方面都或多或少地相同,那么更高的流行程度往往意味着更低的风险

同样地如果你决定使用两者中的任意一个,一段时间后又考虑迁移至另一个,那也没有必要过于纠结,它们的语法高度相似,所以花费的成本并不会太多。

Glamor

关于 Glamor,在这里引用官方文档的一段话:

CSS-in-JS library Glamor is not actively maintained. The maintainer recommends using Emotion.

JSS

JSS 更加适合不愿意将 CSS 和 JavaScript 写在一个文件之中的开发者。它使用类似于 JSON 的语法书写 CSS,如果你不擅长使用 CSS Tricks,那么它将是更为理想的 CSS-in-JS 库。

import jss from 'jss'
import preset from 'jss-preset-default'
import color from 'color'

// One time setup with default plugins and settings.
jss.setup(preset())

const styles = {
    '@global': {
        body: {
            color: 'green'
        },
        a: {
            textDecoration: 'underline'
        }
    },
    withTemplates: `
        border-radius: 3px;
        background-color: green;
        color: red;
        margin: 20px 40px;
        padding: 10px;
    `,
    button: {
        fontSize: 12,
        '&:hover': {
            background: 'blue'
        }
    },
    ctaButton: {
        extend: 'button',
        '&:hover': {
            background: color('blue')
            .darken(0.3)
            .hex()
        }
    },
    '@media (min-width: 1024px)': {
        button: {
            width: 200
        }
    }
}

const {classes} = jss.createStyleSheet(styles).attach()

document.body.innerHTML = `
    <button class="${classes.button}">Button</button>
    <button class="${classes.ctaButton}">CTA Button</button>
`

JSS 与 Style Components 最大的区别在于前者更专注于实现一个 CSS 风格的 JSON 语法(当然你也可以在模板字符串中书写 CSS),后者注重于使用标签模板字符串(Tagged template literals)实现原生的 CSS 语法;相较而言,后者几乎没有学习成本。

此外,由于 JSS 兴起较晚,即使它的官方文档已经相当完备,但在中文社区,它的受欢迎程度和相关文档上依旧与 Style Components 存在不少差距,因此 JSS 的上手难度更高一些。

需要特别注意的是:从 Version 10 开始,JSS 不再支持基于 HOC 的 API,并且在以后的版本中可能会删除它。

Radium

Radium 几乎是最早的 CSS-in-JS 方案之一,实现原理是把 CSS 动态转换为标签内联样式(Inline Styles),即通过 Style 属性传入内联样式,完全规避选择器全局作用域的问题。让我们来看一下 Radium 生成的CSS:

Radium 生成的 CSS

标签内联样式的实现相较于唯一 CSS 选择器的实现有以下优点:

  1. 无需额外的操作,自带局部作用域的效果。
  2. 可以避免权重冲突,内联样式的权重(specificity)最高。
  3. 最终生成的样式方便开发者调试。

但同时它也存在以下问题:

  1. CSS 权重过高。
  2. 只支持部分伪类,且表现行为和 CSS 不一致。
  3. 重写第三方组件复杂。

值得一提的是:为了方便测试,Radium 提供了一个 TestMode 对象来控制内部状态和行为,对象内部定义了 clearStateenabledisable 3 个方法。如果你不知道如何使用它们,可以参考这个文件中的用法。

Style Components

Style Components 是当下最被认可和接受的 CSS-in-JS 库,也是我们 front-end team 目前所采用的方案,它使用 ECMAScript 6 的标签模板字符串(Tagged template literals)提供的解析函数功能来实现兼容 CSS 的书写语法,学习曲线较为平滑,来看下它的基本用法:

const Button = styled.a`
    /* This renders the buttons above... Edit me! */
    display: inline-block;
    border-radius: 3px;
    padding: 0.5rem 0;
    margin: 0.5rem 1rem;
    width: 11rem;
    background: transparent;
    color: white;
    border: 2px solid white;

    /* The GitHub button is a primary button
    * edit this to target it specifically! */
    ${props => props.primary && css`
        background: white;
        color: palevioletred;
    `}
`

在使用了一段时间之后,我大致总结了以下几个优点:

  1. 模板字符串中所用的语法和 CSS 中完全一样,惊叹于作者对 Tagged template literals 的神运用。
  2. All in Component,没有复杂的配置,开箱即用。
  3. 自动处理命名前缀和作用域的问题。
  4. 迁移以及语法学习成本较小。
  5. 相关文档非常丰富,并且更容易找到解决问题的资源和解决方案。

最后

事物都有两面性,CSS-in-JS 也不例外,大家一定要根据自己的实际情况进行衡量和取舍,进而确定是不是要在我们的项目中去使用它,永远不要为了使用一个技术而使用一个技术。同时,面对各式各样地 CSS-in-JS 第三方库,我始终认为,需要结合业务场景、团队习惯等因素决策,理念上求同存异,选取适合实际项目的才是最好的解决方案。


欢迎关注我们的微信公众号「RightCapital」