在TailwindCss中动态构建类名

73

我目前正在使用TailwindCss构建我的下一个项目的组件库,当我在Button组件上工作时遇到了一个小问题。

我传递了一个像'primary''secondary'这样的属性,该属性与我在tailwind.config.js中指定的颜色匹配,然后我想使用模板文字(Template literals)将其分配给按钮组件,如下所示:bg-${color}-500

<button
    className={`
    w-40 rounded-lg p-3 m-2 font-bold transition-all duration-100 border-2 active:scale-[0.98]
    bg-${color}-500 `}
    onClick={onClick}
    type="button"
    tabIndex={0}
  >
    {children}
</button>

类名在浏览器中显示得很好,DOM中显示为bg-primary-500,但未在应用的样式选项卡中显示。

enter image description here

主题配置如下:
  theme: {
    extend: {
      colors: {
        primary: {
          500: '#B76B3F',
        },
        secondary: {
          500: '#344055',
        },
      },
    },
  },

但是它不应用任何样式。如果我手动添加bg-primary-500,它就能正常工作。

我只是想知道这是否是因为JIT编译器无法捕捉到动态类名,还是我做错了什么(或者这不是使用tailWind的方法)。

欢迎提供任何帮助,先谢谢了!


能否提供一个最小可重现的示例?我已经尝试复制了这个错误,但对我来说它可以按照我所期望的工作。 - Jonas Hendel
我真的没有时间去创建一个沙盒环境来重现它。但是下面的rokob给了我答案。谢谢你的时间! - Wesley Janse
最新的Next 13.4请参考https://dev59.com/91EG5IYBdhLWcg3wPnuM#76660733。 - krishnaacharyaa
13个回答

38

在发现这种工作方式不被推荐且JIT不支持后(感谢慷慨的评论者),我改变了方法,采用了更基于“config”的方法。

基本上,我定义一个包含不同属性的基本配置const,然后将其应用于组件。 这需要更多的维护工作,但可以实现目标。

这是一个示例配置(当前没有类型),需要进行一些优化重构,但你会明白的。

const buttonConfig = {
  // Colors
  primary: {
    bgColor: 'bg-primary-500',
    color: 'text-white',
    outline:
      'border-primary-500 text-primary-500 bg-opacity-0 hover:bg-opacity-10',
  },
  secondary: {
    bgColor: 'bg-secondary-500',
    color: 'text-white',
    outline:
      'border-secondary-500 text-secondary-500 bg-opacity-0 hover:bg-opacity-10',
  },

  // Sizes
  small: 'px-3 py-2',
  medium: 'px-4 py-2',
  large: 'px-5 py-2',
};

然后我就这样应用样式:

  <motion.button
    whileTap={{ scale: 0.98 }}
    className={`
    rounded-lg font-bold transition-all duration-100 border-2 focus:outline-none
    ${buttonConfig[size]}
    ${outlined && buttonConfig[color].outline}
    ${buttonConfig[color].bgColor} ${buttonConfig[color].color}`}
    onClick={onClick}
    type="button"
    tabIndex={0}
  >
    {children}
  </motion.button>

10
甚至可以将Tailwind的类(您想要在动态使用中包含的类)作为注释添加到代码的某个位置。 只要在每个颜色想要包含的地方提到bg-accent-100 text-accent-500,就可以使用bg-${color}-100 text-${color}-500 - Patrick Hellebrand
@morganney 这是一个小型个人项目,我喜欢了解所有前端框架的工作原理和注意事项。我仍然认为你可以使用TailWind构建非常灵活和可扩展的前端,这只取决于你如何设置和决定使用它。正如下面所述,自最新更新以来,这不再是一个问题。 - Wesley Janse
@morganney 这不是一个重大的问题。 - forresthopkinsa
@forresthopkinsa 如果您尝试生成大量动态类字符串,这将是一个重大问题。您的捆绑包大小将会非常庞大,因为您最终将安全列出所有tailwindcss类的大部分。 - lmonninger
@WesleyJanse 我刚刚又询问了一些人并进行了一些实验。看起来,如果您处于SSR上下文中,则可以在呈现的HTML上使用tailwind插件运行postcss。而且,性能损失似乎不显著(<100ms)。这使您可以使用动态类名,只要(1)您使用适当的SSR框架(如Nuxt),并且(1.1)在浏览器运行时更改类名的JavaScript未包含在捆绑包中。 - lmonninger

