0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-06-18

この記事の概要

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

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

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

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

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

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

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

  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 を生成することができます。

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

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

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


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

+ 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 属性に割り当てても認識されません。2

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

  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とセレクター、両方に割り当てます。

  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 回ずつ使っています。

  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のコードはこのようになっていました。

<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の数が多い

などなど。

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

  1. この記事で書いたような処理を通せば使えますが、そこまでするほどか?という疑問が出てきます。 https://qiita.com/xrxoxcxox/items/54bb72a350866fa2d2c7

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?