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

React组件库CSS样式方案分析

时间:2023-03-28 12:51:30 HTML

.css-1vdv3ej{color:red;}图片来源:https://unsplash.com作者:iwyvi背景随着业务的发展,一些代码逻辑可能会同时在多个项目中使用,为了避免使用和每次更新复制粘贴代码,需要构建一个组件库。构建组件库需要考虑很多方面。本文主要讨论如何在React生态中选择适合组件库的CSS样式方案。目前开发运行在浏览器中的项目时,可选的样式方案根据书写方式主要分为三种:第一种是常规CSS(regularCSS),即原生CSS和各种预处理语言;二是在JS中编写样式的CSSinJS方案,比如styled-components;三是用HTML编写工具类,通过CSS框架生成相应样式的方案,如TailwindCSS。但是我们在构建组件库的时候,考虑的角度可能和普通的项目不一样。不仅要考虑开发体验,还要照顾用户的感受。因此,本文不从写作层面去分析CSS样式方案,而是从组件库开发的角度来探讨以下两个问题:CSS与JS的关系是什么,即组件如何使用样式,以及组件如何使用样式。CSS参与打包。组件库如何处理不同关联方式下的样式命名冲突。方案分析在React生态中,没有统一的样式管理方案,所以对于如何处理CSS有很多方案,不同的方案各有优缺点。在构建组件库的时候,我们一方面希望开发的时候写起来更简单,另一方面希望使用起来更方便。CSS是组件库必不可少的一部分,选择合适的样式方案会影响后续的开发和使用体验。CSS和JS是如何关联的首先,我们先从CSS和JS是如何关联开始。不同的CSS和JS关联方式有不同的样式引入方式,在按需加载、性能、SSR支持等方面也各有特点。本文将使用CSS构建组件库的方案分为以下三种:样式和逻辑分离。组件的CSS和JS在代码层面是分离的。JS中不引入样式文件,组件库打包时生成独立的逻辑和样式文件。对于组件库的用户,添加一个组件,需要分别引入组件代码和CSS文件。使用这种方式的组件库有AntDesign、Zent等。风格符合逻辑。将组件的JS和CSS捆绑在一起,最后只输出JS文件。使用时只需要引入组件,直接使用即可。该方案目前主要有两种实现形式:JS写CSS。例如,在JS解决方案中使用styled-components、Emotion和其他CSS。代表性的组件库有MUI、Mantine等,写代码的时候还是使用常规的CSS,在组件中导入样式文件,通过打包工具将CSS打包到JS中。例如,将webpack与style-loader一起使用。基于该方案的组件(库)包括react-mobile-picker、AngularMaterial等。样式和逻辑关联。组件的JS和CSS在代码层面分离,打包后生成独立的逻辑和样式文件,但组件中直接引用样式文件,打包结果中保留相应的import语句。这种方法的示例包括SemiDesign、ReactSpectrum、AntDesignMobile5.0。这些方案各有优缺点,本文将详细分析:风格与逻辑分离的CSS组织方案在组件库构建中最为常见,各种框架中的组件库大量采用这种方式形式。CSS写在单独的样式文件中,组件JS不直接导入CSS,但在使用组件库时需要分别引入组件和样式。使用该方案的组件库有一个比较显着的特点。他们的安装教程会让用户自己导入一个或多个CSS文件,一般来说这个CSS文件会包含整个组件库中所有组件的样式。该方案的优点是:适用性广,可以支持组件库用户的各种开发环境。组件库的技术栈不受限制,可以在基于多个框架的组件库上使用同一套样式。无需考虑对SSR(Server-SideRendering)的支持,对外提供CSS文件,因此SSR过程完全由组件库的使用者控制。可直接对外提供less、sass等源文件,方便外部覆盖变量,实现主题定制或换肤等功能。但是这种方案也存在一些问题:需要用户手动导入样式文件。如果直接导入完整的CSS文件,但在实际使用中并没有使用到组件库中的所有组件,就会将一些无用的样式打包到项目中。组件库支持CSS按需导入功能会比较复杂。它不仅需要组件库的开发者进行封装过程和产品的处理,还需要用户按照一定的规则导入样式文件。首先,组件库的开发者需要为样式文件定义一套目录组织规范,这样在打包过程中就可以支持以组件为单位对样式文件进行打包,然后用户可以手动导入样式文件根据需要相应的组件。对于有特定目录组织规范的组件库,目前有插件可以在编译阶段辅助生成引入样式的import语句,如babel-plugin-import、unplugin-vue-components等,如果有组件库中组件之间的引用关系,为了实现按需导入,打包组件的样式可能是多余的。结合样式和逻辑在这种方案中,CSS以字符串或对象的形式存储在JS中,通常打包后的代码会有加载样式的运行时。该方案具有以下优点:用户无需单独引入样式文件,只需要导入组件即可使用天然支持按需加载。每个组件只需要处理自己的样式。然而,这个解决方案也并不完美:它需要带上运行时,可能会增加代码大小并影响性能。与单独的CSS文件相比,该方案中的样式全部在JS中,可能无法充分利用浏览器缓存。SSR的支持需要具体实现方案提供的能力,后面会详细介绍。该方案主要有以下两种实现形式:CSSinJSCSSinJS是一种不同于常规CSS文件的样式方案,多用于React生态,解决了使用常规CSS时存在的一些痛点,如命名冲突和多余的样式。其他问题。现在JS框架中的CSS百花齐放,里面有运行时和零运行时(zero-runtime)两大类。Styled-component和emotion属于runtime的范畴。样式的编写、修改和挂载都在JS中进行,框架会提供相应的runtime来处理这些工作。linaria和vanilla-extract类别是零运行时。它们在编写上类似于运行时框架,但需要配置编译过程。编译后会输出标准的CSS。因为本节讨论的是样式和逻辑的结合,所以后面提到的JS中的CSS指的就是运行时框架。runtime的引入意味着更多的JS代码,所以JS中的CSS和常规CSS肯定存在性能差异。Real-worldCSSvs.CSS-in-JSperformancecomparison本文从用户体验的角度分析了styled-component和linaria的性能差异(从上面可以看出linaria是零运行时的CSSinJS框架,即可以代表常规CSS的性能),得出常规CSS在JS各方面都优于CSS的结论。除了性能问题,由于样式注入过程是由CSSinJS框架提供的运行时执行的,SSR过程也需要框架额外处理。幸运的是,几乎所有主流的CSSinJS框架都提供了SSR支持。目前大多数CSSinJS框架都需要用户在服务端渲染过程中增加额外的样式收集和插入过程,才能成功使用SSR。emotion等少数框架提供了不需要额外配置的SSR支持方案。在服务端渲染时,组件样式会以内联样式标签的形式放在组件DOM的前面。但是这个方案也存在一些问题:当同一个组件在页面中被多次使用时,渲染出来的HTML会包含多个重复的样式;另外,由于插入了额外的样式标签,会影响:nth-child()这类选择器。为什么在SSR中这个额外的过程是不可避免的?服务端的一次渲染可以认为是调用了ReactDOMServer的renderToString方法,但是这个方法并没有为内部组件提供感知渲染状态的能力。对于组件,如果不记录是否渲染过,只能采用类似emotion的零配置方案,组件每次渲染都会带上自己的样式;但是如果组件通过一个全局变量记录自己已经渲染过,没有渲染过就插入样式,已经渲染过就不插入样式。不难发现,组件在服务端无法区分多次渲染,因为用于记录状态的全局变量在服务端始终是相同的。对于组件标记是否被渲染的状态在每一轮渲染时都会刷新,每次渲染都需要创建一个变量来存储这个状态,上述行为就是前面提到的附加样式收集过程.但是由于增加了样式收集过程,大部分CSSinJS方案都支持提取关键样式(CriticalCSS),可以减少SSR时首屏请求的尺寸,这也是它的一个优势。将CSS打包成JS的解决方案,一般是通过打包工具和相应的插件,比如webpack+style-loader或者rollup+rollup-plugin-styles,直接将常规的CSS和JS打包在一起。通常这些插件引入的runtime都有DOM操作,在SSR阶段会报错或者什么都不做,等待CSR阶段真正注入样式,所以不能支持SSR。支持SSR也不是完全没有选择。webpack生态中的isomorphic-style-loader提供了SSR支持,在功能上基本等同于style-loader。但是其实现和效果类似于CSSinJS方案。在开发过程中,需要给组件包一个高阶组件来加载样式,还需要通过SSR阶段提供的方法来收集和注入样式。在React生态中,很少有组件库采用这种样式方案,只有少数单组件项目采用这种封装方案。一方面,原因在于该方案难以提供SSR支持。另一方面,既然已经写好了常规的CSS,不如直接导出文件,让用户自己处理。但是,在一些非React生态中,仍然有很多组件库是使用这种方案构建的,因为他们的主流开发工具提供了包括打包和开发在内的一整套解决方案,而React生态中百花齐放,没有统一的开发工具,所以没有统一的样式方案。风格逻辑关联方案的开发过程与风格逻辑分离方案类似。主要区别是输出结果中直接保留导入样式文件的import语句。如果用户的项目能够正确处理CSS文件,那么就可以搞定。只需导入组件即可使用。并且这个方案还可以支持按需加载,而不需要引入庞大而全面的CSS文件。但是这种方案也有一些缺点:对于组件库的开发者来说,如果使用预处理语言,打包编译过程会比较复杂,需要正确关联组件最终产品的import语句使用编译后的CSS文件。用户的开发配置有一定的要求,需要正确处理组件库中代码引入的CSS文件(比如在webpack下配置相应的loader)。如果需要支持SSR,还需要修改打包工具的配置,让组件库的文件也能参与构建,避免在节点上直接执行css文件造成渲染错误边。总结风格与逻辑分离风格与逻辑组合风格与逻辑关联开发打包过程中简中简输出文件JS文件与CSS文件JS文件JS文件与CSS文件使用方法仅导入JS与CSS仅导入JS仅导入JS按需加载需要额外支持支持支持性能影响无额外运行时,可能有影响无SSR支持需要额外支持(部分解决方案不支持)支持(可能需要用户调整配置)支持在JS中编写常规CSS/零运行时CSS常规CSS/CSSinJSregularCSS/zeroruntimeCSSinJSkey样式提取自处理支持自处理组件库样式命名冲突处理解决样式命名冲突也是构建组件库时需要考虑的问题,开发者总想用更简单的名称,您不希望命名冲突。目前,在React生态中,常见的创建样式命名空间的方式有以下几种:ETC。。这种方案的优点是不需要调整打包方案,缺点是规则都是靠人约定的,开发时要靠开发者的自觉。多人维护时可能比较麻烦;另外,为了实现约定的命名规则,可能还需要编写一些样板代码来生成符合规范的名称,比较耗费精力。CSSModulesCSSModules是命名空间问题的解决方案。它可以根据指定的规则生成选择器名称,无需开发者遵守严格的规范,同时避免污染全局样式。下面是一个简单的例子,原代码是这样的:.test{color:red;}importstylesfrom'index.less';//...转换后,它变成了这样:._xxxxxx{color:red;}varmodules_xxx={"test":"_xxxxxx"};//...添加哈希到selectorvalue等方法,让selector不和其他地方冲突。但是我们在使用CSSModules开发组件库的时候,还需要考虑这些问题:使用CSSModules需要对组件库的编译过程进行一定的处理。如果要采用风格和逻辑分离的封装方案,需要在封装后的代码中去掉对样式文件的引用语句,只保留通过选择器名称转换的数据;如果使用样式和逻辑关联的scheme,需要在保持selector名称转换的同时,正确引入编译后的样式文件。因为不能保证用户的开发环境也支持CSSModules,所以不能直接对外提供样式的源文件。由于生成的选择器的名字不稳定,可能会经常变化,对于组件库的使用者来说,无法在外层覆盖组件样式。CSSinJS是的,CSSinJS方案又出现了,因为这个方案在诞生之初就刻意解决了类命名的问题。由于选择器名称是动态生成的,因此在开发过程中无需遵循命名约定或考虑命名冲突。以Emotion为例:importstyledfrom'@emotion/styled';constTest=styled.div`color:red;`;//在浏览器上运行...时,真实的DOM渲染为:.css-1vdv3ej{color:red;}

