科技微讯

理解 styled-jsx 的 :global()

styled-jsx 的文档对 :global() 的表述容易让人迷惑,我提交了一个文档 PR,顺便在这里记录我对 :global() 的理解。

styled-jsx 是 next.js 官方维护的 css-in-js 方案,它会把这段代码:

export default () => (
  <div>
    <p>only this paragraph will get the style.</p>
    <style jsx>{`
      p {
        color: red;
      }
    `}</style>
  </div>
);

自动 transpile 为:

import _JSXStyle from "styled-jsx/style";
export default () => (
  <div className="jsx-123">
    <p className="jsx-123">only this paragraph will get the style.</p>
    <_JSXStyle id="123">{`p.jsx-123 {color: red;}`}</_JSXStyle>
  </div>
);

styled-jsx 默认只作用于当前的 component,对其他 component 无效。上面的例子中,如果在 <p> 后加入一个自定义组件,例如 <MyComponent>,假设这个 <MyComponent> 也有 <p>,那本组件的 p { color: red; } 并不会对 <MyComponent><p> 起作用。

为什么呢?因为 styled-jsx 会自动给本组件的每个 html 标签增加一个仅属于本组件的 className。

仔细看上面被 transpile 后的结果,所有 divp 的 class 属性都自动增加了 jsx-123,这个 className 在本组件内是相同的,但在不同组件间是唯一的。再看 <_JSXStyle> 中的 css 选择器,我们本来写的选择器仅仅是 p,但 styled-jsx 会把它改成 p.jsx-123,这就导致了 color: red 这段 css 命令只能作用于本组件。

这种在 selector 中自动添加 jsx-123 的做法,叫 selectors scoping。所以用 styled-jsx 写的样式,是自动 scope 在本组件的,无需担心它会对其他组件的样式产生干扰。

现在问题来了,有时候我们就希望在本组件中对 import 进来的组件进行样式设置啊。例如 Next.js 13 的 next/link 组件不支持增加 <a> 标签作为它的内部元素了,之前我们可以在 <a> 添加 className 然后添加样式,现在 <a> 没有了,那怎么继续使用 className 对 next/link 进行样式设置呢?

这时候可以用 :global():global() 经常用于取消前面提到的 selectors scoping。还是前面的例子,用了 :global() 后,p.jsx-123 就会变成 p,于是 {color: red;} 就会作用于所有 <p> 标签,注意,所有是指:包括父组件、子组件、相邻组件在内的所有 <p>

:global() 的最佳实践是仅取消 selectors scoping,而不是取消所有 scoping,即不希望它成为全局样式(全局样式有更好的写法,后文有叙),取消 selectors scoping 是希望它既可以作用于本组件,也可以作用于子组件,并不希望它影响父组件或者相邻的其他组件,所以通常我们会这样使用 :global()

import Link from 'next/link'
export default () => (
  <div>
    <Link href="/about">关于</p>
    <style jsx>{`
      div :global(a) {
        color: red;
      }
    `}</style>
  </div>
);

或:

import Link from 'next/link'
export default () => (
  <div>
    <Link href="/about" className="link">关于</p>
    <style jsx>{`
      div :global(.link) {
        color: red;
      }
    `}</style>
  </div>
);

:global() 前添加本组件的任意一个 css 选择器,这里添加了 div 标签选择器,div 选择器因为没有使用 :global(),所以它还是 selectors scoping 的,所以 styled-jsx 最后会把它理解为 div.jsx-123 adiv.jsx-123 .link,所以 a.link 只会作用于本组件及其子组件。

注意 div :global(.link) 会作用于本组件及所有子组件,即子组件的子组件的 .link,如果仅希望作用于本组件及仅下一级的子组件,可以这样:div > :global(.link)

虽然 :global() 可以让括号内的选择器变成全局组件,但这样全局和局部样式就混在一起了,不利于后续维护。如果想使用 styled-jsx 写全局样式,建议在 <style jsx> 添加 global,把所有全局样式单独写在一起:

export default () => (
  <div>
    <style jsx global>{`
      body {
        background: red;
      }
    `}</style>
  </div>
);
thumbsup0
thumbsdown0
暂无评论