Why Does Shadcn use cn() Helper Functions?

twMerge() + clsx() = cn() ?為啥 Shadcn 要用這些輔助函式?

前言

如果翻閱 Shadcn🔗 元件集的元件會發現大量使用到一個輔助函式 cn 如下:

import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

它其實是兩套函式庫(tailwind-merge🔗clsx🔗)的組成,用於更方便建構元件當中 Tailwind 的樣式。為什麼會需要額外的庫和方法來建構元件樣式呢?

Tailwind Merge

如果將 Tailwind 和基於元件的 UI 渲染器(如 React 或 Vue)一起使用會遇到樣式蓋不過去的問題,不管最終樣式字串如何組合,Tailwind 會在生成 utility class 過程中把所有可能用到的 class 輸出成一份靜態 CSS,其中 定義順序攸關樣式的優先層級

function MyGenericInput(props) {
const className = `px-2 py-1 ${props.className || ''}`
return <input {...props} className={className} />
}
function MyOneOffInput(props) {
return (
<MyGenericInput
{...props}
className="p-3" // ← 傳入 class 想修改 padding 樣式但蓋不過去
/>
)
}

要解決這個問題有兩種可能方法:

  1. 新增元件參數用於切換內部樣式🔗
  2. 使用 Tailwind important 修飾符🔗

以上兩種方法都有各自的問題,所以才需要 Tailwind Merge 協助:

twMerge('p-5 p-2 p-4') // → 'p-4'

tailwind-merge 依靠 ~7 kB 的設定檔決定如何融合 Tailwind 相關的樣式字串,具體融合規則可以參考合併規則文件🔗

clsx

如果將 Tailwind 和基於元件的 UI 渲染器(如 React 或 Vue)一起使用,常常要依照條件動態切換 CSS class,原生寫法很快就變得又長又難讀:

<div
className={
{/* 留意 Class 間空格留白需組合正確 */}
"p-2 " +
(isActive ? "bg-blue-500" : "bg-gray-300") +
(isDisabled ? " opacity-50" : "")
}
/>

clsx 既可以簡化樣式開發流程,也能避免錯誤的 falsy 狀態被組合進去:

<div
className={clsx("p-2", {
"bg-blue-500": isActive,
"opacity-50": isDisabled,
})}
/>

規則如官方 Usage 文件🔗

// Strings (variadic)
clsx('foo', true && 'bar', 'baz');
//=> 'foo bar baz'
// Objects
clsx({ foo:true, bar:false, baz:isTrue() });
//=> 'foo baz'
// Objects (variadic)
clsx({ foo:true }, { bar:false }, null, { '--foobar':'hello' });
//=> 'foo --foobar'
// Arrays
clsx(['foo', 0, false, 'bar']);
//=> 'foo bar'
// Arrays (variadic)
clsx(['foo'], ['', 0, false, 'bar'], [['baz', [['hello'], 'there']]]);
//=> 'foo bar baz hello there'
// Kitchen sink (with nesting)
clsx('foo', [1 && 'bar', { baz:false, bat:null }, ['hello', ['world']]], 'cya');
//=> 'foo bar hello world cya'

總結

雖然以上套件都著重在非常簡單的小問題,但在大型 UI 專案可用於提升開發體驗。最後我對於這些縮寫有很大的疑問,寫完這篇文章才大致猜出縮寫的意涵:

  • twMerge = Tailwind Merge
  • clsx = Class Mix
  • cn = Class Name

延伸閱讀