React v19 styleタグと@scopeでCSS-in-JSを置き換えることは出来ない。

Published at
695 words
3min read

React v19 の<style>@scopeを使うことでCSS-in-JS を置き換えることが出来るかという議論を見かけたので、可能なのか検証した。

検証内容#

これは議論にあるサンプルコードである。

<div>
<style>
@scope {
p {
color: red;
}
}
</style>
<p>Only I am red.</p>
</div>

このコードは、そもそもhrefprecedenceを指定されていないため、<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 に内蔵されているにも関わらず滅多に見る機会がないからだ。

結局、ラップしたライブラリが利用されることになるのではないかと思う。 サービスのパフォーマンスが良くなれば、それだけで十分だが、もの悲しさを感じる。