本文首发于微信公众号:大迁世界, 我的微信:qq449245884,我会第一工夫和你分享前端行业趋势,学习路径等等。
更多开源作品请看 GitHub https://github.com/qq449245884/xiaozhi ,蕴含一线大厂面试残缺考点、材料以及我的系列文章。
快来收费体验ChatGpt plus版本的,咱们出的钱
体验地址:https://chat.waixingyun.cn
能够退出网站底部技术群,一起找bug.
本文是由 Emotion 的第二大沉闷维护者 Sam 分享,本文第一人称都指的是 Sam。Emotion 是一个宽泛风行的 CSS-in-JS 库,用于React。文文章 Sam 会带大家深入探讨 CSS-in-JS 最后吸引人的起因,以及为什么作者(以及Spot团队的其余成员)决定放弃它。
什么是 CSS-in-JS?
顾名思义,CSS-in-JS 就是在 JS 或 TS 中间接编写 CSS,为 React 组件提供款式,如下所示:
// Object Styles 形式function ErrorMessage({ children }) { return ( <div css={{ color: 'red', fontWeight: 'bold', }} > {children} </div> );}// String Styles 形式const ErrorMessage = styled.div` color: red; font-weight: bold;`;
styled-components和Emotion是React社区中最风行的CSS-in-JS库。尽管我只应用了Emotion,但我置信本文的所有观点也实用于styled-components。
本文重点介绍运行时CSS-in-JS,这个类别包含 styled-components 和 Emotion。运行时CSS-in-JS 仅仅意味着库在利用程序运行时解释并利用你的款式。咱们会在文章的最初简要探讨编译时 CSS-in-JS。
CSS-in-JS 的好、坏、丑
在探讨 CSS-in-JS 编码模式及其对性能的影响之前,先来看看为什么有的开发者会应用 CSS-in-JS,有的不会应用。
益处
1.部分作用域的款式。在写一般的CSS时,很容易不小心将款式利用到其它文件中。例如,假如咱们正在写一个列表,每一行都应该有一些 padding
和 border
。咱们可能会这样写:
.row { padding: 0.5rem; border: 1px solid #ddd; }
几个月后,当咱们齐全遗记了这个列表时,又创立了一个列表。而后也设置了 className="row"
。当初,新组件的行有一个难看的边框,而咱们却不晓得为什么! 尽管这类问题能够通过应用较长的类名或更具体的选择器来解决,但作为开发者还是要确保没有类名抵触。
CSS-in-JS 齐全解决了这一问题,它使款式默认为本地作用域。如果把下面的款式写成这样:
<div css={{ padding: '0.5rem', border: '1px solid #ddd' }}>...</div>
这样 padding
和 border
就不可能利用到其它元素了。
2.托管。如果应用一般的CSS,则能够将所有.css
文件放在 src/styles
目录中,而所有的React组件都在 src/components
中。随着应用程序的大小的增长,很难判断每个组件应用哪些款式。很多时候,你的CSS中会呈现死代码,因为没有简略的办法能够说出这些款式没有应用。
一个更好的组织代码的办法是将所有与单个组件相干的货色放在同一个中央。这种做法被称为colocation (托管)。
问题是,在应用一般的CSS时,很难实现 colocation,因为CSS和JavaScript必须放在独自的文件中,而且无论.css
文件在哪里,你的款式都会全局利用。另一方面,如果应用CSS-in-JS,能够间接在应用它们的React组件中编写款式 如果操作切当,这将极大地提高应用程序的可维护性。
3.能够在款式中应用JavaScript变量。CSS-in-JS 能够在款式规定中援用JavaScript变量,例如:
// colors.tsexport const colors = { primary: '#0d6efd', border: '#ddd', /* ... */};// MyComponent.tsxfunction MyComponent({ fontSize }) { return ( <p css={{ color: colors.primary, fontSize, border: `1px solid ${colors.border}`, }} > ... </p> );}
如本示例所示,能够在CSS-in-JS款式中同时应用 JS 常量(例如 colors
)和 React Props/state
(例如 fontSize
)。
在款式中应用 JS 常量的能力在某些状况下能够升高反复,因为同一个常量不须要同时定义为CSS变量和 JS 常量。
应用 props
和 state
的能力能够创立具备高度可定制的款式的组件,而无需应用内联款式。(当雷同的款式利用于许多元素时,内联款式的性能并不现实)。
中立
这是一项热门的新技术。许多Web开发者,包含我本人,个别会社区中最热门的新趋势。局部起因是这样的,因为在很多状况下,新的库和框架曾经被证实比它们的前辈有微小的改良(想想React比晚期的库如jQuery进步了多少生产力就晓得了)。
另一方面,咱们对新工具的痴迷是胆怯错过下一个大事件,在决定采纳一个新的库或框架时,咱们可能疏忽了真正的毛病。我认为这必定是CSS-in-JS被宽泛采纳的一个因素--至多对我来说是这样。
不好
1.CSS-in-JS减少了运行时的开销。当组件渲染时,CSS-in-JS库必须将款式 "序列化"为能够插入到文档中的一般CSS。很显著,这须要占用额定的CPU周期,但这是否足以对应用程序的性能产生显著的影响?咱们在下一节中深入研究这个问题。
2 CSS-in-JS减少的包的大小。这是一个显著的问题--每个拜访你网站的用户都必须下载CSS-in-JS库的JavaScript。Emotion 的最小压缩量是7.9 kB
,styled-components 是12.7 kB
。
3.CSS-in-JS会打乱React DevTools。对于每个应用css prop 的元素,Emotion会渲染<EmotionCssPropInternal>
和<Insertion>
组件。如果你在许多元素上应用css prop,Emotion 的外部组件会使React DevTools变得十分凌乱,如图所示。
丑
1.频繁插入CSS规定迫使浏览器做很多额定的工作。React外围团队成员、React Hooks的最后设计师Sebastian Markbåge在React 18工作组中写了一篇十分有见地的探讨,内容是对于CSS-in-JS库须要如何扭转能力与React 18一起工作,以及总体上对于运行时CSS-in-JS的将来。特地是,他说:
在并发渲染中,React能够在渲染之间向浏览器退让。如果在一个组件中插入一个新的规定,如果React 退让了,那么浏览器就必须看看这些规定是否实用于现有的树。所以它会从新计算款式规定。而后React渲染下一个组件,而后该组件发现了一个新的规定,再次发生。
援用
这无效地导致在React渲染时,每一帧都要针对所有DOM节点从新计算所有CSS规定。这是很慢的。
这个问题最蹩脚的中央在于,它不是一个可修复的问题(在运行时CSS-in-JS的上下文中)。运行时CSS-in-JS库通过在组件渲染时插入新的款式规定来工作,这在根本层面上不利于性能。
2.对于CSS-in-JS,可能出错的中央还有很多,尤其是在应用SSR或组件库的时候。在Emotion的GitHub仓库里,咱们收到了大量这样的问题。
我正在应用Emotion与服务器端渲染和MUI/Mantine/(另一个Emotion驱动的组件库),它不能工作,因为...
尽管每个问题的根本原因各不相同,但有一些独特的起因:
- Emotion的多个实例被同时加载。即便多个实例都是同一版本的Emotion,这也会导致问题。(issue)
- 组件库通常不能齐全管制插入款式的程序。(issue)
- Emotion的SSR反对在React 17和React 18之间的工作形式不同。为了与React 18的流式SSR兼容,这是必要的。(issue)
这些复杂性只是冰山一角。
性能
运行时 CSS-in-JS既有显著的长处也有显著的毛病。为了了解咱们的团队为什么要放弃这项技术,咱们须要摸索CSS-in-JS的理论性能影响。
本节重点介绍Emotion 对性能的影响,因为它被用于 Spot 代码库。因而,如果认为下给出的性能数据也实用于你的代码库,那就错了--有很多办法能够应用Emotion,而且每一种办法都有本人的性能特点。
渲染内的序列化与渲染外的序列化
款式序列化是指Emotion将CSS字符串或对象款式转换为能够插入文档的一般CSS字符串的过程。在序列化过程中,Emotion也会计算出一个一般CSS的哈希值--这个哈希值就是你在生成的类名中看到的,例如css-15nl2r3
。
尽管我没有测量过这一点,但我置信影响Emotion如何执行的最重要因素之一是款式序列化是在React渲染循环外部还是内部执行的。
Emotion文档中的例子是在render
外面进行序列化的,像这样。
function MyComponent() { return ( <div css={{ backgroundColor: 'blue', width: 100, height: 100, }} /> );}
每次MyComponent
渲染的时候,对象的款式都会被再次序列化。如果MyComponent
频繁地渲染(例如每次按键),反复的序列化可能会有很高的性能代价。
一个更无效的办法是把款式移到组件之外,这样序列化就会在模块加载时一次性产生,而不是在每次渲染时。这能够通过@emotion/react的css
函数来实现:
const myCss = css({ backgroundColor: 'blue', width: 100, height: 100,});function MyComponent() { return <div css={myCss} />;}
当然,这种形式就无奈在款式中拜访 props,所以错过了CSS-in-JS的次要卖点之一。
在Spot,咱们在render
中进行了款式序列化,所以上面的性能剖析将集中于这种状况。
对Member Browser 进行基准测试
当初通过对Spot的一个真正的组件进行剖析来使事件具体化。咱们将应用 Member Browser,这是一个相当简略的列表视图,能够显示你的团队中的所有用户。
为了测试:
- Member Browser 显示20个用户
React.memo
四周的列表我的项目将被删除,并且强制最下面的<BrowseMembers>
组件每秒钟渲染一次,并记录前10次渲染的工夫。- React严格模式是敞开的。(它能够效地让咱们在分析器中看到的渲染工夫翻倍)。
我应用React DevTools对该页面进行了剖析,前10
次渲染工夫的平均值为54.3ms
。
我集体的教训是,一个React组件的渲染工夫应该在16
毫秒以内,因为每秒60帧的1帧是16.67
毫秒。Member Browser 目前是这个数字的3倍多,所以它是一个相当重量级的组件。
这个测试是在M1 Max CPU上进行的,它比普通用户的速度要快很多。我失去的54.3
毫秒的渲染工夫在性能较差的机器上可能很容易达到200
毫秒。
应用火焰图(FlameGraph)分析程序性能
上面是上述测试中单个列表项的火焰图:
正如你所看到的,有大量的<Box>
和<Flex>
组件被渲染--这些是咱们的 "tyle primitives",应用css prop。尽管每个<Box>
只须要0.1-0.2毫秒的渲染工夫,但因为<Box>
组件的总数十分大,所以这就减少了。
不应用 Emotion,对 Member Browser 进行测试
为了理解这种低廉的渲染有多少是由Emotion
造成的,我应用Sass Modules而不是Emotion重写了Member Browser 的款式。(Sass模块在构建时被编译成一般的CSS,所以应用它们简直没有性能损失)。
我反复了上述同样的测试,前10次渲染的均匀工夫为27.7ms
。这比原来的工夫缩小了48%!
所以,这就是咱们与CSS-in-JS 说拜拜的起因:运行时的性能老本切实是太高了。
反复我下面的免责申明:这个后果只间接实用于Spot代码库和咱们应用Emotion的形式。如果你的代码库以一种更无效的形式应用Emotion(例如在render之外的款式序列化),你可能会看到从方程中移除CSS-in-JS后的更小益处。
上面是一些数据,供那些好奇的人参考:
咱们新的款式零碎
在咱们下定决心不再应用CSS-in-JS之后,一个新的问题就会呈现:咱们应该用什么来代替?现实状况下,咱们心愿款式零碎的性能与一般CSS相似,同时尽可能多地保留CSS-in-JS的长处:
- 部分作用域
- 款式与它们所利用的组件放在同个中央
- 能够在款式中应用 JS 变量
如果你认真看了那一节,你会记得我说过,CSS Module 还提供了部分作用域的款式和同位。而且,CSS Module 能够编译成一般的CSS文件,所以应用它们没有运行时的性能老本。
在我看来,CSS模块的次要毛病是,说到底,它们依然是一般的CSS--而一般的CSS不足改善DX和缩小代码反复的性能。尽管嵌套选择器行将呈现在CSS中,但它们还没有呈现,而这个性能对咱们来说是一个微小开发品质的晋升。
侥幸的是,这个问题有一个简略的解决方案--Sass模块,它只是用Sass编写的CSS模块。你能够失去CSS模块的部分范畴的款式和Sass弱小的构建工夫性能,而且基本上没有运行工夫老本。这就是为什么Sass模块将成为咱们将来的通用款式解决方案。
实用类
对于从Emotion切换到Sass Modules,团队的一个放心是,利用极其常见的款式,如display: flex
,会不太不便。以前,咱们会写。
<FlexH alignItems="center">...</FlexH>
为了只应用Sass模块做到这一点,咱们必须关上.module。SCSS文件并创立一个利用款式display: flex和align-items: center的类。尽管不是世界末日,但的确不那么不便了。
如果只应用Sass模块,咱们不得在新建.module.scss
文件,并创立一个类,利用款式display: flex
和 align-items: center
。这并不是劫难,但必定不那么不便。
为了改良DX,咱们决定引入一个实用类零碎。实用类就是是在元素上设置一个繁多的CSS属性的CSS类。通常状况下,联合多个实用类来取得所需的款式。对于下面的例子,能够这样写。
<div className="d-flex align-items-center">...</div>
Bootstrap和Tailwind是提供实用程序类的最风行的CSS框架。这些库在其实用程序零碎中投入了大量的设计工作,所以采纳其中一个而不是推出咱们本人的实用程序是最有意义的。我曾经应用Bootstrap多年了,所以咱们抉择了Bootstrap。尽管你能够把Bootstrap的实用类作为一个预建的CSS文件,但咱们须要定制这些类来适应咱们现有的款式零碎,所以我把Bootstrap源代码的相干局部复制到咱们的我的项目中。
咱们应用Sass模块和实用类的新组件曾经有几个星期了,对它相当称心。DX与Emotion类似,而运行时的性能则大大优于Emotion。
对于编译时CSS-in-JS的阐明
本文次要介绍运行时的CSS-in-JS库,如 Emotion 和s tyled-components。最近,咱们看到越来越多的CSS-in-JS库在编译时将款式转换为一般CSS。这些库包含:
- Compiled
- Vanilla Extract
- Linaria
这些库旨在提供相似于运行时CSS-in-JS的益处,而没有性能老本。
尽管我本人没有应用过任何编译时的CSS-in-JS库,但我依然认为它们与Sass模块相比有毛病。以下是我在察看Compiled
时看到的毛病:
- 款式依然是在组件第一次挂载时插入的,这迫使浏览器在每个DOM节点上从新计算款式。(这个毛病曾经在 "丑"一节中探讨过了)。
- 像本例中的 color prop 这样的动静款式不能在构建时提取,所以Compiled应用 style prop(又称内联款式)将该值增加为CSS变量。家喻户晓,当利用许多元素时,内联款式会导致次优的性能
- 该库依然将模板组件插入你的React树中,如图所示。这将使React DevTools变得凌乱,就像运行时的CSS-in-JS一样。
总结
任何技术一样,它有其长处和毛病。归根结底,作为一个开发者,你应该评估这些长处和毛病,而后就该技术是否适宜你的应用状况做出一个理智的决定。对于咱们Spot公司来说,Emotion的运行时性能老本远远超过了DX的益处,特地是当你思考到Sass模块+实用类的代替计划依然有一个很好的DX,同时提供微小的性能。
代码部署后可能存在的BUG没法实时晓得,预先为了解决这些BUG,花了大量的工夫进行log 调试,这边顺便给大家举荐一个好用的BUG监控工具 Fundebug。
交换
有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。