广告:Codex Token 低价中转站稳定接口 · 快速接入 · 开发者备用通道
Engineering article

建议收藏:CSS-in-JS 源码解析 | 建议收藏

我见过真实项目中用CSS-in-JS方案把样式冲突问题干掉,不靠全局污染,不靠命名规范,直接通过组件级样式控制所有样式,这种做法在React生态里越来越流行。你要是用styled-components,那就要在组件内部定义样式,省去写CSS文件的麻烦,同时还能动态传参。我踩过坑,用theme传色值时千万不能混用字符串和数字,不然样式渲染会

建议收藏:CSS-in-JS 源码解析 | 建议收藏
配图来源于网络和AI生成,仅供参考。
▌ 技术引导 我见过真实项目中用CSS-in-JS方案把样式冲突问题干掉,不靠全局污染,不靠命名规范,直接通过组件级样式控制所有样式,这种做法在React生态里越来越流行。你要是用styled-components,那就要在组件内部定义样式,省去写CSS文件的麻烦,同时还能动态传参。我踩过坑,用theme传色值时千万不能混用字符串和数字,不然样式渲染会出现问题。如果在服务端渲染,记得用serverStyleSheet来处理,否则样式会失效。在构建流程中,如果没配置好Babel插件,那写出来的JS样式根本不会被识别。还有个关键点是,你不能在组件外部用样式对象,除非你用到CSS Modules,否则样式会泄漏出去。不管用哪种CSS-in-JS方案,样式命名规则要统一,否则你就会被组件嵌套混乱搞到崩溃。 我用emotion的时候,发现它支持动态样式,比如在条件语句里加样式,这样UI会根据逻辑自动变化,你再也不用手动加class。如果用CSS-in-JS跟TypeScript配合,记得用@emotion/react的ts插件,否则类型推断会有问题。我还遇到过因为动态样式渲染顺序不对,导致样式覆盖的情况,解决办法是用key值确保渲染顺序。用styled-jsx的时候,要记得在每个组件里写style标签,否则样式永远不会被挂载。我见过有人为了效率用CSS-in-JS写性能优化方案,比如用memo组件搭配样式传参,避免不必要的渲染。组件卸载时要记得清理样式,否则内存泄漏。 CSS-in-JS的工具链如果没配置好,你就会被CSS文件的体积和加载顺序搞死。比如用styled-components,如果没用到`createGlobalStyle`,某些全局样式会不生效。你要是用CSS Modules,记得在构建工具里配置`postcss`,不然样式会被转成hash,导致样式名混乱。还有个问题是在CSS-in-JS里写媒体查询,解析器有时候会搞混语法,得用`media`属性包装。我用emotion的时候,发现样式传参如果传的是对象,可能会触发多次渲染,这时候可以用`key`来避免。在一些项目里,CSS-in-JS反而让样式更难调试,因为样式对象在JS里,得靠开发者工具的样式观察功能来找。 总之,如果你的项目需要高度可控的样式,CSS-in-JS是真香。但别以为它万能,如果你用Vue,那它就完全没用。如果团队对CSS不太熟悉,直接用JS写样式可能比写CSS文件还要费劲。我在实际项目里遇到过样式覆盖的问题,特别是用到了继承和层级嵌套,解决方法是用`shouldUpdate`函数控制样式是否重新生成。如果你用next.js配合emotion,记得在`next.config.js`里配置`emotion`插件,否则SSG不会正确渲染样式。还有个细节是你不能在样式对象里直接写逻辑,得用`props`传参数进来。 最后说个真实案例,我在一个大项目里尝试用CSS-in-JS做组件复用,结果样式冲突,因为不同组件用了相同的类名,进而导致UI异常。后来发现是因为没有用到CSS Modules或者没有在组件内部定义样式,解决方法是用`componentId`来标识每个组件的样式,这样就不会混在一起。如果你用的是styled-components,记得在组件里用`as`属性来控制渲染的元素,否则样式可能不会应用。CSS-in-JS的性能其实跟传统CSS差不多,但如果你用到了大量动态样式,可能会造成更多的JS执行,这个时候就需要用`key`来优化。 ▌ 技术参考 一 CSS-in-JS的核心思想是把样式写在JavaScript里,避免全局CSS污染。最常见的是styled-components,它通过`styled`函数创建样式组件,支持直接嵌套样式,还能利用props传参。比如`const Button = styled.button`,这样组件本身就会携带样式,渲染时自动处理。这种写法的好处是样式和组件绑定,逻辑和样式分离,调试起来也更清晰。不过要注意,不能在组件外部直接使用样式对象,否则会被忽略。如果你用的是emotion,记得在组件里通过`styled`创建组件,否则样式不会被挂载到DOM上。 二 使用CSS-in-JS时,样式需要通过`style`对象传入,比如用emotion的时候,组件样式应该写成`{ color: 'red', fontSize: '16px' }`。如果你用的是styled-components,可以直接写CSS代码,例如`button { padding: 10px; }`。但是在动态传参时,如果传的是字符串或者数字,可能会影响样式渲染,比如`color: props.theme.primary`这种写法,props类型要匹配,否则渲染会出问题。此外,样式对象在某些情况下会触发不必要的组件更新,这时候可以考虑用`key`来优化,避免重复渲染。 三 在服务端渲染中,如果用的是styled-components,要记得用`serverStyleSheet`来处理样式,否则在SSR时样式会丢失。具体操作是用`export const getServerSideProps = async (ctx) => { ... }`,在函数内部引入`serverStyleSheet`,然后在`useEffect`中用``。这个步骤容易漏掉,会导致页面完全没样式。如果用的是emotion,只需要在`next.config.js`中配置`emotion`插件即可,这样SSG和SSR都会自动处理样式。 四 如果你用的是CSS Modules,虽然它不是CSS-in-JS,但也可以结合JS来处理样式。使用方式是在组件中用`import styles from './style.module.css'`,然后样式对象通过`className={styles.button}`来应用。不过CSS Modules的样式名会被hash处理,导致样式名重复,这时候需要在构建工具里配置`postcss`,添加`postcss-modules`插件,这样样式名就不会变。如果你用的是styled-jsx,它默认会自动处理样式,但需要在组件中包裹``,否则样式不会生效。 五 在使用CSS-in-JS时,常见问题是样式无法正确应用。比如在React中使用`emotion`,如果没有正确配置`babel-plugin-emotion`,样式对象不会被转换成CSS。解决方法是在`babel.config.js`里添加插件配置,例如`plugins: ['emotion']`。如果样式对象里有媒体查询,比如`@media (min-width: 600px) { ... }`,解析器可能不会正确处理,这时候需要用`media`属性来包装,例如`media: '(min-width: 600px)', button { padding: 10px; }`。 六 性能方面,CSS-in-JS的样式生成比传统CSS慢,特别是在大型项目中。比如用`styled-components`,每个组件都会生成一个CSS类,如果组件频繁更新,就会导致大量CSS重新生成,影响性能。这时候可以用`key`来优化,比如``,让组件在相同key下复用样式。另一个问题是,如果样式对象中包含太多动态参数,比如`color: props.theme.primary`,性能可能会下降,这时候可以考虑用`styled-components`的`as`属性来避免重复生成样式。 七 适用场景方面,CSS-in-JS最适合需要高度样式隔离和动态样式的项目。比如在React中,如果你有很多组件样式需要根据props动态变化,那么CSS-in-JS会更方便。但如果你的项目是大型电商平台,用CSS-in-JS反而会增加构建复杂度,这时候传统CSS配合CSS Modules会更合适。另外,如果你的团队对CSS不太熟悉,直接用JS写样式反而会让你更痛苦,因为调试和维护都更麻烦。 八 替代方案方面,你可以用CSS Modules配合JS来管理样式,这样既能保持样式隔离,又能用JS写样式逻辑。比如在组件里导入`style.module.css`,然后通过`className`来应用。如果你用的是Vue,可以考虑用SCSS或者Less写样式,配合组件级作用域,这样也能实现类似效果。另外,如果你不想用CSS-in-JS,可以尝试用CSS-in-JS的替代方案,比如`jss`,它是一个底层库,支持多种CSS-in-JS方案,比如`emotion`和`styled-components`,但配置起来更复杂。 九 在使用`styled-components`时,要注意避免在组件内部写太多样式,否则会影响性能。比如你写了一个按钮组件,其中样式逻辑太多,可能会导致样式生成变慢。这时候可以用`emotion`,它对性能优化更好,尤其是在大型项目中。在构建工具里,比如Webpack,配置`emotion`的时候,记得用`emotion`插件来处理样式,否则样式不会被正确打包。如果样式对象里有太多嵌套,可能会影响解析速度,这时候可以考虑用`key`来控制样式生成。 十 如果你在项目中尝试用CSS-in-JS,但发现样式覆盖严重,那很可能是因为你没有正确使用`componentId`。比如在styled-jsx中,每个组件的样式都是独立的,但如果你在组件内部用到了相同类名,就会导致样式冲突。这时候需要用`componentId`来标识不同组件,确保样式隔离。此外,在React中使用CSS-in-JS时,不要混用多个库,否则会引发样式优先级的问题。比如同时用`styled-components`和`emotion`,样式可能会互相覆盖,这时候需要统一用一个库。 十一 在实际开发中,我遇到过样式传参时出现类型错误的问题。比如用emotion时,如果传递的是数字,但是样式对象里需要字符串,就会导致样式不生效。这时候可以用TypeScript来约束类型,确保传参类型正确。在构建工具里,比如Vite,配置`postcss`时,要确保它支持CSS-in-JS的插件,否则样式会被忽略。另外,如果你在样式对象里使用了变量,比如`color: theme.primary`,记得用`as`属性来转换,否则样式不会被正确应用。 十二 CSS-in-JS的一个致命问题是在样式对象中的继承逻辑容易出错。比如你写了`button { color: inherit; }`,但如果父组件没有传color值,这个样式就会失效。这时候可以考虑用`theme`传值,或者在样式对象里显式定义默认值。比如用`emotion`,你可以写`{ color: props.theme.primary || 'black' }`,这样可以避免样式丢失。还有个问题是,如果样式对象里写太多逻辑,可能会影响可读性,所以要保持简洁。 十三 如果你用的是CSS-in-JS,但发现样式在开发环境正常,生产环境却失效,那可能是因为构建工具没有正确处理样式。比如在Webpack中,如果没用`emotion`插件,或者没配置`postcss`,样式就会被忽略。这时候需要检查`webpack.config.js`,确保有正确的loader和插件。另外,如果用的是next.js,记得在`next.config.js`里配置`emotion`的SSR支持,否则样式会丢失。 十四 在使用CSS-in-JS时,媒体查询的处理需要特别小心。比如在emotion中,写媒体查询时要写成对象形式,比如`media: '(min-width: 600px)', button { padding: 10px; }`,否则解析器可能不会识别。如果用的是styled-components,媒体查询应该写在组件的样式中,比如`button { @media (min-width: 600px) { padding: 10px; } }`。这种写法虽然灵活,但可能会影响性能,特别是有大量媒体查询的时候。 十五 如果你在项目里用到了CSS-in-JS,但发现样式加载顺序不对,导致UI渲染错误,那可能是因为样式生成时间不对。比如用`emotion`的时候,样式可能在组件渲染之后才生成,这时候需要手动控制样式加载顺序,或者用`key`来确保组件在正确时机渲染。另外,如果你在组件卸载时没有清理样式,可能会导致内存泄漏,这时候需要在`useEffect`里用`clean()`方法来清理样式。