当前位置: 首页 > Web前端 > JavaScript

为何告别CSS-in-JS

时间:2023-03-27 12:05:47 JavaScript

微信搜索【大举走向世界】,第一时间与大家分享前端行业动态、学习路径等。本文已收录到GitHubhttps://github.com/qq449245884/xiaozhi,里面有完整的测试站点、资料和我的一线厂商访谈系列文章。本文由Emotion第二大活跃维护者Sam分享,本文第一人称指Sam。Emotion是一个广受欢迎的ReactCSS-in-JS库。在这篇文章中,Sam将带您深入了解是什么让CSS-in-JS如此吸引人,以及为什么作者(以及Spot团队的其他成员)决定放弃它。什么是CSS-in-JS?CSS-in-JS顾名思义就是直接在JS或者TS中编写CSS,为React组件提供样式,如下所示://ObjectStylesmethodfunctionErrorMessage({children}){return({children}

);}//字符串样式方法constErrorMessage=styled.div`color:red;font-weight:bold;`;styled-components和Emotion是React社区中最流行的CSS-in-JS库。虽然我只使用了Emotion,但我相信本文的所有要点也适用于样式化组件。本文重点介绍运行时CSS-in-JS,这是一个包含样式化组件和Emotion的类别。运行时CSS-in-JS只是意味着库在应用程序运行时解释和应用您的样式。我们将在文章末尾简要讨论编译时CSS-in-JS。CSS-in-JS的优点、缺点和丑陋在讨论CSS-in-JS编码模式及其对性能的影响之前,让我们来看看为什么一些开发人员使用CSS-in-JS而其他人不使用。好处1.局部作用域的样式。在编写纯CSS时,很容易不小心将样式应用到其他文件。例如,假设我们正在编写一个列表,每一行都应该有一些padding和border。我们可能会这样写:.row{padding:0.5rem;边框:1px实心#ddd;几个月后,当我们完全忘记这个列表时,另一个列表被创建了。然后也设置了className="row"。现在,新组件的行有一个丑陋的边框,我们不知道为什么!虽然这类问题可以通过使用更长的类名或更具体的选择器来解决,但作为开发人员,确保没有冲突是很重要的。CSS-in-JS通过默认将样式设置为局部范围完全解决了这个问题。如果上面的样式是这样写的:...
那么padding和border就不能应用到其他元素上向上。2.托管。如果你使用纯CSS,你可以将所有.css文件放在src/styles目录中,将所有React组件放在src/components中。随着应用程序大小的增长,可能很难判断每个组件使用了哪些样式。很多时候,您最终会在CSS中使用死代码,因为没有简单的方法可以判断这些样式未被使用。一种更好的组织代码的方法是将与单个组件相关的所有内容都放在一个地方。这种做法称为托管(hosting)。问题是使用纯CSS时很难实现共置,因为CSS和JavaScript必须在单独的文件中,并且无论.css文件在哪里,您的样式都会全局应用。另一方面,如果你使用CSS-in-JS,你可以直接在使用它们的React组件中编写样式。如果做得好,这可以大大提高应用程序的可维护性。3.JavaScript变量可以用在样式中。CSS-in-JS可以引用样式规则中的JavaScript变量,例如://colors.tsexportconstcolors={primary:'#0d6efd',border:'#ddd',/*...*/};//MyComponent.tsxfunctionMyComponent({fontSize}){return(...

);}如这个例子所示,可以在CSS-in-JS样式中同时使用JS常量(例如colors)和ReactProps/state(例如fontSize)。在样式中使用JS常量的能力在某些情况下可以减少重复,因为同一个常量不需要同时定义为CSS变量和JS常量。使用props和state的能力可以创建具有高度可定制样式的组件,而无需使用内联样式。(当相同的样式应用于许多元素时,内联样式表现不佳)。中性这是一项热门的新技术。许多Web开发人员,包括我自己,通常都会关注社区中最热门的新趋势。部分原因在于,因为在许多情况下,较新的库和框架已被证明比它们的前辈有了巨大的改进(想想React比jQuery等早期库提高了多少生产力)。另一方面,我们对新工具的痴迷是害怕错过下一件大事,在决定采用新的库或框架时,我们可能会忽视真正的缺点。我认为这绝对是CSS-in-JS被广泛采用的一个因素——至少对我而言。不好1.CSS-in-JS增加了运行时开销。当组件呈现时,CSS-in-JS库必须将样式“序列化”为可以插入到文档中的纯CSS。显然这需要额外的CPU周期,但是否足以对应用程序的性能产生显着影响?我们将在下一节深入探讨这个问题。2CSS-in-JS增加了包的大小。这是一个明显的问题——每个访问您站点的用户都必须下载CSS-in-JS库的JavaScript。Emotion的最小压缩为7.9kB,styled-components的最小压缩为12.7kB。3.CSS-in-JS会搞乱ReactDevTools。对于每个使用cssprop的元素,Emotion渲染组件。如果你在许多元素上使用css属性,Emotion的内部组件会使ReactDevTools非常混乱,如图所示。丑1.频繁插入CSS规则迫使浏览器做很多额外的工作。React核心团队成员和ReactHooks的原始设计师SebastianMarkb?ge在React18工作组中写了一篇非常有见地的讨论,讨论CSS-in-JS库需要如何改变才能与React18一起工作,以及CSS的未来-运行时在JS中。他特别指出:在并发渲染中,React可以让位于渲染之间的浏览器。如果在组件中插入新规则,如果React退缩,浏览器必须查看这些规则是否适用于现有树。所以它会重新计算样式规则。然后React渲染下一个组件,然后发现一个新规则,然后它再次发生。这有效地导致在React渲染时为所有DOM节点重新计算所有CSS规则。这很慢。这个问题最糟糕的部分是它不是一个可修复的问题(在运行时CSS-in-JS的上下文中)。运行时CSS-in-JS库通过在组件呈现时插入新的样式规则来工作,这从根本上不利于性能。2.对于CSS-in-JS,还是有很多地方会出错,尤其是在使用SSR或者组件库的时候。在Emotion的GitHub存储库中,我们收到了很多这样的问题。我将Emotion与服务器端渲染和MUI/Mantine/(另一个Emotion-driven组件库)一起使用,但它不起作用,因为...虽然每个问题的根本原因各不相同,但有一些常见原因:EmotionMultiple的实例同时加载。即使多个实例属于同一版本的Emotion,这也会导致问题。(问题)组件库通常不能完全控制插入样式的顺序。(问题)Emotion的SSR支持在React17和React18之间的工作方式不同。这是与React18的流式SSR兼容所必需的。(问题)这些复杂性只是冰山一角。性能运行时CSS-in-JS具有明显的优点和缺点。为了理解为什么我们的团队放弃了这项技术,我们需要探索CSS-in-JS的实际性能影响。本节重点介绍Spot代码库中使用的Emotion对性能的影响。所以,如果您认为下面给出的性能数据也适用于您的代码库,那您就错了——使用Emotion的方法有很多种,每种方法都有自己的性能特征。渲染内序列化vs.渲染外序列化样式序列化是指Emotion将CSS字符串或对象样式转换为可插入文档的普通CSS字符串的过程。在序列化过程中,Emotion还会计算纯CSS的哈希值——这个哈希值就是您在生成的类名中看到的,例如CSS-15nl2r3。虽然我没有对此进行测量,但我认为影响Emotion执行方式的最重要因素之一是样式序列化是在React渲染循环内部还是外部执行。Emotion文档中的示例在渲染中被序列化,就像这样。functionMyComponent(){return();}每次MyComponent渲染时,对象的样式都会被再次序列化。如果MyComponent被频繁渲染(例如每次按键),重复的序列化可能会产生很高的性能成本。一种更有效的方法是将样式移动到组件外部,以便序列化在模块加载时同时发生,而不是在每次渲染时发生。这可以通过@emotion/react的css函数来实现:constmyCss=css({backgroundColor:'blue',width:100,height:100,});functionMyComponent(){return;}当然,这样你无法访问样式中的props,因此你错过了CSS-in-JS的主要卖点之一。在Spot中,我们在render中进行样式序列化,所以下面的性能分析将围绕这种情况进行。BenchmarkingMemberBrowser现在通过分析Spot的真实组件使事情具体化。我们将使用成员浏览器,这是一个相当简单的列表视图,显示您团队中的所有用户。用于测试:MemberBrowser显示20个用户React.memo周围的列表项被移除,最顶层的组件被强制每秒渲染一次,并记录前10次渲染的时间。React严格模式已关闭。(它有效地使我们在分析器中看到的渲染时间加倍)。我使用ReactDevTools对页面进行了概要分析,前10次渲染时间的平均值为54.3毫秒。我的个人经验是React组件应该在16毫秒内呈现,因为每秒60帧的1帧是16.67毫秒。MemberBrowser目前是这个数量的3倍多,因此它是一个相当重量级的组件。本次测试是在M1MaxCPU上进行的,比一般用户快很多。我得到的54.3毫秒渲染时间在功能较弱的机器上很容易变成200毫秒。使用FlameGraph分析程序性能下面是来自上述测试的单个列表项的火焰图:如您所见,有很多组件正在渲染-这些是我们的“样式基元”,使用css道具。虽然每个只需要0.1-0.2毫秒来渲染,但由于组件的总数非常大,所以这会加起来。在没有情感的情况下测试会员浏览器为了了解这种昂贵的渲染有多少是由情感引起的,我重写了会员浏览器的样式以使用Sass模块而不是情感。(Sass模块在构建时被编译为纯CSS,因此使用它们几乎没有性能损失)。我重复了上面的相同测试,前10次渲染的平均时间为27.7毫秒。这比原来的时间减少了48%!所以,这就是我们告别CSS-in-JS的原因:运行时的性能成本太高了。重复我上面的免责声明:这个结果只直接适用于Spot代码库和我们使用Emotion的方式。如果您的代码库以更有效的方式使用Emotion(例如渲染之外的样式序列化),您可能会发现从等式中删除CSS-in-JS带来的好处较小。以下是为那些好奇的人提供的一些统计数据:我们的新样式系统在我们决定不使用CSS-in-JS之后,出现了一个新问题:我们应该使用什么来代替?理想情况下,我们希望样式系统的性能类似于纯CSS,同时尽可能多地保留CSS-in-JS的优点:局部范围内的样式与其应用的组件可以使用的位置相同样式变量中的JS如果你仔细阅读该部分,你会记得我说过CSS模块还提供局部范围的样式和奇偶校验。此外,CSS模块可以编译成普通的CSS文件,因此使用它们不会产生运行时性能成本。在我看来,CSS模块的主要缺点是,归根结底,它们仍然是纯CSS——而纯CSS缺乏改进DX和减少代码重复的功能。虽然嵌套选择器即将出现在CSS中,但它们还没有出现,这个特性对我们来说是一个巨大的开发质量提升。幸运的是,这个问题有一个简单的解决方案——Sass模块,它们只是用Sass编写的CSS模块。您可以获得CSS模块的局部范围样式和Sass强大的构建时功能,而基本上没有运行时成本。这就是为什么Sass模块将成为我们未来的通用样式解决方案。实用类团队对从Emotion切换到Sass模块的担忧之一是应用非常常见的样式(如display:flex)会很不方便。以前,我们会写。...为了仅对Sass模块执行此操作,我们必须打开.module。SCSS文件并创建一个应用样式display:flex和align-items:center的类。虽然不是世界末日,但肯定不那么方便。如果我们只使用Sass模块,我们必须创建一个新的.module.scss文件并创建一个应用样式display:flex和align-items:center的类。这不是灾难,但肯定不那么方便。为了改进DX,我们决定引入实用类系统。实用程序类是在元素上设置单个CSS属性的CSS类。通常,组合多个实用程序类以获得所需的样式。对于上面的例子,可以这样写。...
Bootstrap和Tailwind是提供实用程序类的最流行的CSS框架。这些图书馆在他们的实用系统中投入了大量的设计工作,所以采用其中一个而不是推出我们自己的系统是最有意义的。我已经使用Bootstrap多年,所以我们选择了Bootstrap。虽然您可以获得Bootstrap的实用程序类作为预构建的CSS文件,但我们需要自定义这些类以适应我们现有的样式系统,因此我将Bootstrap源代码的相关部分复制到我们的项目中。几周以来,我们一直在使用Sass模块和实用程序类的新组件,并且对它非常满意。DX类似于Emotion,而运行时性能比Emotion好很多。编译时CSS-in-JS注意事项本文主要介绍运行时的CSS-in-JS库,如Emotion、styled-components。最近,我们看到越来越多的CSS-in-JS库在编译时将样式转换为纯CSS。这些库包括:CompiledVanillaExtractLinaria这些库旨在提供运行时类似CSS-in-JS的优势,而无需性能成本。虽然我自己没有使用过任何编译时CSS-in-JS库,但我仍然认为与Sass模块相比它们有缺点。这是我在查看Compiled时看到的缺点:首次安装组件时仍会插入样式,这会迫使浏览器在每个DOM节点上重新计算样式。(这个缺点已经在“丑陋”部分讨论过了)。像本例中的color属性这样的动态样式无法在构建时获取,因此Compiled使用样式属性(也称为内联样式)将该值添加为CSS变量。众所周知,在应用许多元素时,内联样式会导致性能不佳。库仍然将模板组件插入到您的React树中,如图所示。这会使运行时的CSS-in-JS等ReactDevTools变得混乱。总之,与任何技术一样,它有其优点和缺点。归根结底,作为开发人员,您应该评估这些优缺点,然后就该技术是否适合您的用例做出明智的决定。对于Spot的我们来说,Emotion的运行时性能成本远远超过DX的好处,尤其是当你考虑到Sass模块+实用程序类替代方案在提供巨大性能的同时仍然具有不错的DX。代码部署后可能存在的bug,无法实时获知。事后为了解决这些bug,花费了大量的时间在日志调试上。顺便推荐一个好用的bug监控工具Fundebug。来源:https://dev.to/srmagura/why-w...交流有梦想,有干货,微信搜索【大千世界】关注这位大清早还在洗碗的洗碗智慧。本文已收录到GitHubhttps://github.com/qq449245884/xiaozhi,里面有完整的测试站点、资料和我的一线厂商访谈系列文章。