使用 Tailwind CSS 编写组件变体的不同方法
问题
传统上,当使用 tailwind css 编写组件变体时,我会使用简单的类映射将 prop 值映射到组件插槽:
type ttheme = "default" | "secondary"; interface icomponentslot { root: string; accent: string; } const theme_map: record<ttheme, icomponentslot> = { default: { root: "bg-red hover:background-pink", accent: "text-blue hover:text-green", }, secondary: { root: "bg-green hover:background-black", accent: "text-pink hover:text-white", } } <div :class="theme_map['default'].root"> <div :class="theme_map['default'].accent">/**/</div> </div>
这种方法的问题是保持所需的类相互一致,确保每个变体都具有所有必需的类,特别是在更复杂的组件中。在我们想要跨不同组件插槽共享样式(例如文本颜色)的组件中,需要我们单独更新每个插槽。
tailwind 的局限性
tailwind 通过扫描代码库和匹配字符串来生成实用程序类,这意味着虽然 tailwind 可以从任意值创建类,但我们无法在不创建安全列表的情况下动态启动它们。所以这行不通:
// .ts type ttheme = "default" | "secondary"; const colors: record<ttheme, string> = { default: "red", secondary: "blue", } // .html <div :class="`text-[${colors[default]}]`">
但是,我们可以通过利用 css 变量来模仿所需的行为,这是 tailwind 在其许多类的底层使用的东西。我们可以使用以下语法通过 tailwind 中的类设置变量: [--my-variable-key:--my-variable-value]
那么我们如何更新上面的代码示例以使用动态值?
// .ts type ttheme = "default" | "secondary"; const colors: record<ttheme, string> = { default: "[--text-color:red]", secondary: "[--text-color:blue]", } // .html <div :class="[ colors[default], 'text-[--text-color]' ]">
解决最初的问题
现在我们了解了 tailwind 的局限性,我们需要研究解决由类映射方法引起的初始问题的方法。我们可以从简化我们的类映射开始:
type ttheme = "default" | "secondary"; interface icomponentslot { root: string; accent: string; } const theme_map: record<ttheme, string> = { default: "[--backgound:red] [--hover__background:pink] [--text:blue] [--hover__text:green]", secondary: "[--backgound:green] [--hover__background:black] [--text:pink] [--hover__text:white]", } <div class="bg-[--background] hover:bg-[--hover__background]"> <div class="text-[--text] hover:text-[--hover__text">/**/</div> </div>
不幸的是,仅此并不能解决我们的问题,我们仍然无法确保我们已经设置了正确显示每个变体所需的所有类。那么我们如何才能更进一步呢?好吧,我们可以开始编写一个接口来强制我们设置指定的值:
interface icomponentthemevariables { backgound: string; hover__backgound: string; text: string; hover__text: string; } const theme_map: record<ttheme, icomponentthemevariables> = { default: { backgound: "[--backgound:red]", text: "[--hover__background:pink]", hover__background: "[--text:blue]", hover__text:"[--hover__text:green]", }, secondary: { backgound: "[--backgound:green]", text: "[--hover__background:black]", hover__background: "[--text:pink]", hover__text:"[--hover__text:white]", }, }
所以这可行,但是,仍然有一个问题,没有什么可以阻止我们混合字符串值。例如,我们可能不小心将关键背景设置为 [--text:blue]。
所以也许我们也应该输入我们的值。我们无法输入整个类,这将是维护的噩梦,那么如果我们输入颜色并编写一个辅助方法来生成 css 变量会怎么样:
type tcolor = "red" | "pink" | "blue" | "green" | "black" | "white"; interface icomponentthemevariables { backgound: tcolor; hover__backgound: tcolor; text: tcolor; hover__text: tcolor; } // example variablemap method at the end of the article const theme_map: record<ttheme, string> = { default: variablemap({ backgound: "red", text: "pink", hover__background: "blue", hover__text:"green", }), secondary: variablemap({ backgound: "green", text: "black", hover__background: "pink", hover__text:"white", }), }
好的,这太棒了,我们可以确保始终为组件的每个变体设置正确的变量。但是等等,我们刚刚遇到了 tailwind 发现的最初问题,我们不能只生成类,tailwind 不会选择它们。那么我们该如何解决这个问题呢?
js 中的 css 怎么样?
js 中的 css 似乎是显而易见的答案,只需生成一个类,该类创建具有正确变量的自定义类。但有一个障碍,javascript 在客户端上运行,这会导致“flash”,组件最初加载时没有设置变量,然后更新才能正确显示。
js 库中的 css 如何处理这个问题?
像 emotion 这样的库通过插入有关组件的内联样式标签来处理这个问题:
<body> <div> <style data-emotion-css="21cs4">.css-21cs4 { font-size: 12 }</style> <div class="css-21cs4">text</div> </div> </body>
这对我来说不是正确的方法。
那么我们如何解决这个问题呢?
我正在使用 vue,这让我走上了 css 中 v-bind 的道路,这是 vue 中将 javascript 绑定为 css 值的功能。过去我只很少使用这个功能,并且从未深入研究过它的用途。 css 中的 v-bind 只是在相关元素上设置内联样式。
这让我想起了几个月前我从 tailwind css 的创建者 adam wathan 那里看到的一条推文:
那么这对我们有什么帮助呢?好吧,虽然我们无法动态生成 tailwind 类,但我们可以动态生成内联样式并使用 tailwind 类中的这些内联样式。那会是什么样子?
type tcolor = "red" | "pink" | "blue" | "green" | "black" | "white"; interface icomponentthemevariables { backgound: tcolor; hover__backgound: tcolor; text: tcolor; hover__text: tcolor; } // example variablemap method at the end of the article const theme_map: record<ttheme, string> = { default: variablemap({ backgound: "red", text: "pink", hover__background: "blue", hover__text: "green", }), secondary: variablemap({ backgound: "green", text: "black", hover__background: "pink", hover__text: "white", }), } <div class="bg-[--background] hover:bg-[--hover__background]" :style="theme_map['default']" > <div class="text-[--text] hover:text-[--hover__text">/**/</div> </div> /* output: <div class="bg-[--background] hover:bg-[--hover__background]" style="--background: red; --text: pink; --hover__background: blue; --hover__text: green;" > <div class="text-[--text] hover:text-[--hover__text">...</div> </div> */
结论
通过结合 typescript、css 变量和内联样式的功能,我们能够确保在使用 tailwind css 时,我们组件的每个变体都会设置每个选项并具有正确的类型。
这是一种实验性方法,我相信会有一些强烈的意见。我确信这是最好的方法吗?现阶段,我不确定,但我认为它有腿。
如果您觉得这篇文章有趣或有用,请在 bluesky(我在这里最活跃)、medium、dev 和/或 twitter 上关注我。
示例:变量映射
// variableMap example export const variableMap = <T extends Record<string, string>>( map: T ): string => { const styles: string[] = []; Object.entries(map).forEach(([key, value]) => { const wrappedValue = value.startsWith("--") ? `var(${value})` : value; const variableClass = `--${key}: ${wrappedValue};`; styles.push(variableClass); }); return styles.join(" "); };
以上就是使用 Tailwind CSS 编写组件变体的不同方法的详细内容,更多请关注硕下网其它相关文章!