React v19 styleタグと@scopeでCSS-in-JSを置き換えることは出来ない。
React v19でstyleタグと@scopeを使って、いくつかのCSS-in-JSライブラリのようにclassNameを指定せずにスタイルのカプセル化が出来るのかを検証した。
React v19の<style>
と@scope
を使うことでCSS-in-JSを置き換えることが出来るかという議論を見かけたので、可能なのか検証した。
検証内容
これは議論にあるサンプルコードである。
<div>
<style>
@scope {
p {
color: red;
}
}
</style>
<p>Only I am red.</p>
</div>
このコードは、そもそもhref
とprecedence
を指定されていないため、<style>
のhoistingと重複排除がされず、React v19のstyleタグの恩恵を受けられない。
次のように書く必要がある。
<div>
<style href="sample" precedence="medium">
{`
@scope {
p {
color: red;
}
}
`}
</style>
<p>Only I am red.</p>
</div>
この記述によってスタイルのカプセル化が出来るかどうかを持って、CSS-in-JSの代替できるかの確認とする。
次の環境で検証した。
- “next”: “14.2.5”,
- “react”: “19.0.0-rc-14a4699f-20240725”,
- “react-dom”: “19.0.0-rc-14a4699f-20240725”
結果
次のようにレンダリングされる。React v19の<style>
と@scope
を利用することで、スタイルのカプセル化を行うことは出来ない。
つまり、既存のCSS-in-JSライブラリを置き換えることは出来ない。
<head>
<style data-precedence="medium" data-href="sample">
@scope {
p {
color: red;
}
}
</style>
</head>
<body>
<div>
<p>Only I am red.</p>
</div>
<p>I am red, too.</p>
</body>
React 19でサポートされる<style>
は、要素やセレクタに対して、いくつかのCSS-in-JSライブラリのようにユニークなクラス名を自動的に付与しない。そのため@scope
はそのままhoistingされ無意味なものになる。
余談
className
を利用すれば当然カプセル化は可能である。
<div>
<style href="sample" precedence="medium">
{`
.im-red {
color: red;
}
`}
</style>
<p className="im-red">Only I am red.</p>
</div>
議論にあったような利用は現状では出来ないが、<style>
自体はとても有益な機能だと個人的には思っている。
例えば、次のようなコンポーネントを追加のライブラリやトランスパイラなしで作成することが出来る。また、このコンポーネントをいくつ作成してもスタイルの重複を気にする必要はない。
const css = String.raw;
export default function Button({ children }) {
return (
<>
<style href="button" precedence="medium">
{css`
.button-base {
color: white;
background-color: coral;
padding: 8px 16px;
border-radius: 6px;
}
`}
</style>
<button type="button" className="button-base">
{children}
</button>
</>
);
}
ただ、これがそのまま普及するかどうかに関しては懐疑的になっている。多くの人はhrefやprecedenceの指定を煩わしいと感じるのではないかと思うのと、見た目だけは似ている<style jsx>
がNext.jsに内蔵されているにも関わらず滅多に見る機会がないからだ。
結局、ラップしたライブラリが利用されることになるのではないかと思う。 サービスのパフォーマンスが良くなれば、それだけで十分だが、もの悲しさを感じる。