🌑

Hi Dear.

React CSS-In-JS 方案 -- Linaria Vs Styled-Components

在开发一个 React 应用时,其中一个比较大的挑战就是为应用选择一个合适的样式处理方案。因为我们需要考虑到样式的可维护性,开发体验,以及样式对应用性能的影响等。基于这些考虑,很多开发者会选择使用 CSS-in-JS 方案。CSS-in-JS 方案将 javascript 作用于编写应用样式上。这有利于提升样式的可维护性,在编写样式过程中使用更加模块化的方式,将「动态样式」引入 react 应用中。目前市面上有非常多的 CSS-in-JS 方案。本文选择了使用比较多的两个方案 LinariaStyled-components 进行比较

在本文中,我们将回顾这两个流行的 CSS-in-JS 方案: LinariaStyled-components 。我们将一起研究他们的功能,同时比较他们功能差异,性能以及生态。

CSS-in-JS 解决方案

CSS-in-JS 解决方案给我们提供了新的编写 CSS 的方式。这些方案使用以 javascript 为基础的 API 来创建和编写样式。主要的优点包括:

  • 动态样式:允许开发者编写动态 CSS
  • 元素作用域:可以把样式的范围固定在某些元素上
  • 消除无用代码:会自动除去应用中冗余的 CSS 代码
  • 支持自定义主题
  • 安装和设置简单
  • 支持 ES modules 和 作用域
  • 更加容易编写单元测试
  • 性能提升
  • 支持 SSR
  • 支持所有的 CSS 语法

Linaria

Linaria 是最流行的 CSS-in-JS 解决方案之一,GitHub 拥有 7.1k 个 star 和 260 个 fork。Linaria 是 「零运行时」方法,这意味着它将开发者写好的样式代码在构建时转换为一个单独的 .css 文件。这个行为跟很多的 CSS 预处理器相似,比如 SASS ,LESS

它提供了很多功能,包括:

  • 提供创建 CSS 类的 API 。”css” API 允许开发者创建所选择的样式,同时他也支持模版语法来满足当我们需要插入动态值。
import { css } from '@linaria/core';

const red = "red"
const header = css`
  text-transform: uppercase;
	color: ${red}
`;

<h1 className={header}>Hello world</h1>;

0.png

  • 它也提供可以创建元素的 API. “styled” API 允许开发者创建任何元素,比如: div,p,等等。当然,这个 API 也是支持模版语法来插入对应的变量值的
import { styled } from '@linaria/react'

const Container = styled.div`
  font-size: 35px;
  color: red;
  border: 1px solid red;

  &:hover {
    border-color: blue;
  }

  h1 {
    margin-bottom: 24px;
  }
`;

const App = () => {
    return <Container>
        <h1>Hello World</h1>
    </Container>
  }
export default App;

1.png

  • 它通过 React 的 Props 或者常规变量来管理动态样式。在下面的代码中,我们通过 React 组件传递 props 和一些常规变量到另一个元素上
import { styled } from '@linaria/react';

const Title = styled.h1`
  font-family: inherit;
`;
const medium = 30

const Navbar = styled.nav`
  font-size: ${medium}px;
  color: ${props => props.color};
  border: 1px solid red;

  &:hover {
    border-color: blue;
  }

  ${Title} {
    margin-bottom: 24px;
  }
`;

const App = () => {
    return <Navbar color="#999">
        <Title>Hello world</Title>
    </Navbar>
}

export default App;

2.png

其他的功能包括:

  • 通过 CSS source maps 可以很容易的找到样式变量是在哪里定义的
  • 可以在 JS 代码中开启 CSS Lint https://github.com/stylelint/stylelint
  • 通过 @linaria/atomic 可以支持原子样式

Styled-Components

Styled-Components 也是流行的 CSS-in-JS 解决方案之一。在 GitHub 上拥有 37.2 k 的 star 和 2.3 k 的 forks。Styled-components 让开发者能够通过编写真实的 CSS 代码来修改组件的样式。它在组件和样式之间创建了一个抽象层,从而消除了直接的映射。

提供的能力,包括:

  • 自动提取关键 CSS 和 代码分割:Styled-Components 监控组件,并且在组件渲染到页面的时候插入组件必要的样式代码。同时支持代码分割来加快组件加载的速度
  • 为样式生成唯一的类名,以防止样式的覆盖,拼写错误以及冗余
  • Styled-component 也提供通过 props 或者常规变量为元素注入动态值。“styled” API 允许开发者创建选择的元素,跟 Linaria 一样,Styled-component 也支持大致相同的模版语法
