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 后的结果,所有 div
、p
的 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 a
或 div.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>
);