一文读懂Tailwind CSS 核心理念(附开发实践)

7,298 阅读10分钟

作者:张泽慧

目录

  • 原子/功能类优先
    • 从Semantic CSS说起
    • 迎接Atomic/Utility-First CSS
  • 丰富的功能类
  • 生产优化
    • 配置Purge
    • 即时模式JIT
  • 开发实践
    • 使用Playground
    • 在React中使用
    • 在vs code中使用

原子/功能类优先

从Semantic CSS说起

要完成一个名片的样式开发,我们通常会这样写:在html或jsx结构中添加赋有语义化的class类名,随后在css中写入对应类的样式。

这是最常见、最常规的写法,被称作Semantic CSS(语义化CSS)规范。在这种规范下,我们追求关注点分离,让结构与样式各司其职,使结构更具语义化。但这样一来,在很多场景下也面临着许多问题,比如:

  • 每一个标签都有一个专有的class,但就算是给标签添加一个小小的样式时,我们都得绞尽脑汁想出一个类名,既要求有语义化、符合代码规范,还要和与它作用相似的标签类名有区分;

  • 每个类中往往会有很多个样式规则,在结构的语义、样式完全相同时才能做到真正的复用,若存在一点差异,就难以实现样式复用;

  • 可以通过在html或jsx中删除类名的方式去掉相应的样式,但此时我们无法轻易的删除该类下的css样式,因为我们无法保证该类在别处也使用过,而在修改某个类中的样式时也存在同样的问题;

  • 如果将html或jsx结构做迁移,我们还要将相应css同时迁移,即使这样,迁移后的样式也可能会根据上下文环境变得错乱;

  • ......

image-20210828193227650.png

Bootstrap可以视为Semantic CSS(语义化css)为基础的组件化框架。以按钮组件为例,它预定义了各种按钮样式,将不同样式封装成不同的类组件,并且每个样式都有自己的语义目的,比如success成功就为绿色,danger危险就代表红色......在使用时,我们不用再关注繁琐的css,引用相应的类名即可。但这样组件化的css,若想实现自定义的样式,可能需要更多的精力去做样式覆盖。

image-20210829115756285.png

迎接Atomic/Utility-First CSS

Atomic/Utility-First CSS与 Semantic CSS 相对,Utility-First CSS(功能类优先css)不像Semantic CSS(语义化css)那样将组件样式放在一个类中,而是为我们提供一个由不同功能类组成的工具箱,我们可以将它们混合在一起应用在html元素上。对于Atomic CSS(原子css)来说,在物理世界中, 原子构成一般物质的最小单位,而在css世界中,则是一个类中只有唯一的css规则。

<style>
/* Atomic/Utility-First CSS */
.bg-black {
    background-color: black;
}

.text-white {
    color: white;
}

.rounded-8 {
    border-radius: 8px;
}

/* Utility-First CSS,非Atomic CSS */
.py-8 {
    padding-top: 8px;
    padding-bottom: 8px;
}

.px-5 {
    padding-left: 20px;
    padding-right: 20px;
}
</style>

<button class="bg-black text-white rounded-8 py-2 px-5">button</button>

本文主人公 Tailwind CSS ,用法与Bootstrap类似,都是通过类名来引用样式。但最大的区别,也是Tailwind CSS的核心,即它是一套以Atomic/Utility-First CSS为基础CSS框架。同样是名片的样式开发,Tailwind CSS 这样做:

image-20210829114145171.png

由此看来,基于Atomic/Utility-First CSS规范的Tailwind CSS框架的优点肉眼可见:

  • 不用花时间想可恶的类名;

  • 功能越简单的类,复用率越高;

  • 结构与样式紧密耦合,不用来回切换html与css,更专注于html;

  • 修改某个样式,不影响其他结构样式;

  • 保证全局样式统一,不存在全局样式污染问题;

  • 结构迁移时,对应的样式依然存在;

  • 丰富的功能类,覆盖大多数的开发场景;

  • ......

但也可肉眼可见的发现一些问题:

  • 引用的类也太多了吧。但在Tailwind也中可以通过@apply提取组件样式;

  • 类名堆砌,都不能通过语义化类名得知元素的作用。但在组件化开发的今天,相对于组件中某个标签元素的作用,更重要的是了解组件本身的作用;

  • Tailwind功能类超多,体积并不小喔。但可以通过配置purge,删除没用的东西;

  • ......

