💎

【Tailwind和訳】CORE CONCEPTS/Extracting Components

2021/10/23に公開

この記事について

この記事は、CORE CONCEPTS/Extracting Componentsの記事を和訳したものです。

記事内で使用する画像は、公式ドキュメント内の画像を引用して使用させていただいております。

コンポーネントの抽出

重複への対応とユーティリティファーストのプロジェクトの保守性の維持

Tailwind では、ユーティリティーファーストのワークフローを推奨しています。設計の初期段階では、ユーティリティークラスのみを使用して実装し、早期の抽象化を避けます。
Image from Gyazo

<div class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl">
  <div class="md:flex">
    <div class="md:flex-shrink-0">
      <img
        class="h-48 w-full object-cover md:w-48"
        src="/img/store.jpg"
        alt="Man looking at item at a store"
      />
    </div>
    <div class="p-8">
      <div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">Case study</div>
      <a href="#" class="block mt-1 text-lg leading-tight font-medium text-black hover:underline"
        >Finding customers for your new business</a
      >
      <p class="mt-2 text-gray-500">
        Getting a new business off the ground is a lot of hard work. Here are five ideas you can use
        to find your first customers.
      </p>
    </div>
  </div>
</div>

しかし、プロジェクトが大きくなってくると、同じコンポーネントをさまざまな場所で再現するために、共通のユーティリティーの組み合わせを繰り返すことになります。これは、ボタン、フォームエレメント、バッジなどの小さなコンポーネントで顕著に見られます。
Image from Gyazo

<!-- Repeating these classes for every button can be painful -->
<button
  class="py-2 px-4 bg-green-500 text-white font-semibold rounded-lg shadow-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-opacity-75"
>
  Click me
</button>

ユーティリティークラスの長いリストを多くのコンポーネントインスタンスで同期させておくと、すぐにメンテナンスの負担になってしまいます。このように重複してしまった場合には、コンポーネントを抽出することをお勧めします。

テンプレートコンポーネントの抽出

UI コンポーネントを定義するために必要なすべての情報が CSS だけで完結することは非常に稀です。

複雑なコンポーネントの抽出を CSS クラスに頼らない

Image from Gyazo

<style>
  .vacation-card {
    /* ... */
  }
  .vacation-card-info {
    /* ... */
  }
  .vacation-card-eyebrow {
    /* ... */
  }
  .vacation-card-title {
    /* ... */
  }
  .vacation-card-price {
    /* ... */
  }
</style>

<!-- Even with custom CSS, you still need to duplicate this HTML structure -->
<div class="vacation-card">
  <img class="vacation-card-image" src="..." alt="Beach in Cancun" />
  <div class="vacation-card-info">
    <div>
      <div class="vacation-card-eyebrow">Private Villa</div>
      <div class="vacation-card-title">
        <a href="/vacations/cancun">Relaxing All-Inclusive Resort in Cancun</a>
      </div>
      <div class="vacation-card-price">$299 USD per night</div>
    </div>
  </div>
</div>

このような理由から、カスタム CSS クラスを記述する代わりに、UI の再利用可能な部分をテンプレートのパーシャルや JavaScript コンポーネントに抽出する方が良い場合があります。

テンプレートのための単一の情報源を作ることで、同じクラスを複数の場所に複製することによるメンテナンスの負担なく、ユーティリティークラスを使い続けることができます。

テンプレートパーシャルまたは JavaScript コンポーネントの作成

<!-- In use -->
<VacationCard
  img="/img/cancun.jpg"
  imgAlt="Beach in Cancun"
  eyebrow="Private Villa"
  title="Relaxing All-Inclusive Resort in Cancun"
  pricing="$299 USD per night"
  url="/vacations/cancun"
/>

<!-- ./components/VacationCard.vue -->
<template>
  <div>
    <img class="rounded" :src="img" :alt="imgAlt" />
    <div class="mt-2">
      <div>
        <div class="text-xs text-gray-600 uppercase font-bold">{{ eyebrow }}</div>
        <div class="font-bold text-gray-700 leading-snug">
          <a :href="url" class="hover:underline">{{ title }}</a>
        </div>
        <div class="mt-2 text-sm text-gray-600">{{ pricing }}</div>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    props: ["img", "imgAlt", "eyebrow", "title", "pricing", "url"],
  }
</script>

上記の例ではVueを使用していますが、React コンポーネントERB パーシャルBlade コンポーネントTwig インクルードなどでも同様のアプローチが可能です。

@apply によるコンポーネントクラスの抽出

ボタンやフォーム要素のような小さなコンポーネントの場合、テンプレートパーシャルや JavaScript コンポーネントの作成は、シンプルな CSS クラスに比べて重く感じることがよくあります。

このような状況では、Tailwind の@applyディレクティブを使って、よくあるユーティリティーパターンを CSS のコンポーネントクラスに簡単に抽出することができます。

ここでは、@applyを使って既存のユーティリティから構成したbtn-indigoクラスのイメージをご紹介します。

Image from Gyazo

<button class="btn-indigo">Click me</button>

<style>
  .btn-indigo {
    @apply py-2 px-4 bg-indigo-500 text-white font-semibold rounded-lg shadow-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-opacity-75;
  }
</style>

意図しない特異性の問題を避けるために、カスタムコンポーネントのスタイルを@layer components { ... }ディレクティブでラップすることをお勧めします。} ディレクティブを使って、スタイルがどのレイヤーに属しているかを Tailwind に伝えることをお勧めします。

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .btn-blue {
    @apply py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75;
  }
}

Tailwind は、これらのスタイルを@tailwind componentsと同じ場所に自動的に移動させますので、ソースファイルの順番を気にする必要はありません。

また、@layerディレクティブを使用すると、componentsレイヤーをパージする際に、これらのスタイルをパージの対象とするように指示することができます。詳しくは、プロダクションへの最適化についてのドキュメントをご覧ください。

カスタムユーティリティーと同様に、@variantsディレクティブを使用して、独自のカスタムコンポーネントのresponsivehoverfocusactiveなどのバリエーションを生成することができます。

/* ... */

@layer components {
  @variants responsive, hover {
    .btn-blue {
      @apply py-2 px-4 bg-blue-500 ...;
    }
  }
}

詳しくは@variants ディレクティブのドキュメントをご覧ください。

コンポーネントプラグインの作成

CSS ファイルに直接コンポーネントクラスを記述するだけでなく、独自のプラグインを記述することで Tailwind にコンポーネントクラスを追加することができます。

// tailwind.config.js
const plugin = require("tailwindcss/plugin")

module.exports = {
  plugins: [
    plugin(function ({ addComponents, theme }) {
      const buttons = {
        ".btn": {
          padding: `${theme("spacing.2")} ${theme("spacing.4")}`,
          borderRadius: theme("borderRadius.md"),
          fontWeight: theme("fontWeight.600"),
        },
        ".btn-indigo": {
          backgroundColor: theme("colors.indigo.500"),
          color: theme("colors.white"),
          "&:hover": {
            backgroundColor: theme("colors.indigo.600"),
          },
        },
      }

      addComponents(buttons)
    }),
  ],
}

これは、Tailwind のコンポーネントをライブラリとして公開したい場合や、複数のプロジェクトでコンポーネントを共有することを容易にしたい場合に適した選択肢となります。

詳しくは、コンポーネントプラグインのドキュメントをご覧ください。

Discussion

ログインするとコメントできます