25

不建议使用这种编写Tailwind CSS类的方式。即使JIT模式也不支持它。引用Tailwind CSS文档的话说:“Tailwind不包含任何客户端运行时,因此类名需要在构建时静态提取,不能依赖于任何在客户端上更改的任意动态值”。


Jup 有道理,很遗憾但我猜我得尝试另一种方法。谢谢你的回答! - Wesley Janse

12

编辑: 2022年更好的实现方法 - https://dev59.com/91EG5IYBdhLWcg3wPnuM#73057959

Tailwind CSS不支持动态类名(请参见此处)。但是,仍然有一种方法可以实现这一点。在我的Vue3应用程序中,我需要使用动态构建类名。请参见下面的代码示例。

在构建过程中,Tailwind会扫描您的应用程序中正在使用的类,并自动清除所有其他类(请参见此处)。但是,您可以使用safelist功能来排除要从清除中排除的类——也就是说,它们将始终包含在生产环境中。

我在下面创建了一个样本代码,在生产中使用它来组合每个颜色和每个颜色的阴影 (colorValues数组)。

这个类名的数组被传递到safelist。请注意,通过实现此功能,您将向生产环境发送更多的CSS数据,并且可能永远不会使用某些CSS类。

const colors = require('./node_modules/tailwindcss/colors');
const colorSaveList = [];
const extendedColors = {};
const colorValues = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];

for (const key in colors) {
  

  // To avoid tailWind "Color deprecated" warning
  if (!['lightBlue', 'warmGray', 'trueGray', 'coolGray',  'blueGray'].includes(key))
  {
    extendedColors[key] = colors[key];
    for(const colorValue in colorValues) {
       colorSaveList.push(`text-${key}-${colorValue}`);
       colorSaveList.push(`bg-${key}-${colorValue}`);
    }
  }
}


module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{vue,js,ts,jsx,tsx}"
  ],
  safelist: colorSaveList,
  theme: {
   extend: {
      colors: extendedColors
   }
  },
  plugins: [
    require('tailwind-scrollbar'),
  ]

}

1
我想知道safelist是否比在辅助函数中自动生成所有可能的组合,然后只需调用该函数动态查找类名更好。我就这个话题写了这个要点:https://gist.github.com/tahesse/345830247456980d1c8ac6e53a2dd879 - tahesse
1
我喜欢你的解决方案。它可以用作其他开发人员的外部脚本或“插件”。我的代码更像是一个热修复或快速修复。正如您在gist中提到的,您必须考虑可能不使用的类。但是,您仍然可以修改我的解决方案,仅使用您想要的类并将其列入safeList。我认为我的解决方案非常简单,因为您利用了TailWindCSS中已经包含的功能。所以回答你的问题。我认为使用我的解决方案比通过node fs直接向文件编写CSS类更“清洁”,但这只是我的意见。 - A. Mrózek
我认为这已经在2022年不再起作用了。我在下面的帖子中添加了更新版本。 - mbdavis
它在2022年仍然能够正常工作。我们在生产代码库中使用它。我已将您的实现链接添加到我的原始回答中,因为我发现它更好。感谢您调整我的代码。 :) - A. Mrózek

12

可能有些晚了,但对于仍在查看此主题的人们,在这里简单解释一下:

问题的原因是:只有在为动态类名配置安全列表后,才能使用它,否则无效。

但是,只要使用完整的 Tailwind 类名,则动态类可以正常工作。

详细说明请参见此处

以下代码将无效:

<div class="text-{{ error ? 'red' : 'green' }}-600"></div>

但是这个有效

<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>

其状态;

只要您在代码中始终使用完整的类名,Tailwind每次都会完美地生成您的CSS。

更详细的解释;

