React で単にスタイルにスコープをつけるだけなら各種ライブラリに頼らなくても良いのかもしれない

## この記事の概要

つい先日こちらの記事を投稿しました。

https://qiita.com/xrxoxcxox/items/ca257cb5bcc9379318ef

React 19 で`style`の巻き上げができるようになる前提であれば、もしかして……という程度の実験です。

私はプロジェクトの依存関係が増えるのは、あまり好きではありません。
必要なものは使えば良いと思いますが、不用意に増やしたくないので、たまに「今当たり前に使っているライブラリ、実は剥がせないかな」と考えています。

この記事も似たようなモチベーションで調べた内容です。

実戦投入できるほどの検証はしていませんが、こういう記事をきっかけに「ってことはコレならできるんじゃない？」なんて話が出たら良いなあと思って投稿します。

## 前回記事のコンポーネントをベースに土台を用意する

まずは前回の記事の内容をベースに、コンポーネントを用意しました。

```diff_jsx
  export function TestComponent({ color, size }) {
    return (
      <>
        <style href={`test-component-${color}`} precedence="medium">{`
          .test-component-${color} {
            color: ${color};
          }
        `}</style>
        <style href={`test-component-${size}`} precedence="medium">{`
          .test-component-${size} {
            font-size: ${size}px;
          }
        `}</style>
        <p className={`test-component-${color} test-component-${size}`}>This is a test component</p>
      </>
    );
  };
```

今のままだと、生成されるクラスはこのようなものです。

- test-component-red
- test-component-blue
- test-component-16
- test-component-24

コンポーネント名をつけているからある程度は大丈夫な気がしつつも、どこで被りがおきるか分かりません。

ここからスコープをつけていきます。

## `crypto.randomUUID()`を使ってユニークな id を生成する

React には`useId`という hook があり、ユニークな id を生成することができます。

https://react.dev/reference/react/useId

しかし`:`の文字からはじまるので className 属性や id 属性には使いづらい[^not-good-as-css-selector]のと、同じコンポーネントでも生成される度に違う id を付与するという性質がスタイリングにおいてはイマイチです。

[^not-good-as-css-selector]: この記事で書いたような処理を通せば使えますが、そこまでするほどか？という疑問が出てきます。 https://qiita.com/xrxoxcxox/items/54bb72a350866fa2d2c7

コンポーネントの外側でランダムな id を指定できて、かつ依存関係を増やさなくていいやり方はないものか……と探していたところ Web Crypto API に出会いました。

用途が違うように思いますが、ひとまず実験なので良しとします。

---

まずコンポーネントの外側でランダムな id を生成します。
コンポーネントの内側で生成するとコンポーネントの数だけ`style`要素が生まれてしまい、今回の恩恵に預かれません。

```diff_jsx
+ const uuid = crypto.randomUUID();

  export function TestComponent({ color, size }) {
    return (
      <>
        <style href={`test-component-${color}`} precedence="medium">{`
          .test-component-${color} {
            color: ${color};
          }
        `}</style>
        <style href={`test-component-${size}`} precedence="medium">{`
          .test-component-${size} {
            font-size: ${size}px;
          }
        `}</style>
        <p className={`test-component-${color} test-component-${size}`}>This is a test component</p>
      </>
    );
  };
```

## `data-*`属性を使ってスコープを作る

`crypto.randomUUID()`で生成された id が数字始まりなことは多いです。
すると className 属性や id 属性に割り当てても認識されません。[^css-selector-escaping]

[^css-selector-escaping]: これもまたエスケープすれば認識されるのですが、それはそれで大変そうだな……と感じて違う方法を探すに至りました。 https://www.w3.org/TR/css-syntax-3/#escaping

しかし、`data-*`属性ならそういった縛りはありません。
というわけで新たに作成し、割り当てます。

```diff_jsx
  const uuid = crypto.randomUUID();

  export function TestComponent({ color, size }) {
    return (
      <>
        <style href={`test-component-${color}`} precedence="medium">{`
          .test-component-${color} {
            color: ${color};
          }
        `}</style>
        <style href={`test-component-${size}`} precedence="medium">{`
          .test-component-${size} {
            font-size: ${size}px;
          }
        `}</style>
-       <p className={`test-component-${color} test-component-${size}`}>This is a test component</p>
+       <p
+         data-component-id={uuid}
+         className={`test-component-${color} test-component-${size}`}
+       >
+         This is a test component
+       </p>
      </>
    );
  };
```

## `style`に割り当てる

`href`とセレクター、両方に割り当てます。

```diff_jsx
  const uuid = crypto.randomUUID();

  export function TestComponent({ color, size }) {
    return (
      <>
-       <style href={`test-component-${color}`} precedence="medium">{`
+       <style href={`${uuid}-${color}`} precedence="medium">{`
-         .test-component-${color} {
+         [data-component-id="${uuid}"].test-component-${color} {
            color: ${color};
          }
        `}</style>
-       <style href={`test-component-${size}`} precedence="medium">{`
+       <style href={`${uuid}-${size}`} precedence="medium">{`
-         .test-component-${size} {
+         [data-component-id="${uuid}"].test-component-${size} {
            font-size: ${size}px;
          }
        `}</style>
        <p
          data-component-id={uuid}
          className={`test-component-${color} test-component-${size}`}
        >
          This is a test component
        </p>
      </>
    );
  };
```

これらを呼び出してみます。
`red`, `blue`, `20`を 2 回ずつ使っています。

```jsx
  import { TestComponent } from "./TestComponent";

  export default function App() {
    return (
      <>
        <TestComponent color="red" size={10} />
        <TestComponent color="red" size={20} />
        <TestComponent color="blue" size={20} />
        <TestComponent color="blue" size={30} />
      </>
    );
  }
```

実際に生成された`style`のコードはこのようになっていました。

```html
<style data-href="1752b6a4-995b-483e-b439-b9d1d22504b9-red" data-precedence="medium">
  [data-component-id="1752b6a4-995b-483e-b439-b9d1d22504b9"].test-component-red {
    color: red;
  }
</style>
<style data-href="1752b6a4-995b-483e-b439-b9d1d22504b9-10" data-precedence="medium">
  [data-component-id="1752b6a4-995b-483e-b439-b9d1d22504b9"].test-component-10 {
    font-size: 10px;
  }
</style>
<style data-href="1752b6a4-995b-483e-b439-b9d1d22504b9-20" data-precedence="medium">
  [data-component-id="1752b6a4-995b-483e-b439-b9d1d22504b9"].test-component-20 {
    font-size: 20px;
  }
</style>
<style data-href="1752b6a4-995b-483e-b439-b9d1d22504b9-blue" data-precedence="medium">
  [data-component-id="1752b6a4-995b-483e-b439-b9d1d22504b9"].test-component-blue {
    color: blue;
  }
</style>
<style data-href="1752b6a4-995b-483e-b439-b9d1d22504b9-30" data-precedence="medium">
  [data-component-id="1752b6a4-995b-483e-b439-b9d1d22504b9"].test-component-30 {
    font-size: 30px;
  }
</style>
```

重複なくスタイルを宣言できました。

## 最後に

一旦形になったとは言え、問題はたくさんあります

- エディタで補完がきかない
- `props`の数が増えるとコードの管理が煩雑
- `style`の数が多い

などなど。

今の時点で実用的とは思いませんが、しばらく経ってから意外と役立つこともあるだろう、という希望的観測とともにこの記事は終わりです。