The New Old Thing

CSS-in-JS 样式冲突问题处理

· Cheng

使用 @emotion/css 实现的组件库在接入到使用多个技术栈实现的项目中,面临着样式被反复覆盖的问题。

Styles Overide by Hosts' Styles

从上图可以看到,组件上样式被宿主项目引入的 css 文件中定义的样式覆盖,究其原因是因为 css-in-js 生成的 css-[hash] 的选择器权重比较低。

根据 Selectors Level 3Calculating a selector's specificity 的章节可以得出一下结论。

/* count the number of ID selectors in the selector (= a) */
/* count the number of class selectors, attributes selectors, and pseudo-classes in the selector (= b) */
/* count the number of type selectors and pseudo-elements in the selector (= c) */
/* ignore the universal selector */

Examples:

*               /* a=0 b=0 c=0 -> specificity =   0 */
LI              /* a=0 b=0 c=1 -> specificity =   1 */
UL LI           /* a=0 b=0 c=2 -> specificity =   2 */
UL OL+LI        /* a=0 b=0 c=3 -> specificity =   3 */
H1 + *[REL=up]  /* a=0 b=1 c=1 -> specificity =  11 */
UL OL LI.red    /* a=0 b=1 c=3 -> specificity =  13 */
LI.red.level    /* a=0 b=2 c=1 -> specificity =  21 */
#x34y           /* a=1 b=0 c=0 -> specificity = 100 */
#s12:not(FOO)   /* a=1 b=0 c=1 -> specificity = 101 */



div[data-tag="some-tag-name"] input                      /* a=0 b=1 c=2 -> specificity =   12 */
input[type="text"]                                       /* a=0 b=1 c=1 -> specificity =   11 */
.css-1t321tv                                             /* a=0 b=1 c=0 -> specificity =   10 */

要解决这个问题,第一时间想到的可能是给每一个冲突的样式添加 !important ,但是这样做的工作量太大了,而且很容易留下后患。那么如何优雅的解决这个问题呢?紧接着 Calculating a selector's specificity 下面有一段话

Note: Repeated occurrences of the same simple selector are allowed and do increase specificity.

这也就是说,如果能让 @emotion/css 在为元素生成 class 的同时能为生成的样式选择器添加一个重复的 class 就可以解决这个问题了。@emotion/css 是支持如下的语法的:

<div
  css={{
    '&': {
      width: '100%',
    }
  }}
/

那么如果使用&&@emotion/css 会为我们生成重复的类名选择器吗?

<div
  css={{
    '&&': {
      width: '100%',
    }
  }}
/>

答案是会的。

Double Classes

从上图看出,@emition/css 为元素生成了两个相同了 class 。这样生成的样式选择器的权重就是 a = 0 b = 2 c = 0 -> specificity = 20 足够覆盖大多数场景了。