處理重複並且維持功能優先專案的可維護性
Tailwind 鼓勵 功能優先 的開發流程,在一開始的設計中僅使用功能 class 來避免不成熟的抽象行為。
<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">案例分析</div>
<a href="#" class="block mt-1 text-lg leading-tight font-medium text-black hover:underline">為您新的生意找到消費者</a>
<p class="mt-2 text-gray-500">展開新生意是一項艱鉅的工作。五個點子讓您找到第一位消費者。</p>
</div>
</div>
</div>
但是隨著專案的成長,你將無可避免地發現自己正在不同的地方重複建立著擁有共通功能的相同元件,這通常發生在小型元件,像是按鈕、表單元件和徽章等等。
<!-- 在每個按鈕上重複輸入這些 class 是一件很痛苦的事情 -->
<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>
在多個元件中同步保持長串功能 class 的一致性,很快會變成一種維護上的負擔,所以當你開始進入像這個案例中的痛苦迴圈時,最好的做法是將其提取成一個元件。
定義一個 UI 元件所需要的資訊都能完全只存在於 CSS 中是一件罕見的事 — 大多數情況下,你也需要依靠一些相對應的的 HTML 結構。
不要依靠 CSS class 來提取複雜元件
<style>
.vacation-card { /* ... */ }
.vacation-card-info { /* ... */ }
.vacation-card-eyebrow { /* ... */ }
.vacation-card-title { /* ... */ }
.vacation-card-price { /* ... */ }
</style>
<!-- 即使有自定義的 CSS,你還是需要複製相同的 HTML 結構 -->
<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">私人別墅</div>
<div class="vacation-card-title">
<a href="/vacations/cancun">全部包到好的坎昆休閒度假村</a>
</div>
<div class="vacation-card-price">一晚 $299 美金</div>
</div>
</div>
</div>
基於這個理由,較好的方式是提取可重複使用的 UI 片段到模板或是 JavaScript 元件中,而不是編寫自定義的 CSS class。
透過建立單一來源的模板,你可以持續使用功能 class 並避免在不同地方複製相同的 class 而造成維護上的負擔。
建立模板或是 JavaScript 元件
<!-- In use -->
<VacationCard
img="/img/cancun.jpg"
imgAlt="Beach in Cancun"
eyebrow="私人別墅"
title="全部包到好的坎昆休閒度假村"
pricing="一晚 $299 美金"
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 components, ERB partials, Blade components, Twig includes 等。
對於小型元件,像是按鈕、表單元件,跟建立 CSS class 相比,建立模板或是 JavaScript 元件可能會太大材小用。
在這情況下,你可以使用 Tailwind 的 @apply
指令輕易的提取出共用的部分成為 CSS 元件 class。
這邊示範使用 @apply
整合現有的功能來組成一個 btn-indigo
class:
<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
指令也能夠讓 Tailwind 理解在清除樣式時,哪些 components
層級的樣式需要清除。閱讀 生產環境優化 以獲得更多細節。
Like with custom utilities, you can generate responsive
, hover
, focus
, active
, and other variants of your own custom components using the @variants
directive:
/* ... */
@layer components {
@variants responsive, hover {
.btn-blue {
@apply py-2 px-4 bg-blue-500 ...;
}
}
}
Learn more in the @variants directive documentation.
除了在你的 CSS 檔案中直接撰寫元件 class 外,你還可以藉由撰寫你自己的插件來新增元件 class 到 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 元件成一個函式庫或是在多個專案中更容易的共用元件,這將會是一個好選擇。
在 新增元件 了解更多。