傳統上,當使用 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 可以從任意值建立類,但我們無法在不建立安全清單的情況下動態啟動它們。所以行不通:
// .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> <p>Unfortunately, this alone doesn't solve our problem, we still can't ensure we've set all of the classes we need to display each variant correctly. So how can we take this a step further? Well, we could begin writing an interface to force us to set specified values:<br> </p> <pre class="brush:php;toolbar:false">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 似乎是顯而易見的答案,只需產生一個類,該類創建具有正確變數的自訂類別。但有一個障礙,Javascript 在客戶端上運行,這會導致“Flash”,組件最初加載時沒有設定變量,然後更新才能正確顯示。
像 Emotion 這樣的函式庫透過插入有關元件的內嵌樣式標籤來處理這個問題:
<body> <div> <style data-emotion-css="21cs4">.css-21cs4 { font-size: 12 }</style> <div> <p>This didn't feel like the right approach to me.</p> <h3> So how do we solve this? </h3> <p>I was working with Vue, this led me down the path of v-bind in CSS, a feature in Vue to bind Javascript as CSS values. I'd only used this feature sparingly in the past and never taken a deep dive into what it's doing. v-bind in CSS simply sets an inline style on the relevant element.</p> <p>This jogged my memory about a Tweet I saw from the creator of Tailwind CSS, Adam Wathan a couple of months previously:</p> <p>So how does this help us? Well, while we can't dynamically generate Tailwind classes, we can dynamically generate inline styles and consume those inline styles from our Tailwind classes. So what would that look like?<br> </p> <pre class="brush:php;toolbar:false">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 > <h2> Conclusion </h2> <p>By combining the powers of Typescript, CSS variables, and inline styles we were able to ensure that while using Tailwind CSS, each variant of our component would have every option set and with the correct type.</p> <p>This is an experimental approach on which I'm sure there will be some strong opinions. Am I convinced this is the best approach? At this stage, I'm not sure, but I think it has legs.</p> <p>If you've found this article interesting or useful, please follow me on Bluesky (I'm most active here), Medium, Dev and/ or Twitter.</p> <h3> Example: variableMap </h3> <pre class="brush:php;toolbar:false">// 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 編寫組件變體的不同方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!