基本概念
重複の管理と再利用可能な抽象化の作成。
Tailwind は、ユーティリティファースト ワークフローを推奨しており、デザインは低レベルのユーティリティクラスのみを使用して実装されます。これは、早期の抽象化とそれに伴う問題点を回避するための強力な方法です。
しかしもちろん、プロジェクトが大きくなるにつれて、同じデザインを多くの場所で再現するために、共通のユーティリティの組み合わせを繰り返し使用することになります。
たとえば、以下のテンプレートでは、各アバター画像のユーティリティクラスが5回も繰り返されていることがわかります。
<div>
<div class="flex items-center space-x-2 text-base">
<h4 class="font-semibold text-slate-900">Contributors</h4>
<span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700">204</span>
</div>
<div class="mt-3 flex -space-x-2 overflow-hidden">
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.25&w=256&h=256&q=80" alt=""/>
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
</div>
<div class="mt-3 text-sm font-medium">
<a href="#" class="text-blue-500">+ 198 others</a>
</div>
</div>
ご心配なく!このガイドでは、プロジェクトでスタイルを再利用するためのさまざまな戦略と、それぞれを使用する際のベストプラクティスについて説明します。
多くの場合、このような重複は、すべてが1箇所にまとめられているか、配列項目を反復処理してマークアップを1回だけ記述しているため、実際には問題になりません。
再利用する必要があるスタイルが単一のファイル内でのみ再利用される必要がある場合は、マルチカーソル編集とループが重複を管理する最も簡単な方法です。
重複が単一ファイル内の要素のグループに限定されている場合、それを処理する最も簡単な方法は、マルチカーソル編集を使用して、各要素のクラスリストを一度にすばやく選択して編集することです。
<nav class="flex justify-center space-x-4">
<a href="/dashboard" class="font-medium px-3 py-2 text-slate-700 rounded-lg hover:bg-slate-100 hover:text-slate-900">Home</a>
<a href="/team" class="font-medium px-3 py-2 text-slate-700 rounded-lg hover:bg-slate-100 hover:text-slate-900">Team</a>
<a href="/projects" class="font-medium px-3 py-2 text-slate-700 rounded-lg hover:bg-slate-100 hover:text-slate-900">Projects</a>
<a href="/reports" class="font-medium px-3 py-2 text-slate-700 rounded-lg hover:bg-slate-100 hover:text-slate-900">Reports</a>
</nav>
これが最適なソリューションになることがどれくらい多いかに驚くでしょう。重複したクラスリストをすべて同時にすばやく編集できる場合、追加の抽象化を導入するメリットはありません。
コンポーネントを抽出したり、カスタムクラスを作成する必要があると仮定する前に、テンプレートで実際に複数回使用していることを確認してください。
多くの場合、レンダリングされたページに複数回表示されるデザイン要素は、実際のマークアップがループでレンダリングされるため、実際には1回しか作成されません。
たとえば、このガイドの冒頭にある重複したアバターは、実際のプロジェクトではほぼ確実にループでレンダリングされます。
<div>
<div class="flex items-center space-x-2 text-base">
<h4 class="font-semibold text-slate-900">Contributors</h4>
<span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700">204</span>
</div>
<div class="mt-3 flex -space-x-2 overflow-hidden">
{#each contributors as user}
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="{user.avatarUrl}" alt="{user.handle}"/>
{/each}
</div>
<div class="mt-3 text-sm font-medium">
<a href="#" class="text-blue-500">+ 198 others</a>
</div>
</div>
必要に応じて、ループまたは`map`を使用してナビゲーションの例を書き直すこともできます。
<nav className="flex sm:justify-center space-x-4">
{[
['Home', '/dashboard'],
['Team', '/team'],
['Projects', '/projects'],
['Reports', '/reports'],
].map(([title, url]) => (
<a href={url} className="rounded-lg px-3 py-2 text-slate-700 font-medium hover:bg-slate-100 hover:text-slate-900">{title}</a>
))}
</nav>
このようにループで要素がレンダリングされると、実際のクラスリストは1回だけ記述されるため、解決する必要がある実際の重複の問題はありません。
複数のファイルでいくつかのスタイルを再利用する必要がある場合は、React、Svelte、Vueなどのフロントエンドフレームワークを使用している場合はコンポーネントを、Blade、ERB、Twig、Nunjucksなどのテンプレート言語を使用している場合はテンプレートパーシャルを作成するのが最適な戦略です。
<template>
<div>
<img class="rounded" :src="img" :alt="imgAlt">
<div class="mt-2">
<div>
<div class="text-xs text-slate-600 uppercase font-bold tracking-wider">{{ eyebrow }}</div>
<div class="font-bold text-slate-700 leading-snug">
<a :href="url" class="hover:underline">{{ title }}</a>
</div>
<div class="mt-2 text-sm text-slate-600">{{ pricing }}</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['img', 'imgAlt', 'eyebrow', 'title', 'pricing', 'url']
}
</script>
このコンポーネントは、好きなだけ多くの場所に使用できます。スタイルの情報は単一の情報源に保持されるため、一箇所で簡単にまとめて更新できます。
コンポーネントが単一のHTML要素でない限り、それを定義するために必要な情報はCSSだけでは捉えられません。少しでも複雑なものであれば、HTML構造はCSSと同じくらい重要です。
複雑なコンポーネントを抽出するためにCSSクラスに依存しないでください。
新しいメッセージが届いています!
<!-- Even with custom CSS, you still need to duplicate this HTML structure -->
<div class="chat-notification">
<div class="chat-notification-logo-wrapper">
<img class="chat-notification-logo" src="/img/logo.svg" alt="ChitChat Logo">
</div>
<div class="chat-notification-content">
<h4 class="chat-notification-title">ChitChat</h4>
<p class="chat-notification-message">You have a new message!</p>
</div>
</div>
<style>
.chat-notification { /* ... */ }
.chat-notification-logo-wrapper { /* ... */ }
.chat-notification-logo { /* ... */ }
.chat-notification-content { /* ... */ }
.chat-notification-title { /* ... */ }
.chat-notification-message { /* ... */ }
</style>
このように、コンポーネント内の異なる要素にクラスを作成した場合でも、このコンポーネントを使用するたびにHTMLを複製する必要があります。フォントサイズを一箇所で更新できますが、タイトルをリンクにする必要がある場合はどうでしょうか?
コンポーネントとテンプレートパーシャルは、コンポーネントがHTMLとスタイルをカプセル化できるため、CSSのみの抽象化よりもこの問題をはるかにうまく解決します。すべてのインスタンスのフォントサイズを変更することは、CSSと同じくらい簡単ですが、すべてのタイトルをリンクに変更することも一箇所でできます。
テンプレートパーシャルまたはJavaScriptコンポーネントを作成する
新しいメッセージが届いています!
function Notification({ imageUrl, imageAlt, title, message }) {
return (
<div className="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md flex items-center space-x-4">
<div className="shrink-0">
<img className="h-12 w-12" src={imageUrl.src} alt={imageAlt}>
</div>
<div>
<div className="text-xl font-medium text-black">{title}</div>
<p className="text-slate-500">{message}</p>
</div>
</div>
)
}
このようなコンポーネントとテンプレートパーシャルを作成する場合、スタイルの単一の情報源が既に存在するため、ユーティリティークラス以外のものを使用する理由はありません。
ERBやTwigなどの従来のテンプレート言語を使用している場合、ボタンのように小さなものに対してテンプレートパーシャルを作成するのは、btn
のような単純なCSSクラスと比較して、やりすぎに感じるかもしれません。
より複雑なコンポーネントには適切なテンプレートパーシャルを作成することを強くお勧めしますが、テンプレートパーシャルがやりすぎに感じる場合は、Tailwindの@apply
ディレクティブを使用して、繰り返されるユーティリティパターンをカスタムCSSクラスに抽出できます。
既存のユーティリティから構成されたbtn-primary
クラスの例を示します。
<!-- Before extracting a custom class -->
<button class="py-2 px-5 bg-violet-500 text-white font-semibold rounded-full shadow-md hover:bg-violet-700 focus:outline-none focus:ring focus:ring-violet-400 focus:ring-opacity-75">
Save changes
</button>
<!-- After extracting a custom class -->
<button class="btn-primary">
Save changes
</button>
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn-primary {
@apply py-2 px-5 bg-violet-500 text-white font-semibold rounded-full shadow-md hover:bg-violet-700 focus:outline-none focus:ring focus:ring-violet-400 focus:ring-opacity-75;
}
}
@apply
と@layer
の詳細については、関数とディレクティブのドキュメントを参照してください。
どのような場合でも、「見た目」をきれいにするためだけに@apply
を使用しないでください。Tailwindのクラスが散らばったHTMLテンプレートは確かに見栄えがよくありません。大量のカスタムCSSを持つプロジェクトでの変更は、さらに悪化します。
すべてに@apply
を使用し始めると、基本的にCSSを書き直しているだけで、Tailwindが提供するワークフローと保守性の利点をすべて失うことになります。例えば、
@apply
を使用する場合は、ボタンやフォームコントロールなど、非常に小さく、再利用性の高いものに対してのみ使用してください。それでも、Reactのようなフレームワークを使用していない場合に限ります。