1. 核心概念
  2. 重复使用样式

Tailwind 鼓励采用 utility-first 工作流程,其中仅使用低级实用程序类来实现设计。这是一种避免过早抽象及其带来的痛点的有效方法。

但当然,随着项目的发展,您不可避免地会发现自己重复常见的实用程序组合以在许多不同的地方重新创建相同的设计。

例如,在下面的模板中,您可以看到每个头像图像的实用程序类重复了五次:

Contributors

204
<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>

不要惊慌!在本指南中,您将了解在项目中重复使用样式的不同策略,以及何时使用每种样式的最佳实践。


使用编辑器和语言功能

很多时候,像这样的重复甚至不是一个真正的问题,因为它们都集中在一个地方,甚至根本不存在,因为您正在遍历一个项目数组并且只编写一次标记。

如果您需要重复使用的样式仅需要在单个文件中重复使用,则多光标编辑和循环是管理任何重复的最简单方法。

多光标编辑

当重复局限于单个文件中的一组元素时,处理它的最简单方法是使用 multi-cursor editing 快速选择并编辑每个元素的类列表:

<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>

你会惊讶地发现,这往往是最好的解决方案。如果你能快速同时编辑所有重复的类列表,那么引入任何额外的抽象就没有任何好处。

循环

在您假设需要提取组件或为某些内容创建自定义类之前,请确保您在模板中确实多次使用它。

很多时候,在呈现的页面中多次出现的设计元素实际上只创作了一次,因为实际的标记是在循环中呈现的。

例如,本指南开头的重复头像几乎肯定会在实际项目中循环渲染:

Contributors

204
<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>

当元素像这样在循环中呈现时,实际的类列表仅被写入一次,因此不需要解决实际的重复问题。


提取组件和部分

如果您需要在多个文件中重复使用某些样式,那么最好的策略是:如果您使用 React、Svelte 或 Vue 等前端框架,则创建一个 component;如果您使用 Blade、ERB、Twig 或 Nunjucks 等模板语言,则创建一个 template partial

Beach
Private Villa
$299 USD per night
VacationCard.vue
<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>

现在,您可以在任意多个地方使用此组件,同时仍然拥有样式的单一真实来源,以便可以轻松地在一个地方一起更新它们。

与 CSS 抽象相比

除非组件是单个 HTML 元素,否则定义它所需的信息无法仅通过 CSS 捕获。对于任何稍微复杂的东西,HTML 结构与 CSS 一样重要。

不要依赖 CSS 类来提取复杂组件

ChitChat

You have a new message!

<!-- 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。当然,您可以在一个地方更新每个实例的字体大小,但如果您需要将标题变成链接怎么办?

组件和模板部分比仅使用 CSS 的抽象更好地解决了这个问题,因为组件可以封装 HTML 和样式。更改每个实例的字体大小与使用 CSS 一样简单,但现在您还可以在一个地方将所有标题变成链接。

创建模板部分或 JavaScript 组件

ChitChat

You have a new message!

Notification.jsx
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>
  )
}

当您创建这样的组件和模板部分时,没有理由使用除实用程序类之外的任何其他东西,因为您已经拥有样式的单一真实来源。


使用@apply提取类

如果您使用的是传统模板语言(例如 ERB 或 Twig),那么与 btn 之类的简单 CSS 类相比,为按钮这样小的东西创建模板部分可能会感觉有点小题大做。

虽然强烈建议您为更复杂的组件创建适当的模板部分,但当模板部分感觉过于繁琐时,您可以使用 Tailwind 的 @apply 指令将重复的实用程序模式提取到自定义 CSS 类中。

以下是使用 @apply 从现有实用程序组成 btn-primary 类的样子:

HTML
<!-- 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>
CSS
@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;
  }
}

Functions & Directives 文档中了解有关 @apply@layer 的更多信息。

避免过早抽象

无论你做什么,不要使用 @apply 只是为了让事情看起来更干净。是的,充斥着 Tailwind 类的 HTML 模板有点丑陋。在包含大量自定义 CSS 的项目中做出更改会更糟糕。

如果您开始对所有内容使用 @apply,那么您基本上只是再次编写 CSS,并抛弃 Tailwind 为您提供的所有工作流程和可维护性优势,例如:

  • 你必须一直想着类名 — 没有什么比为不值得命名的东西想出一个类名更能拖慢你的速度或消耗你的精力了。
  • 你必须在多个文件之间跳转才能进行更改 — 在将所有内容放在一起之前,这会比你想象的更严重地影响工作流程。
  • 更改样式更可怕 — CSS 是全局的,你确定你可以更改该类中的最小宽度值而不会破坏网站其他部分的内容吗?
  • 你的 CSS 包会更大 — 哎呀。

如果您要使用 @apply,请将其用于非常小、高度可重复使用的东西,例如按钮和表单控件 — 即使如此,也只有在您不使用 React 之类的框架时才可以使用,因为组件是更好的选择。