丰富的功能类

  • 尺寸:在Tailwind中,1对应0.25rem,py-2则代表y轴上padding的大小,即padding-top及padding-bottom为0.5rem;

  • 字号:使用sm/base/lg等表标识字号及行高大小,如text-sm表示字号为14px,行高为20px;

  • 颜色:通过50-900的数字来表示某种颜色的深浅程度,如text-gray-500,与色卡中#6B7280对应;

image-20210829151853340.png

  • 响应式设计

Tailwind根据常见的屏幕分辨率,设置默认的5个断点,分别有:sm(640px)、md(768px)、lg(1024px)、xl(1280px)、2xl(1536px)。

<div class="h-12 border sm:w-1/2 md:w-48"></div>

sm:w-1/2、 md:w-48相当于:

@media (min-width: 640px) {
    .sm\:w-1\/2 {
        width: 50%;
    }
}

@media (min-width: 768px) {
    .md\:w-48 {
        width: 12rem/* 192px */;
    }
}
  • 悬浮等状态

Tailwind使用功能类为各种状态的元素设置样式,包含Hover、Active、Focus、Disabled、 First-child、 Last-child等状态。

/* hover */
<button class="bg-red-500 hover:bg-red-700 ...">
    Hover me
</button>

/* group-hover */
<div class="hover:bg-gray-800 group">
    <h1 class="group-hover:text-white">title</h1>
    <span class="text-gray-500 group-hover:text-white">i am content</span>
</div>

/* 与响应式前缀结合使用 */
<button class="... hover:bg-green-500 sm:hover:bg-blue-500">
    Hover me
</button>
  • 深色模式

Tailwind支持深色模式,仅需添加dark变体即可。使用该模式时需配置,默认不开启,以减小文件体积。

<div class="bg-white dark:bg-gray-800">
    <p class="text-gray-900 dark:text-white">Dark mode is here!</p>
</div>
  • 自定义配置

Tailwind可解决大部分的开发场景,此外,它还可以通过tailwind.config.js文件去覆盖原有配置,或增加自定义配置。

// tailwind.config.js
module.exports = {
    purge: [],
    darkMode: false, //默认不开启
    theme: { // 主题
        colors: {},
        fontFamily: {},
        extend: {},
    },
    variants: { //变体
        extend: {},
    },
    plugins: [], // 插件
    perset: [], // 预设
    prefix: '', // 前缀
    important: true, // 启用!important
    ...
}
  • 更多Tailwind CSS精心设计的功能类,如弹性布局、背景、边框、过渡和动画等,在Tailwind CSS文档中拥有详尽的说明。

生产优化

相较于其他框架,Tailwind丰富的功能类让它有了一个不小的体积基数,但也正是因为丰富的功能类拥有单一的功能,促使它大大提高了复用率,新增的css就越少,并且页面越多,最终在打包体积上的收益就越大。此外,Tailwind还对生产作出优化,如配置purge以及其2.1版本上线的Just In Time即时模式。

image-20210829222921789.png

配置Purge

Tailwind拥有成千上万的功能类,但不是所有都是必须的,我们可以通过在配置文件中设定purge选项,tree-shake 删除未使用的样式,优化最终构建大小。

  • enabled:是否启用,开发时可设为false;

  • content:检查的文件路径;

  • layers:清理特定层中的所有样式

