松の盆栽のイラスト

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- で指定する

参考

記事の理解度チェック

以下の問題に答えて、記事の理解を深めましょう。

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

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

    もう一度考えてみましょう

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

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

    正解!

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

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

    もう一度考えてみましょう

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

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

    もう一度考えてみましょう

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

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

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

    もう一度考えてみましょう

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

    正解!

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

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

    もう一度考えてみましょう

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

    もう一度考えてみましょう


Contributors

> GitHub で修正を提案する
この記事をシェアする
はてなブックマークに追加

関連記事