import styled from 'styled-components';

const Button = styled.button`
  background: ${props => props.primary ? "palevioletred" : "white"};
  color: ${props => props.primary ? "white" : "palevioletred"};

  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

const App = () => {
    return <div>
        <Button>Normal</Button>
        <Button primary>Primary</Button>
  </div>
}

export default App;

3.png

  • 样式扩展:Styled-Components 允许在已有的样式上通过 styled 进行扩展
import styled from 'styled-components';

const Button = styled.button`
  color: palevioletred;
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

const TomatoButton = styled(Button)`
  color: tomato;
  border-color: tomato;
`;

const App = () => {
    return <div>
        <Button>Normal Button</Button>
        <TomatoButton>Tomato Button</TomatoButton>
    </div>
}

export default App;

3-1.png

4.png

其他的功能,包括:

  • 维护成本低
  • 更简便的删除不必要的 CSS
  • 支持 SSR
  • 支持主题定制

对比 Linaria 和 Styled-Components

开发者在很长一段时间都在选择最适合自己项目的样式解决方案,而 Linaria 和 Styled-Components 无疑是当中的佼佼者。接下来我们将从:「功能」,「性能」以及 「生态」来对这两个方案进行比较

功能

  • Linaria 是「零运行时」方案,这意味着样式文件会在构建时被单独抽取成 CSS 文件;Styled-Components 则是在构建时通过 javascript 将 CSS 注入,不会生成额外的 CSS 文件
  • 都拥有相似的 CSS 语法(类似 Sass 的风格)
  • 在 React 应用中都可以基于 Prop 实现变量注入(原理是使用 CSS 变量)
  • 都可以通过 CSS source maps 很快找到 CSS 变量定义的位置
  • 都可以通过 stylelint 的方案进行代码检查
  • 都使用 Javascript 来组织代码逻辑,无需使用 CSS 预处理器
  • 都可以平替 Sass 或者 PostCSS 方案

通过上述的的功能描述,我们发现 Linaria 和 Styled-Components 的 API 都比较相似,所以开发者很容易就可以从其中一个方案迁移到另一个方案

基于请求的性能对比

  • 在生产环境中, Linaria 会产生额外的 .css 文件,这将会引起 CSS 文件体积变大,文件数量变多,导致请求数量变多的问题
  • 对于 Styled-Components 来说,相同情况下,CSS 文件体积和数量无疑是更少的,但是会增加 JS bundle 的体积大小

许多争论在于认为 Linaria 产生的 css 文件对性能的影响是比较小的,相对于 Styled-Components ,Linaria 不会增加 JS bundle 体积是一种更好的取舍;而另一些则认为 Linaria 增加了 CSS 冗余代码的可能性。

我们可以在 这里 看到更多关于请求的性能对比

基于页面加载的性能对比

在加入多种页面加载标准之后发现,大部分的页面使用 Linaria 的加载性能要 好于 使用 Styled-Components。其中一个比较重要的原因就是,Linaria 导致的 CSS 资源体积与数量的增加对于页面加载的影响要小于 Styled-Components 导致的 JS bundle 体积的增加

我们可以从下面的资料中看到更多关于加载的对比

基于渲染和用户交互的性能对比

在这个方面的对比,主要是页面元素拖拽交互以及重新渲染;结果显示大部分的 Linaria 会有更少的脚本运行时间,更少的样式重绘重排

我们可以从下面的资料中看到更多关于渲染的对比

生态系统

Styled-components 目前拥有 37.2K GitHub stars, 2.3K GitHub forks,超过 4百万的 NPM 包周下载量,可以说是 CSS-in-JS 最大的生态系统方案;而 Linaria 只有 7.1K GitHub stars, 260 GitHub fork 和 16000 的 NPM 包周下载量。这意味着 Styled-components 会有更大的社区以及讨论热度,更多的课程(学习成本低)以及更多的问题解答等等

总结

本文我们先介绍了 Linaria 和 Styled-Component 的使用。然后又对比两者之间的功能,性能特点以及生态系统。

本文的代码地址:

参考资料

— Oct 11, 2022