Tailwind将扫描tailwind.config.js文件中指定的module.exports.content中的所有文件,并查找Tailwind类。 它甚至不必在class属性中添加,甚至可以添加到注释行中,只要该文件中存在完整的类名且该类名未动态构建; Tailwind将提取该类的样式,

因此,在您的情况下,您只需要在其中一个文件中为所有可能的动态类值放入完整的类名,例如:

<button className={ color === 'primary' ? 'bg-primary-500' : 'bg-secondary-500'}>
    {children}
</button>

或者我更喜欢的方法

<!-- bg-primary-500 bg-secondary-500 -->
<button className={`bg-${color}-500 `}>
    {children}
</button>

以下是另一个例子,虽然它是Vue,但对于任何JS框架来说,思路都是相同的。

<template>
    <div :class="`bg-${color}-100 border-${color}-500 text-${color}-700 border-l-4 p-4`" role="alert">
        test
    </div>
</template>
<script>
    /* all supported classes for color props 
    bg-red-100 border-red-500 text-red-700
    bg-orange-100 border-orange-500 text-orange-700
    bg-green-100 border-green-500 text-green-700
    bg-blue-100 border-blue-500 text-blue-700
    */
    export default {
        name: 'Alert',
        props: {
            color: {type: String, default: 'red'}
        }
    }
</script>

结果将会是这样的

<Alert color="red"></Alert> <!-- this will have color related styling-->
<Alert color="orange"></Alert> <!-- this will have color related styling-->
<Alert color="green"></Alert> <!-- this will have color related styling-->
<Alert color="blue"></Alert> <!-- this will have color related styling-->
<Alert color="purple"></Alert> <!-- this will NOT have color related styling as the generated classes are not pre-specified inside the file -->

7
如果有人在2022年看到这篇文章 - 我采用了A. Mrózek的答案,并进行了一些调整,以避免已弃用警告和迭代非对象调色板的问题。
const tailwindColors = require("./node_modules/tailwindcss/colors")
const colorSafeList = []

// Skip these to avoid a load of deprecated warnings when tailwind starts up
const deprecated = ["lightBlue", "warmGray", "trueGray", "coolGray", "blueGray"]

for (const colorName in tailwindColors) {
  if (deprecated.includes(colorName)) {
    continue
  }

  const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]

  const pallette = tailwindColors[colorName]

  if (typeof pallette === "object") {
    shades.forEach((shade) => {
      if (shade in pallette) {
        colorSafeList.push(`text-${colorName}-${shade}`)
        colorSafeList.push(`bg-${colorName}-${shade}`)
      }
    })
  }
}

// tailwind.config.js
module.exports = {
  safelist: colorSafeList,
  content: ["{pages,app}/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {
      colors: tailwindColors,
    },
  },
  plugins: [],
}

2
如果我错了,请纠正我。但是,在for循环中检查colorName是否包含在废弃数组中时,你应该使用continue而不是break,否则在找到废弃颜色时,会打断循环,并且不会再添加更多的颜色到tailwindColors数组中。 - A. Mrózek
@A.Mrózek,你说得完全正确。发现得好,我会更新的! - mbdavis

6
对于使用tailwind的JIT模式或v3版本使用JIT,您需要确保将导出对象样式的文件包含在tailwind.config.js中的 content选项中,例如:
 content: ["./src/styles/**/*.{html,js}"], 

我曾经遇到过同样的问题,实际上我缺少的是:我的实用类从存储在自定义文件夹中的本地数据对象导入,因此Tailwind无法引用它们。我只需要使用我的文件完成“content”数组即可。 - Stéphane Changarnier
谢谢你让我知道,我还没有研究过Tailwind V3。所以我现在不知道什么是更好的方法。 - Wesley Janse
这不仅仅是关于Tailwind v3,而是关于JIT模式。 - Blessing

4

在 tailwind 中使用动态类是推荐的吗?

不推荐

通常不建议在 tailwind-css 中使用 dynamic classes,因为 tailwind 使用了 tree-shaking 技术,即在源文件中未声明的任何类都不会生成到输出文件中。因此,始终建议使用 full class names

