CSS-in-JS 技术解惑

September 10 2021 , Category: CSS

早在 2018 年的 7 月,我曾写过一篇《styled-components 的实践贴》

彼时,由于 CSS-in-JS 的方案有限,styled-components 作为当时此项技术的代表吸引了关注者们的眼球,也顺理成章的成为了我在公司内尝试这门技术的首选。

没想到过了 4 年,styled-components 仍然是 CSS-in-JS 技术领域的的领头羊之一:

https://npmcharts.com/compare/styled-components,@emotion/core,jss,emotion,tailwindcss?interval=30&log=true

经过 3 年的成长和进化 ,时下的 CSS-in-JS 技术,可谓进入了一个较为成熟的阶段。

先来简单回顾一下 CSS-in-JS 的特性,以免部分没有体验、使用过的同学一头雾水。

Scoped CSS

Almost 所有的 CSS-in-JS 方案都会生成唯一的 CSS classname

![[Pasted image 20210708141257.png]]

当然,此特性当以 CSS Modules 为代表。

classname 的生成规则,对于每个框架而言都不一样,例如,Emotion 的 classname 是通过 css-${hash} 的规则生成的,而 styled-components 的生成规则则是:

const className = hash(componentId + evaluatedStyles);

这一特性,对于从组件思维出发进行编程的开发者而言,无疑是一剂救世良方。

当然,你可能不喜欢这些随机生成的字符串,更倾向于 BEM 等偏向于通过「约定」的方式来处理 CSS 命名冲突,那么这就是另一个值得一谈的话题了。我们且不在此文中进行论述。

Vendor autoprefixer

Vendor autoprefixer –「自动前缀补齐」 – 这个特性对于习惯使用 Sass 或者 Less 等 Preprocessor 或者 PostCSS 等方案的开发者来说,恐怕再熟悉不过了。

可以说,Autoprefixer 已经是 CSS 编程领域的必备功能,毕竟没有人愿意去手写那一堆当下看来还需要去写的浏览器前缀。

而对于 CSS-in-JS 技术而言,这个特性的实现上刚好满足期望,这可以让我们在编程的时候,不再依赖任何的预处理或者后置处理工具。

样式的定义

示例:

const Header = styled.h1`
  font-size: 20px;
  color: #333;
  line-height: 1.5;
  ${(props) => {}};
`;

我们可以跟平日里写 CSS 一样去写 CSS-in-JS 的代码,区别仅仅是将其用 JS 的模板字符串语法,将其包裹起来。

而在此之外,你可以用到任何的 JS 语法,例如:

import theme from 'common/theme';

const Header = styled(Button)`
  font-size: ${theme.mainFontSize};
  color: ${theme.mainBlack};
  line-height: 1.5;

  .zent-btn {}
`;

话说……

谁记得 Sass 里的循环和函数是怎么写的?

……

当然,除了上述以 styled-components 为代表的 React 组件语法,众多 CSS-in-JS 方案都提供了另一种方式:

const header = css({
	fontSize: '12px',
	color: '#333',
});

const Header = () => <div className={header}>WTF</div>;

这种方式,有点类似现在在 Scss 语法中使用 :local(.header) {}

像 Emotion 这样的方案,还开始支持了作为 props 的方式进行传入

const Header = () => <div css=>WTF</div>;

这种方式似乎和被我们唾弃的 inline style 是一样的,而实际上几乎当前还存活着的 CSS-in-JS 方案,都不会最终生成 inline style

所以,上述不同的方式,只是在语法上看起来相似而已 – 最终 Emotion 等方案,还是会将其生成唯一的 className,而在对于 CSS 样式的表达能力上,与上文提到的 styled 的方式几乎无异。

无论是 styledclassName 还是 css props 的方式,CSS-in-JS 库的作者们希望能提供足够多的方式去满足开发者的编程癖好。

不过我觉得这三种主流方式的外在表现形式,其实都不是特别重要,可能对于真正的开发者在实操的时候,只是编码方式上存在部分区别 – 毕竟条条大路通罗马。

对于我们来说,最重要的,IT IS ALL JAVASCRIPT

实现方式

CSS-in-JS 被人诟病最多的,可能就是其性能问题 – 这与具体 CSS-in-JS 的实现方式息息相关。

一般来说,会有两种方式去实现:

① 动态插入 <style> 标签

此方案使用 CSSOM 的 CSSStyleSheet API 动态管理 CSS – 可以通过 insertRule 或者 deleteRule 管理 CSS。

最终通过 1~N 个 <style> 标签插入到 HTML head 标签中。

② 编译时生成静态的 CSS 文件

这种方式与直接编写 CSS 相比,除了编码时使用 CSS / JS 的区别,在运行时来讲是几乎一致的。

两种实现方式各有优劣:

例如,动态插入的方式,需要引入运行时的 JS 库,而这部分运行时的代码则决定了之后性能。

静态编译则会需要将所有的样式编译到 CSS 中,这可能会增加不少额外的 CSS 文件内容。从而增大了整体程序的体积。

大多数的 CSS-in-JS 方案,都是通过第 ① 种方式实现的。

还是以 styled-components 为例,在去年发布了 v5 版本之后,官方的数据表示:

客户端样式更新的性能相比 v4 提升了 18%,bundle 体积减少了 19%。当我们回顾历史版本的 styled-components 时,会发现:

那么 v2 到 v5,性能提升了将近 15 倍 – 而 v6 则已经在路上了。

在现代浏览器性能日益提升的 21 世纪 20 年代,以及各个类库开发人员对内部引擎代码的优化和改善,性能问题似乎不再成为我们技术选型的最大瓶颈。

-EOF-

支持作者 | 文章采用 CC BY-NC-SA 4.0,转载请注明出处