为什么在下一个项目中使用 Tailwind CSS
Tailwind CSS是一个底层、可定制的 CSS 框架,目前发展迅猛。它经常被拿来与 Bootstrap 比较,但它们本质上截然不同。Bootstrap 提供预先设计好的组件,而Tailwind则提供构建模块,让开发者能够快速构建设计。
在接触 Tailwind 之前,我先学习了 CSS,然后转向 SCSS,最后又学习了 Styled Components。在我的学习过程中,每种技术都给我带来了很多宝贵的经验,但没有一种技术能像 Tailwind 一样,带给我如此巨大的满足感和效率提升。
漫长的旅程
对于初学者来说,编写纯 CSS 非常有利,因为可以充分理解并记住实现特定布局所需的规则。即使是高级开发者,也能通过编写完全符合需求的自定义 CSS 来获得非常好的效果。然而,这些规则很可能无法扩展。用户界面中的每个元素都需要自己的一套规则,跟踪数百条规则可能会变得令人望而生畏,难以维护。但这并不意味着无法管理它们。通过适当的练习和自律,这些规则可以被很好地组织和维护。
为了解决这个问题,我从 CSS 迁移到了 SCSS,并采用了7 合 1 架构。起初,我非常喜欢这种将代码清晰地划分到 7 个文件夹中的方式,每个文件夹分别代表网站的特定组件和部分。此外,它还带来了变量、函数和嵌套规则等新特性,开启了无限可能。这使得网站的样式更容易查找和维护。然而,我希望将样式嵌入到 JavaScript 代码中,这样就不用频繁地在不同文件之间切换。因此,我开始使用像Styled Components这样的 CSS-in-JS 解决方案。
在项目中无法使用 Tailwind 时,Styled Components 是我的首选。它是一个非常出色的库,因为它允许我将所有样式精确地应用到需要的地方。此外,它还能确保应用程序只使用所需的样式,而不是在每个页面上都导入所有样式。尽管它比其他解决方案有了很大的改进,但它仍然存在一个问题:重复性。我感觉自己一遍又一遍地编写相同的规则,浪费了大量时间。而且,我的样式表也变得越来越庞大。当我意识到这一点并发现 Tailwind 后,我决定放弃 Tailwind,并且从此再也没有后悔过。
句法
Tailwind 的语法非常简单:只需在 HTML 或 JSX 元素后添加代表一个或多个 CSS 声明的类名即可。如下所示:
<div class="max-w-sm mx-auto flex p-6 bg-white rounded-lg shadow-xl">
<div class="flex-shrink-0">
<img class="h-12 w-12" src="/img/logo.svg" alt="ChitChat Logo">
</div>
<div class="ml-6 pt-1">
<h4 class="text-xl text-gray-900 leading-tight">ChitChat</h4>
<p class="text-base text-gray-600 leading-normal">You have a new message!</p>
</div>
</div>
这将生成以下内容:
每个类名代表一个或多个CSS 声明。例如,flex 代表display: flex;p-6 padding: 1.5rem、mx-automargin-left: auto; margin-right: auto;等。
这种范式被称为“实用性优先”。它指的是利用一组有限的基本实用工具来构建复杂的组件。
与自定义 CSS 相比,使用这种方法,Tailwind 可以获得诸多优势。
小号款式
使用 Tailwind 的首要好处是编写的 CSS 不会线性增长。通过使用元素间共享的实用类名,可以确保打包后的文件大小保持一致。此外,Tailwind 与 Purge CSS 结合使用,可以移除所有未使用的样式,从而避免代码库因过多的类而变得臃肿。
使用 Tailwind CSS 的网站 CSS 包看起来会像这样:
样式表在某个阶段可能会比较高,但未来随着功能的增加,这些样式表将保持不变,从而节省大量 KB 空间。
Facebook也采用了类似的方法,不过他们使用的是自定义的CSS-in-JS 库,该库利用了原子 CSS,这与 Tailwind 的实用性优先方法非常相似。通过这种方法,他们将整个网站的 CSS 包大小从 413kb 减少到了 74kb,降幅高达约 82%!现在,我们无需创建自定义库也能拥有这种强大的功能。
💡 原子 CSS 类只能有一个唯一的 CSS 声明。这与实用性优先的 CSS 有很大不同,在实用性优先的 CSS 中,类可以有多个声明。
认知负荷降低
Tailwind 的设计理念是组件化。它能更轻松地将网站元素拆分成更小的组件,避免用对象或无关的 CSS 类污染代码库。此外,所有类都内联到组件中,使代码更易于阅读和理解。
例如,使用 Styled Components 构建的这个导航栏组件:
const Navbar = () => {
return (
<Wrapper>
<Items>
<NavItem label="Home" icon={<Home size={16} />} />
<NavItem label="Blog" icon={<Bookmark size={16} />} />
<NavItem label="About" icon={<User size={16} />} />
</Items>
</Wrapper>
)
}
const Wrapper = styled.nav`
position: fixed;
width: 100vw;
bottom: 0;
justify-content: center;
`
const Items = styled.ul`
flex: 1;
padding: 0 2rem;
justify-content: space-around;
`
const NavItem = ({label, icon, ...otherProps}) => (
<Item {...otherProps}>
<span>{icon}</span>
{label}
</Item>
)
const Item = styled.li`
display: flex;
flex-direction: column;
align-items: center;
font-size: 1.2rem;
`
我认为这段文字组织得很好,读完之后很容易理解。
让我们来看看 Tailwind 的实现方式:
const Navbar = () => {
return (
<nav className="fixed bottom-0 justify-center w-screen">
<ul className="justify-around flex-1 px-8">
<NavItem label="Home" icon={<Home size={16} />} />
<NavItem label="Blog" icon={<Bookmark size={16} />} />
<NavItem label="About" icon={<User size={16} />} />
</ul>
</nav>
)
}
const NavItem = ({label, icon, ...otherProps}) => (
<li className="flex flex-col items-center text-xl" {...otherProps}>
<span>{icon}</span>
{label}
</li>
)
Tailwind 从一开始就占用空间更少。此外,它的样式表也更容易理解,因为开发者在浏览代码时会同时读取样式表。无需跳转到特定文件即可查看样式表。
在这个小例子中,可能不太容易看出在大文件中这会变得多么繁琐。但在包含多个组件的大型代码库中,组件间各自独立的样式简直是一场噩梦。与 Tailwind 相比,理解每个元素如何构成用户界面需要花费更多的时间。
你可能会问,如何才能充分利用 Styled Components 的主要优势:允许在样式中使用 JavaScript。这取决于解决方案的具体情况。
可以利用内联样式对象或条件类名。
// Inline object
<CustomComponent styles={{ color: fontColorByBackground('#000') }}/>
// Conditional classes
import clsx from "clsx"
<CustomComponent
className={clsx('flex', {
'text-black': luminance > 0.18,
'text-white': luminance < 0.18,
})}
/>
然而,对于某些团队来说,这可能并非最具吸引力的解决方案。好消息是,由于我们使用的是 CSS,因此可以同时使用 Tailwind 和 Styled Components,而无需额外成本(Emotion CSS 属性或CSS 模块可能更符合 Tailwind 的设计理念)。
💡 请记住,Tailwind 是主流解决方案,因此您可以充分利用它的优势。对于需要大量 CSS/JavaScript 操作的情况,Styled Components 是可选的。此外,这也使得希望逐步迁移到 Tailwind 的代码库更容易上手。
更佳的成分
Tailwind 默认引导开发者以组件化的方式思考。它促使我们保持代码简洁,避免重复。这得益于它占用空间小,便于实现布局;它能让我们注意到重复或臃肿的样式;以及将样式拆分成小组件后,样式更容易阅读和记忆。
可以将这种做法想象成为网站的每个功能或部分创建 CSS 类名。只不过,我们不是用 CSS 来实现,而是用 HTML 来实现。例如,如果网站有一个“首页横幅”(Hero),那么最好创建一个组件来代表这个横幅部分。同样地,我们也可以用类似的方法处理博客文章、导航、页眉等等。
我们来看下一段代码:
const IndexPage = () => {
return (
<section className="container p-2 mx-auto lg:p-8 xl:p-0">
<h1 className="font-bold text-white text-11xl sm:text-12xl">Blog.</h1>
<div className="flex flex-col mx-auto space-y-8 md:grid md:row-gap-6 md:col-gap-8 md:grid-cols-2">
{posts.map(
({ frontmatter: { title, description }, slug, hasCoverImage }, i) => {
const isFirstItem = i === 0;
return (
<div
key={slug}
className={clsx(
"w-full bg-white rounded-md font-body relative overflow-hidden",
isFirstItem && "md:col-span-2",
!isFirstItem && "md:col-span-1"
)}
>
<a href="#">
{hasCoverImage && (
<img
className="object-contain w-full rounded-md md:object-cover"
src={src}
alt="cover image"
/>
)}
<header className="flex flex-col items-center p-12 space-y-4 text-center bg-white rounded-md">
<h1 className="max-w-6xl text-5xl font-bold">{title}</h1>
<p className="max-w-5xl text-2xl font-medium">
{description}
</p>
</header>
</a>
</div>
);
}
)}
</div>
</section>
);
};
你会发现,除非仔细阅读,否则很难理解它的意图。类名杂乱无章,HTML元素缺乏连贯性。人们立刻就会意识到,肯定有更好的方法来表示它。
如果我们把它分解成各个组成部分,就能立即看到它的好处:
const IndexPage = ({ posts }) => {
return (
<div className="container p-2 mx-auto lg:p-8 xl:p-0">
<h1 className="font-bold text-white text-11xl sm:text-12xl">Blog.</h1>
<Posts posts={posts} />
</div>
);
};
const Posts = ({ posts }) => (
<div className="flex flex-col mx-auto space-y-8 md:grid md:row-gap-6 md:col-gap-8 md:grid-cols-2">
{posts.map((postInfo, i) => {
const isFirstItem = i === 0;
return (
<PostCard key={postInfo.slug} isFirstItem={isFirstItem} {...postInfo} />
);
})}
</div>
);
const PostCard = ({
frontmatter: { title, description },
hasCoverImage,
isFirstItem,
}) => (
<div
className={clsx(
"w-full bg-white rounded-md font-body relative overflow-hidden",
isFirstItem && "md:col-span-2",
!isFirstItem && "md:col-span-1"
)}
>
<a>
{hasCoverImage && (
<img
className="object-contain w-full rounded-md md:object-cover"
src={src}
alt="cover image"
/>
)}
<header className="flex flex-col items-center p-12 space-y-4 text-center bg-white rounded-md">
<h1 className="max-w-6xl text-5xl font-bold">{title}</h1>
<p className="max-w-5xl text-2xl font-medium">{description}</p>
</header>
<div className="absolute top-0 right-0 px-6 py-8 mr-8 text-white bg-black">
<p className="block font-semibold">Read more</p>
</div>
</a>
</div>
);
这样不仅短期内更容易阅读,而且当我们再次查看时,也能更快地理解。此外,当代码规模增大,需要重用“阅读更多”之类的元素时,我们可以轻松创建组件并在不同元素之间共享。
ℹ 这种做法并非仅适用于 Tailwind,而是适用于所有情况。不过,Tailwind 的类名与具体实现无关,因此更容易判断何时应该这样做。而 CSS 或 CSS-in-JS 库则恰恰相反:它们要求创建的类或对象只能用于特定的上下文。
更高的生产力
毫不奇怪,编写 Tailwind 类比编写 CSS 或 CSS-in-JS 更快。在样式表中添加选择器声明时,我们需要输入属性和值,并在末尾添加冒号。好在 IDE 和文本编辑器会自动提供自动补全和格式化功能。但是,我们仍然需要手动输入选择器和部分声明才能触发自动补全。而 Tailwind 通过直接内联编写样式,从长远来看,大大提高了开发效率。
此外,Tailwind 还具有称为变体的功能,变体是可以添加到类中的前缀,代表特定的CSS 伪类、响应式断点或自定义插件。
以媒体查询为例。在普通的 CSS 中,我们会这样写:
/* Small (sm) */
@media (min-width: 640px) { /* ... */ }
/* Medium (md) */
@media (min-width: 768px) { /* ... */ }
/* Large (lg) */
@media (min-width: 1024px) { /* ... */ }
/* Extra Large (xl) */
@media (min-width: 1280px) { /* ... */ }
这段代码乍看之下似乎很容易读懂。但是,想象一下如果有数百个类,很快就会变得难以阅读,修改起来也会很慢。
现在,我们来看看 Tailwind 的:
<!-- Width of 16 by default, 32 on medium screens, and 48 on large screens -->
<img class="w-16 md:w-32 lg:w-48" src="...">
这里我们为每个断点定义了宽度。这样做的好处在于,我们将样式限定在元素内部。这样一来,以后移除或修改声明就容易得多,因为它不会与数百条规则冲突。
同样,伪类在 Tailwind 中比在 CSS 中更容易实现。这并非因为可维护性或可读性,而是因为编写起来更方便。添加前缀比添加一条全新的规则要快得多:
<input class="w-16 bg-orange-500 hover:bg-blue-500 focus:bg-red-500" />
<!--
Translates to:
.block {
width: 4rem;
background: #ed8936;
}
.block:hover {
background: #4299e1;
}
.block:focus {
background: #f56565;
}
-->
最后,我们还有自定义插件。它们允许您添加自定义前缀,从而增强类的行为。例如,它们允许开发者根据条件(例如暗黑模式)添加类,或者通过赋值来覆盖其他类!important。
<!-- Dark mode (Coming in v2) -->
<div className="bg-black dark:bg-white"/>
<!-- Important (https://github.com/chasegiunta/tailwindcss-important) -->
<div className="!bg-red"/>
一致性
除了能更快地编写样式之外,Tailwind 还预置了大量用于尺寸和颜色的类,这可以减少甚至完全消除实现设计系统的需要。这也意味着,对于经验丰富的开发者来说,使用 Tailwind 在现有项目中学习样式所需的学习曲线也更平缓。
💡 你可能会问它在各种浏览器上的兼容性如何。由于 Tailwind 是一个非常底层的框架,这意味着对旧版浏览器的支持取决于开发者,而不是像 Bootstrap 那样由浏览器实现。
高度定制化
Tailwind 为开发者提供了一个功能丰富的默认主题,非常适合基于 Bootstrap 的项目。不过,它也鼓励开发者通过配置tailwind.config.js文件来扩展或修改这些类。
Tailwind 的配置文件允许开发者直接修改 Tailwind 的设置。任何更改都会反映在最终的样式表中。它还允许添加自定义插件,这为第三方开发者开启了无限可能。
许多开发者在刚开始使用 Tailwind 时都会抱怨其样式过于单一。与纯 CSS 不同,Tailwind 似乎限制了样式的自定义,因为其默认类非常丰富,而且看起来像是遵循 Bootstrap 之类的规范。然而,由于 Tailwind 中的类通常只代表一个 CSS 声明,因此可以轻松地扩展、添加或删除它们。
另一方面,开发人员有时拒绝扩展此配置,因为他们不想深入研究文档,或者只是想为一个组件向一个类添加多个声明。
由于 Tailwind 使用 PostCSS 作为预处理器,这意味着它的功能可以扩展到配置文件之外的更广泛领域。大多数代码库都会有一个 CSS 文件,通过 PostCSS 导入 Tailwind 样式。
在这个文件中,既可以创建普通的 CSS 类,也可以使用 Tailwind 提供的丰富类库。例如:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn-blue {
@apply bg-blue-500 text-white font-bold py-2 px-4 rounded;
}
.btn-blue:hover {
@apply bg-blue-700;
}
}
总而言之,这意味着所有可以用 CSS 实现的功能都可以在 Tailwind 中实现!
绝佳的升级途径
Tailwind 的未来一片光明。它频繁推出更新,新增了适用于现代浏览器的 CSS 属性、新的前缀、便捷的 PostCSS 指令、新的类等等。
例如,在即将发布的 2.0 版本中,我们将为深色模式添加一个新的前缀。以前,添加深色模式需要一些变通方法,但现在只需dark:在应用于深色模式的类名前添加前缀,即可轻松实现原生添加。
<div class="bg-white text-black dark:bg-black dark:text-white dark:hover:text-gray-300"/>
此外,在 1.7 版本(目前为 1.9.6 版本)中,我们新增了文本背景裁剪功能,用于创建炫酷的渐变文本。这些只是众多显著提升开发体验和效率的更新中的几个例子。
缺点
Tailwind CSS 具有诸多优势,例如提升性能、一致性和生产力。然而,这并不意味着它完美适用于所有代码库或开发团队。
Tailwind 的一个缺点是可读性。起初可能并不明显,但当一个元素添加了相当多的类时,对于一些开发者来说,阅读起来可能会非常吃力。就我个人而言,我并没有遇到太多这方面的问题,而且我相信,只要组件抽象得当,这个问题就能轻松解决。尽管如此,许多开发者仍然会把这视为一个缺点。
Tailwind 的另一个弱点是创建复杂的动画。虽然它内置了一些简单的动画,并且可以通过配置添加更多动画,但要实现复杂的动画编排却非常困难。
要创建复杂的动画,需要为每个元素创建一个类名。这比直接使用 CSS 更费力。幸运的是,使用纯 CSS 的样式表、像 Framer Motion 这样的动画库,或者像 Styled Components 这样的 CSS-in-JS 库,都可以轻松解决这个问题。我认为这些方法在 Tailwind 框架下都是有效且值得推荐的,但是,有些开发者可能更喜欢直接使用纯 CSS 来实现动画,而不需要任何库。
另一个缺点是需要学习一套固定的命名规则。这有利有弊。一方面,固定的命名风格意味着一旦掌握,相关知识就能在不同项目间迁移,从而大幅提高效率。另一方面,必须投入时间学习所有细微差别及其运作方式,才能创造出有用的成果。
我认为,如果了解 CSS,学习 Tailwind 的命名规范会非常容易,因为它的类名与纯 CSS 非常相似。此外,他们提供的专属扩展程序还具备自动补全功能,方便开发者学习和记忆类名。
然而,有些团队仍然不愿意引入需要学习曲线的框架,尤其是在时间非常宝贵的情况下。
结论
Tailwind 作为底层 CSS 框架,已成为我首选的样式解决方案。它解决了我在尝试各种 CSS 方案时遇到的大部分问题。它提高了我的工作效率,减小了应用程序的打包体积,使我的代码更加一致,并改变了我的开发模式。
尽管 Tailwind 对我以及许多其他开发者来说都非常有效,但这并不意味着它是每个项目的完美解决方案。在考虑采用它之前,必须评估它对代码库的益处以及对团队的影响。
未来,我预见Tailwind将会发展壮大,并被应用于许多项目中,即使这些项目已经采用了现有的CSS解决方案。我已经看到许多现代项目开始这样做,例如Vercel电商模板。
归根结底,前端开发人员最终还是要使用 CSS。这意味着只要我们保证可维护性、可扩展性和性能,就可以融合不同的解决方案。考虑到这一点,Tailwind 凭借其诸多优势,无疑是一个值得考虑的解决方案。
想了解更多最新的网站开发内容,请在Twitter和Dev.to上关注我!感谢阅读!😎
你知道我有电子报吗?📬
如果您想在我发布新博客文章时收到通知,并每周收到很棒的资源以在 Web 开发方面保持领先地位,请访问https://jfelix.info/newsletter。
文章来源:https://dev.to/joserfelix/why-use-tailwind-css-for-your-next-project-39hp