作为我们在文章中提到,JS解决方案中的CSS分为两种类型:运行时和零运行时。其中,零运行时方案编译后最终会输出一个以随机选择器命名的CSS文件。这种效果类似于CSS模块。例如vanilla-extract称自己为“CSSModules-in-TypeScript”。因此,JS解决方案中的零运行时CSS可用于任何支持常规CSS的解决方案。考虑关联方案的类型选择选择样式方案,首先要确定CSS和JS的关联方式,然后考虑如何处理样式命名。如果你打算构建一个支持多种框架的组件库,首要的封装方案就是样式和逻辑的分离。在这种方案下,可以产生一个基本样式,可以在多框架组件库中共享,而不必在每个框架中单独处理,具有最广泛的适用性。但如果只需要支持React技术栈,可以根据使用情况进一步考虑以上方案:在当前时间节点构建一个组件库,本文的观点是:在兼容性需求下,CSSinJS可以接受的写法,并且可以容忍自身的一些缺点(比如性能问题或者一些写法限制),优先考虑CSSinJS的runtime解决方案。组件库的开发者无需在样式的命名、导入和打包上花费精力,在使用过程中可以轻松实现样式的按需加载,并且可以支持SSR和关键渲染路径样式提取。如果你不接受CSSinJS而仍然偏爱常规CSS,或者觉得CSSinJS方案太多难以取舍,也可以考虑以下常规CSS方案:如果组件库的应用场景显然不需要支持SSR,也不需要考虑单独获取CSS文件进行缓存等性能优化。可以选择将CSS直接打包成JS。该方案不需要调整封装流程,用户只需要导入组件即可使用,逻辑和样式都可以支持按需加载。如果不满足以上条件,可以优先选择样式关联逻辑的方案。如果组件库在内部项目中使用,项目需要支持对组件库中直接导入的样式文件进行打包。风格和逻辑分离是最常见和最安全的解决方案。需要注意的是,这种方案实现按需加载比较麻烦。通常,组件库会考虑提供一套生成按需加载样式语句的处理方法,比如引导用户使用babel-plugin-import,并给出相应的配置。命名方案选择选择写正规的CSS,还需要考虑样式的命名规则。如果是基础的UI组件库,更推荐使用命名约定的方案:UI组件库通常有相对固定的维护者,便于实现统一的命名约定;业务中使用基础组件时,可能会涉及到定制化的场景。该方案可以直接对外提供源码,方便在具体业务中覆盖变量或样式,给用户更大的自由度。如果是业务组件库,更推荐使用CSSModules/zero-runtimeCSSinJS解决方案:业务组件库本身与业务强相关,可能由不同业务的开发者维护。统一的命名规范很难实行,也不可能保证所有人都能严格遵守。使用CSSModules可以帮助开发者保证样式的命名空间不会被污染。业务组件较少涉及自定义场景,不需要满足覆盖变量或样式的要求。总结目前,React生态中还没有一种占主导地位的样式方案,现有的各种方案各有优缺点。因此,选择合适的风格方案需要多方面的综合考虑。本文从CSS与JS的关联和命名冲突处理方法两个方面对组件库的样式方案选择进行了对比分析。没有一种方案是完美的,但一定会有更适合不同业务场景的方案。希望本文能对大家在构建组件库时的组件选择提供一些帮助。参考ReactCSSvs.CSS-in-JS性能对比中如何优雅地编写CSSAThoroughAnalysisofCSS-in-JSReal-world本文由网易云音乐技术团队发布。未经授权禁止任何形式的转载。我们常年招聘各种技术岗位。如果你要跳槽,又恰好喜欢云音乐,那就加入我们吧grp.music-fe(at)corp.netease.com!