かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

React Tailwind CSS v3 クラス名を動的に作ったらクラスが適応されない?にハマる

React で Tailwind CSS v3.x を使っていて変数で動的に Tailwind CSS のクラス名を作成したらスタイルが適応されなかったのでメモ

環境

  • react@18.1.0
  • typescript@4.6.4
  • tailwindcss@3.1.2

動的にサイズを指定できる Spinner を作りかった。

<svg role="status" class="w-8 h-8 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
  <path d="M100 … 50.5908Z" fill="currentColor"/>
  <path d="M93.9676 … 39.0409Z" fill="currentFill"/>
</svg>

このコンポーネントのサイズは <svg> タグの w-{n} h-{n} クラスを変更すれば良いっぽい

🙅 動作しない例

import { FC } from 'react';

type SizeType = 'xs' | 'sm' | 'md' | 'lg';
type SpinnerProps = {
  size?: SizeType;
};

const getSize = (size: SizeType): number => {
  switch (size) {
    case 'xs': { return 4; }
    case 'sm': { return 6; }
    case 'md': { return 8; }
    case 'lg': { return 10; }
    default: { return 8; }
  }
};

const Spinner: FC<SpinnerProps> = ({ size = 'md' }) => {
  const n = getSize(size);
   return (
    <svg
      role='status'
      className={`inline w-${n} h-${n} mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600`}
      viewBox='0 0 100 101'
      fill='none'
      xmlns='http://www.w3.org/2000/svg'
    >
      <path d='M100 ... 50.5908Z' fill='currentColor' />
      <path d='M93.9676 … 39.0409Z' fill='currentFill' />
    </svg>
  );
};

export const App: FC = () => {
  return (
    <Spinner size="md" />
  );
};

<Spinner /> コンポーネントに付けられる w-6, h-6 のスタイルがそもそも存在しない状態でした…

🙆 問題ない例

type SizeType = 'xs' | 'sm' | 'md' | 'lg';

type SpinnerProps = {
  size?: SizeType;
};

const getSizeClass = (size: SizeType): string => {
  switch (size) {
    case 'xs': { return 'w-4 h-4'; }
    case 'sm': { return 'w-6 h-6'; }
    case 'md': { return 'w-8 h-8'; }
    case 'lg': { return 'w-10 h-10'; }
    default: { return 'w-8 h-8'; }
  }
};

const Spinner: FC<SpinnerProps> = ({ size = 'md' }) => {
  const sizeClass = getSizeClass(size);
   return (
    <svg
      role='status'
      className={`inline ${sizeClass} mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600`}
      viewBox='0 0 100 101'
      fill='none'
      xmlns='http://www.w3.org/2000/svg'
    >
      <path d='M100 ... 50.5908Z' fill='currentColor' />
      <path d='M93.9676 … 39.0409Z' fill='currentFill' />
    </svg>
  );
};

Tailwind CSS で定義されているクラス名そのものを動的に作るのではなく、存在しているクラス名の文字列が変数で渡される場合は問題なくスタイルが適応された

Tailwind CSS の賢い Migrating to the JIT engine との相性の問題

The new Just-in-Time engine we announced in March has replaced the classic engine in Tailwind CSS v3.0.
The new engine generates the styles you need for your project on-demand, and might necessitate some small changes to your project depending on how you have Tailwind configured.
cf. Upgrade Guide - Tailwind CSS

Tailwind CSS v3.0 からデフォルトで Just-in-Time engine が搭載されており、プロジェクトに必要なスタイルだけを動的に生成するようになっている

In tailwind you can't use dynamic class naming like bg-${color}, though we can mock it to be that, but it is not preffered. Because Tailwind compiles its CSS, it looks up over all of your code and checks if a class name matches.
cf. reactjs - Tailwind not working when using variables (React.js) - Stack Overflow

つまり、Tailwind CSS v3.0 から不要なクラスをバインドしないように、プロジェクトで使われているクラスに該当するスタイルだけを生成するようになった。スタイルの生成方法が恐らくコードを静的解析し Tailwind CSS のクラス名とマッチするものがあれば、そのスタイルを出力する仕組みになっている。

なので、Tailwind CSS のクラス名そのものを変数を使って動的に作成すると解析時にマッチせずスタイルが出力されない。ということっぽい!

  • 🙅 NGだった例 w-{n} は Tailwind CSS のクラス名とマッチしないので w-4, w-6 といったスタイルが一切生成されない
  • 🙆 OK だった例 はコード中に w-4, w-6, w-8 という文字列があるので、実際にコンポーネントで使っているのが w-6 だけだったとしても、使ってない w-4, w-8 のスタイルも出力される

という挙動になっていたのが理由だったようです。

utility ファーストな設計なので、使ってないクラスも全て読み込むと巨大な CSS を読み込むことになるのでいい感じにしてくれる仕組みのお作法を理解してなかったのが原因でした。
ドキュメントがうまく探せずなんでや〜ってなってました (ᐡ o̴̶̷̤ ﻌ o̴̶̷̤ ᐡ)

おわり


[参考]

しっぽと風…と言えば、みんな大好き「狼と香辛料」ですよね!!!