This article was translated from Japanese by AI and may contain inaccuracies. For the most accurate content, please refer to the original Japanese version.
松の盆栽のイラスト

CSS から React コンポーネントを生成する MistCSS

MistCSS は CSS in JS になぞらえた JS from CSS というコンセプトで、CSS から React コンポーネントを生成するツールです。ピュアな CSS を記述できるので、学習コストが低い、パフォーマンスに影響がないといったメリットがあります。

昨今のフロントエンド開発では、CSS の手法が多様化しています。特に React での開発では以下のような手法があげられます。

  • グローバル CSS(エントリーポイントで 1 つの CSS ファイルを読み込む)
  • CSS Modules
  • CSS in JS
  • Tailwind CSS

CSS の手法に新たな選択肢が加わりました。それが MistCSS です。MistCSS は CSS in JS になぞらえた JS from CSS というコンセプトで、CSS から React コンポーネントを生成するツールです。

MistCSS のメリットとして、以下のような点が挙げられます。

  • ピュアな CSS を記述できるので、学習コストが低い
  • 生成されたコンポーネントは自動で型安全になる
  • コンポーネントがシンプルでステートレスな設計になる
  • ゼロランタイムで動作するので、パフォーマンスに影響がない

MistCSS を使ってみる

実際に MistCSS を使ってみましょう。以下のコマンドで MistCSS をインストールします。

npm install --save-dev mistcss

JS From CSS というコンセプトのとおり、まずは CSS を記述します。MistCSS の自動生成の対象とする CSS ファイルはいくつかのルールがあります。

  • CSS ファイルの拡張子は .mist.css であること
  • コンポーネント名を @scope で指定する。このクラス名はプロジェクトで一意である必要がある
  • :scope 擬似クラスでコンポーネントのルート要素を指定する
  • Props で受け取る値を data- で指定する

例として、以下の Props を受け取る <Button> コンポーネントを作成してみましょう。

  • variantprimary または secondary
  • disabledtrue または false
src/components/button.mist.css
/*
 * @scope() の値に .button を指定すると Button コンポーネントが生成される
 */
@scope (.button) {
  /** <button> 要素がコンポーネントのルート要素になる */
  button:scope {
    /* 共通のスタイル */
    padding: 8px 16px;
    border: none;
    border-radius: 4px;
    font-size: 16px;
    cursor: pointer;
 
    /**
     * <Button variant="primary"> の場合
     */
    &[data-variant='primary'] {
      background-color: #007bff;
      color: #fff;
    }
 
    /**
     * <Button variant="secondary"> の場合
     */
    &[data-variant='secondary'] {
      background-color: #6c757d;
      color: #fff;
    }
 
    /**
     * <Button disabled> の場合
     */
    &[data-disabled] {
      opacity: 0.5;
      cursor: not-allowed;
    }
  }
}

Note

2024 年 3 月現在、属性の値は必ずシングルクォート(')で囲む必要があるようです。ダブルクォートで囲むと Props の型が正しく生成されませんでした。

CSS の記述が完了したら、以下のコマンドで React コンポーネントを生成します。

npx mistcss ./src

コマンドの実行に成功すると、src/components/button.mist.css.tsx が生成されます。

src/components/Button.tsx
// Generated by MistCSS, do not modify
import './button.mist.css'
 
type ButtonProps = {
  children?: React.ReactNode
  variant?: 'primary' | 'secondary'
  disabled?: boolean
} & JSX.IntrinsicElements['button']
 
export function Button({ children, variant, disabled, ...props }: ButtonProps) {
  return (
    <button {...props} className="Button" data-variant={variant} data-disabled={disabled}>
      {children}
    </button>
  )
}

姿勢されたコンポーネントは以下のように使用できます。

src/App.tsx
import { Button } from './components/Button'
 
export function App() {
  return (
    <div>
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button disabled>Disabled</Button>
    </div>
  )
}

1 つの CSS ファイルに複数のコンポーネントを記述する

複数の @scope を記述することで、1 つの CSS ファイルから複数のコンポーネントを生成することができます。これは <DialogHeader><DialogBody><DialogFooter> などのように、複数のコンポーネントで 1 つの UI を構成する場合に便利です。

src/components/dialog.mist.css
@scope (.dialog) { /** */ }
@scope (.dialog-header) { /** */ }
@scope (.dialog-body) { /** */ }
@scope (.dialog-footer) { /** */ }
import {
  Dialog,
  DialogHeader,
  DialogBody,
  DialogFooter,
} from "./components/Dialog";

CSS による論理演算

/* foo=bar かつ baz=qux の場合 */
&[data-foo="bar"]&[data-baz="qux"] {
  /**  */
}
/* foo=bar または baz=qux の場合 */
&[data-foo="bar"],
&[data-baz="qux"] {
  /** */
}

まとめ

  • MistCSS は CSS から React コンポーネントを生成するツール
  • ピュアな CSS を記述できるので、学習コストが低い、パフォーマンスに影響がないといったメリットがある
  • CSS を以下のルールに従って記述し、コマンドを実行することでコンポーネントが生成される
    • CSS ファイルの拡張子は .mist.css
    • コンポーネント名を @scope で指定する
    • :scope 擬似クラスでコンポーネントのルート要素を指定する
    • Props で受け取る値を data- で指定する

参考

Comprehension check

Answer the following questions to deepen your understanding of the article.

MistCSS において、コンポーネント名を指定するためのルールはどれか?

  • CSS のファイル名がコンポーネント名になる

    Try again

    CSS のファイル名は生成される .tsx ファイルの名前に使われ、コンポーネント名には関係ありません。

  • @scope ルールの値に .{コンポーネント名} を指定する

    Correct!

    例として @scope(.button) と記述すると Button コンポーネントが生成されます。

  • @component ルールの値に .{コンポーネント名} を指定する

    Try again

    @component というルールは存在しません。

  • コンポーネント名はランダムに生成される

    Try again

    コンポーネント名は @scope ルールの値に指定した名前になります。

スタイルを変更するためのコンポーネントの Props を受け取るためのルールはどれか?

  • CSS 変数で指定した名前が Props になる

    Try again

  • data- で始まる属性を Props に指定する

    Correct!

    例として &[data-variant='primary'] と記述すると variant という Props が生成されます。

  • ネストされたクラス名が Props になる

    Try again

  • コンポーネントに Props を渡すことはできない

    Try again