ant-design/docs/blog/css-in-js.zh-CN.md
2024-07-19 11:00:46 +08:00

51 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 组件级别的 CSS-in-JS
date: 2022-11-25
author: MadCcc
zhihu_url: https://zhuanlan.zhihu.com/p/606291980
yuque_url: https://www.yuque.com/ant-design/ant-design/iv21twb9368r0goc
juejin_url: https://juejin.cn/post/7322352551088635931
---
在 2022 年 11 月 18 日,我们发布了 Ant Design 5.0 的正式版本,同时带入大家视野中的还有 Ant Design 独特的 CSS-in-JS 方案。通过这个方案Ant Design 获得了相较于其他 CSS-in-JS 库更高的性能,但代价则是牺牲了其在应用中自由使用的灵活性。所以我们把它称为“组件级”的 CSS-in-JS 方案。
## CSS-in-JS 的困境
在 CSS-in-JS 中hash 会用于确认一段 style 是否已经插入。而计算 hash 的方法通常是将一段完整的 css 转换为 hash 值。比如在 emotion 中,我们检查页面中的元素就可以看到这样的 style 标签,这样的 style 标签对应的 hash 值每一段都是不一样的:
![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*X5tDQ5VIpcoAAAAAAAAAAAAADrJ8AQ/original)
如此便可以引入一个 CSS-in-JS 被诟病已久的问题:我们在编写代码时写的并不是最终的 css所以每次都需要重新序列化得到 css 后再次计算 hash这就在每次渲染组件时带来了额外的开销。如果你的页面或者组件带有非常复杂或者大量的 CSS-in-JS 代码,甚至样式会跟随组件的 props 变化,那么这个性能消耗便变得不可忽视。<br />针对这个问题,各个 CSS-in-JS 库会有自己的应对方式,这里就先不做赘述,让我们来看一看 Ant Design 的方案。
## 计算 hash
其实我们不难发现,问题其实在于序列化 css 的过程。如果通过缓存的方法去减少序列化 css 的次数呢?对于应用级的 CSS-in-JS 来说,我们很难去找到一个很合适的 key 去确认缓存。但是如果是组件库的话,最终得到的样式则是比较稳定的。根据我们从 v4 及之前版本确定下来的样式结构,每一个组件的样式在相同的主题变量和相同的版本下是不会改变的。反过来说,只有修改了主题变量,或者改变了 antd 的版本,样式才可能会变化。由此我们得到了一个非常简单的计算 hash 的方法:
![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*XuVYRJ_27Q0AAAAAAAAAAAAADrJ8AQ/original)
我们会对所有的 antd 组件应用**相同的** **hash**。如此一来,使用 antd 组件时,我们只会对当前的版本和主题变量进行 hash 计算,而前者可以直接由 `package.json`中得到,后者可以直接从 context 中得到,所以我们并不需要进行繁重的序列化 css 的操作,就可以得到稳定的 hash从而大幅地减少性能消耗。
## 组件缓存
通过上述的方式我们迈出了『组件级』CSS-in-JS 的第一步,但是这还不够。既然是『组件级』,那我们也可以针对组件再次进行优化。
在 Ant Design 中一个组件的样式通常来说是“完整”的也就是说不管这个组件有什么样的变体他的样式会一并存在于组件样式中。如此我们可以再次得到一个结论antd 组件的 props **不会**影响组件样式。这是非常重要的一点,在应用级的 CSS-in-JS 方案中,由于存在 props 影响组件样式的可能,所以不可避免地会在渲染阶段重新生成组件样式,不管如何去优化这一点仍无法忽视。既然我们采用了“组件级”的方案,那么这个问题就可以很轻松地解决:对组件做样式缓存。
![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*yZMNSYVtxnAAAAAAAAAAAAAADrJ8AQ/original)
在 hash 相同的情况下,同一个组件无论使用了多少次、渲染了多少次,样式永远只会在第一次 mount 时生成一次剩下的时间里都会命中缓存这便是『组件级』CSS-in-JS 方案的第二重保险。
## Benchmark
在 Ant Design 5.0 的发布会上,我们简单地做了一次 benchmark在这里可以做一些补充说明
![image.png](https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*upmYSqZ5FwsAAAAAAAAAAAAADrJ8AQ/original)
这个 benchmark 的成立条件是产生一段非常长的不会变更的样式,以此来测试这三个库的基本用法的性能。可以看出在 Ant Design 的“组件级”使用场景下无论是初次渲染还是二次渲染antd 都拥有性能上的优势。由于 styled 在处理稳定的样式时有一定优化,所以这个 benchmark 中二次渲染的性能较好,但在有 props 参与样式计算时仍会和 emotion 一样受到重新计算的影响。
## 『组件级』的局限
在上述的对比中,其实并不能说 antd 一定优于 styled 和 emotion而是在 antd 的组件级使用场景下我们做了相应的优化以取得了性能上的优势。反过来说由于『组件级』的局限性antd 的 CSS-in-JS 方案并不能适用于日常构建应用。
由于特殊的 hash 计算方法和组件缓存,在套用 antd 的 CSS-in-JS 方案时,开发者必须自己提供稳定的 hash 和独特的组件名。对于应用来说,像 css module 这样的自动 hash 的能力反而是更为需要的,同时对应用中海量的组件进行缓存也需要额外的管理成本,一旦出错问题是很难排查的。因此我们更加推荐在组件库中使用“组件级”的 CSS-in-JS 方案。