module.exports = {
    purge: {
        enabled: true,
        content:['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
        layers: ['components', 'utilities'],
        ...
    },
    ...
}

该功能的底层使用的是 PurgeCSS 库,它的清扫思路是:

  1. 扫描我们提供内容文件,针对特定文件类型,根据提取器提取出使用到的各种属性;
  2. 解析我们依赖的css文件,识别所有存在的选择器,生成AST抽象语法树;
  3. 提取到的信息与AST抽象语法树进行查找匹配,将未使用到的CSS规则从语法树中删掉;
  4. 最后在NODE_ENV为production的情况下,构建生成的样式表只会留下用到的样式。

在Tailwind中,我们可以不用去设置提取器,Tailwind已经帮我们做好了一切,我们只需要配置需要清扫的文件路径即可。此外,PurgeCSS在扫描文件时,查找与正则表达式相匹配的任何字符串,所以我们在动态判断类名时,需写完整的类名,而不是让类名做动态的拼接,这样保证PurgeCSS可以找到使用过的类,避免意外删除掉重要的样式。

`/[^<>"'`\s]*[^<>"'`\s:]/g`

即时模式JIT

虽然PurgeCSS帮助我们在生产环境中清除掉了未使用到的类,但是在开发环境中,构建工具仍然生成所有的类。这样一来,Tailwind 生成的 CSS 包变得越来越大,应用程序的构建变得越来越慢,并且 Dev Tools由于要摄取的 CSS 量而变得缓慢,造成极高时间与极差的开发体验,而tailwind2.1版本中JIT模式为我们很好的解决了这个问题。

JIT与AOT的区别:

  • JIT(Just In Time):运行时,在浏览器中编译应用程序;
  • AOT(Ahead of Time):构建时,在服务器上编译应用程序。

JIT编译器不是在初始构建期间去提前创建所有内容,它会按需生成模版,监视我们的html文件,只会获得我们真正需要的css类。css体积很小,构建时间更短,Dev Tools响应更快。并且在以前,默认情况下禁用了许多Tailwind变体,因为它们会导致生成数兆字节的 css,但因为 JIT 会“按需”生成样式,默认情况下这些变体都是启用的,这意味着所有这些变体都可以零配置直接使用。

// tailwind.config.js
module.exports = {
    mode: 'jit',
    purge: [
    // ...
    ],
    theme: {
    // ...
    }
// ...
}

同时,JIT“捆绑”了一些更灵活的写法:

<!-- 支持任意值 -->
<button class="bg-[#1da1f1]">button</button>

<!-- 内置重要修饰符 -->
<p class="font-bold !font-medium">
This will be medium even though bold comes later in the CSS.
</p>

<!-- 可堆叠变体 -->
<button class="md:dark:disabled:focus:hover:bg-gray-400">

<!-- 颜色/不透明度简写 -->
<div class="bg-red-500/25">
and so on...

开发实践

使用Playground

在使用开发之前,可以先使用Tailwind官网提供的在线运行环境 Tailwind Play 尝尝鲜。在这里,可以即时编译你编写的代码、配置CSS和Config文件、给出Tailwind语法提示,鼠标悬浮在类名上会显示css源代码,完成后还能向别人分享你的作品链接。

image-20210830232845805.png

在React中使用

在react项目中,安装tailwind、postcss以及autoprefixer,

  • Tailwimd CSS 本质是 postcss 的一个插件(react自身安装了旧版本的postcss,故在此需指定安装对应的旧版本);
  • 通过autoprefixer自动添加浏览器引擎前缀。
npm install tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

配置Craco

CRA脚手架将webpack配置封装到react-scripts的package中,默认不可见。当脚手架配置不满足需求时,可通过npm run eject命令暴露配置,交由我们自己管理。但eject操作一旦完成,就无法还原、无法升级react-scripts。而Craco工具可帮助我们不执行eject,即可进行webpack自定义配置、统一集中化管理所有的配置。

  1. 在react项目中安装Craco工具
npm install @craco/craco
  1. 在package.json文件中改写scripts部分
"scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "react-scripts eject"
},
  1. 在根目录手动创建一个craco.config.js文件,并且添加tailwind和autoprefix作为PostCSS插件
module.exports = {
    style: {
        postcss: {
            plugins: [
                require('tailwindcss'),
                require('autoprefixer'),
            ],
        },
    },
}

引入Tailwind CSS

执行命令初始化tailwind,初始化后在根目录下生成tailwind.config.js文件,可进行purge等自定义配置。

npx tailwind init

src/index.css文件中,清空原有样式,使用@tailwind命令引入tailwind三大件

  • **base(基础):**tailwind的基本样式,即重置浏览器样式;
  • **components(组件):**tailwind定义的组件类;
  • **utilities(功能):**tailwind的实用程序类,是tailwind核心。
@tailwind base;
@tailwind components;
@tailwind utilities;

或者,直接在js文件中导入 tailwindcss / tailwind.css

import "tailwindcss/tailwind.css"

在vs code中使用

在vs code中,安装 Tailwind CSS IntelliSense 插件,即可拥有智能代码提示:自动补全语法检查悬停预览语法高亮显示

此外,我们还可以根据 速查表 帮助我们减少记忆负担。

intellisense.0bd2cbf8c277e6c1330e345ab3cd7684.png

参考链接