根据 Tailwind-css 文档

如果您使用字符串插值或将部分类名连接在一起,则 Tailwind 将无法找到它们,因此也不会生成相应的 CSS。

难道没有解决方法吗?

作为最后的手段,Tailwind 提供了 安全列出类

安全列表是最后的选择,只应在无法扫描某些内容以获取类名的情况下使用。这些情况很少见,您几乎永远不需要使用此功能。
在您的示例中,您想要有100、500和700个色调。您可以使用正则表达式使用模式包括所需的所有颜色,并相应地指定色调。
注意:您也可以强制Tailwind创建变量:
在tailwind.config.js中
module.exports = {
  content: [
    './pages/**/*.{html,js}',
    './components/**/*.{html,js}',
  ],
  safelist: [
    {
      pattern: /bg-(red|green|blue|orange)-(100|500|700)/, // You can display all the colors that you need
      variants: ['lg', 'hover', 'focus', 'lg:hover'],      // Optional
    },
  ],
  // ...
}
额外内容:如何自动化以在中包含所有Tailwind颜色
const tailwindColors = require("./node_modules/tailwindcss/colors")
const colorSafeList = []

// Skip these to avoid a load of deprecated warnings when tailwind starts up
const deprecated = ["lightBlue", "warmGray", "trueGray", "coolGray", "blueGray"]

for (const colorName in tailwindColors) {
  if (deprecated.includes(colorName)) {
    continue
  }

  // Define all of your desired shades
  const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]

  const pallette = tailwindColors[colorName]

  if (typeof pallette === "object") {
    shades.forEach((shade) => {
      if (shade in pallette) {
       // colorSafeList.push(`text-${colorName}-${shade}`)  <-- You can add different colored text as well 
        colorSafeList.push(`bg-${colorName}-${shade}`)
      }
    })
  }
}

// tailwind.config.js
module.exports = {
  safelist: colorSafeList,                      // <-- add the safelist here
  content: ["{pages,app}/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {
      colors: tailwindColors,
    },
  },
  plugins: [],
}

注意:我已经尝试以所有可能的方式总结答案,希望能有所帮助。


3
2023年更新
TailwindUI库(由Tailwind创建)使用了一个非常方便的小助手函数,您可以在应用程序中定义它(无需任何依赖)。
export function classNames(...classes) {
  return classes.filter(Boolean).join(' ')
}

然后在任何文件中,您可以简单地将类的字符串相加:

className={
  classNames(
    // This could be a string representing classes passed into the component
    "flex flex-col",
    primary ? "bg-teal-600" : "bg-white",
    active ? 'bg-slate-100' : '',
  )
}

2
现在可以使用 safeListing 和 tailwind-safelist-generator 包来“预生成”我们的动态样式。
使用 tailwind-safelist-generator,您可以基于一组模式为主题生成 safelist.txt 文件。
Tailwind 的 JIT 模式会扫描您的代码库以查找类名,并根据其找到的内容生成 CSS。如果类名没有明确列出,例如 text-${error ? 'red' : 'green'}-500,则 Tailwind 将无法发现它。为确保这些实用程序被生成,您可以维护一个文件,明确列出它们,例如项目根目录中的 safelist.txt 文件。

2
在Nextjs 13.4+中使用tailwind,您可以同时使用clsxtwMerge的组合。 clsx:用于基于对象的className。
const [pending, setPending] = useState(false);
<button className={ 
    "px-4", 
    {
       "bg-blue-500":pending, // if pending is true apply blue background
    }
  }
/>

twMerge: 用于有效合并 tailwind 类的函数,

<button className={twMerge(
    "bg-blue-500 px-4",
    "bg-black"
   )}
/>

实用函数 /lib/utils.ts
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}


使用它作为 page.tsx

import { cn } from "@/lib/utils";
export default function Component(){
   return <div className={cn(
              "bg-black font-sans",
              "bg-white h-full", 
               {
                 "px-5":pending, // if pending is true apply padding
               }
           )}
         />
}

进一步参考:cn() - 每个 Tailwind 程序员都需要它 (clsx + twMerge)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接