tailwind+reactにおいてクラス名でコードを複雑化しない運用
tailwindといえば、
「CSSのクラス名を考えなくていい」
「マークアップの画面でそのままスタイリングできる」
「Purge CSSでCSSのバンドルサイズを小さくできる」
「でもクラス名がいっぱいになってコードが複雑化するんだよなぁ...」
という印象を持ってる人が多いイメージです。
実際、自分は前者のメリットを魅力的に感じて個人開発などで利用することも多いのですが、このクラス名が増えてしまうというデメリットはコンポーネント指向の現代のフロントエンドにおいては自分的にはあまり問題にはなっていないということをお伝えしたいと思います。
実例
今回作るcompomentはこちら
背景がピンク色のprimary
とborderがあるoutlined
の2つのバリエーションがあるButtonです。
とりあえずprimary
をサクッとつくる。
type Props = {
onClick: () => void
text: string
className?: string
}
const Button: React.FC<Props> = (props)=> {
const classes: string[] = []
props.className && classes.push(props.className)
return (
<button
onClick={props.onClick}
className={`w-64 h-12 rounded-3xl bg-primary text-white ${classes.join(' ')}`}
>
{props.text}
</button>
)
}
export default Button
ここでは最低限のpropsとしてtext、onClickとともにclassNameを渡すようにしています。
これは、marginなどのcomponentに持たせるべきではないstyleを当てるためのclassNameです。
このとき、props.classNameを直接buttonのclassNameに入れても良いのですが、後ほどバリエーションを追加するときにもclassを増やすのでclassesというstringの配列をつくり、そこにpushしたものを追加するようにしています。
また、bg-primaryはtailwind.config.jsで設定した色です。
次にoutlined
を追加します。
type Props = {
onClick: () => void
text: string
appearance?: 'outlined'
className?: string
}
const Button: React.FC<Props> = (props)=> {
const classes: string[] = []
props.className && classes.push(props.className)
props.appearance === 'outlined'
? classes.push('bg-white border border-black')
: classes.push('text-white bg-primary')
return (
<button
onClick={props.onClick}
className={`w-64 h-12 rounded-3xl ${classes.join(' ')}`}
>
{props.text}
</button>
)
}
export default Button
ここでは、バリエーションを指定するappearanceを任意のpropsとして追加し、primaryの時は何も指定せずdefaultのstyle、outlinedを指定したらoutlinedのstyleを表示するようにします。
これらのstyleは、props.appearanceとしてoutlinedが指定されているかどうかで条件分岐し、それぞれに合うclassNameを先ほどのclasses配列にpushするようにしています。
これを見てもらうと分かるように、tailwindだからといってclassNameが多くなりコードが読みにくくなるということも無く、むしろHTMLタグに直接書き込んでいるclassNameの数は少なくなりました。
また、それぞれのバリエーションのstyle変更があった際にどこを修正すれば良いのか分かりやすくなっています。
例えばsecondaryというバリエーションを増やしたいとなったときには、
props.appearance === 'outlined'
? classes.push('bg-white border border-black')
: props.appearance === 'secondary'
? classes.push('text-white bg-secondary')
: classes.push('text-white bg-primary')
ここに追加をするだけで良くなります。
text-whiteなどの共通のstyleが多くなってきた場合には、それらだけを追加する条件式を分けるのも良いかもしれません。
追加のstyleを当てたくなったことを想定し、hover時のstyleも追加してみます。
type Props = {
onClick: () => void
text: string
appearance?: 'outlined'
className?: string
}
const Button: React.FC<Props> = (props)=> {
const classes: string[] = []
const primaryHoverClasses = 'hover:bg-red-300 hover:hoge hover:fuga'
const outlinedHoverClasses= 'hover:bg-gray-300 hover:hoge hover:fuga'
props.className && classes.push(props.className)
props.appearance === 'outlined'
? classes.push(`bg-white border border-black ${outlinedHoverClasses}`)
: classes.push(`text-white bg-waveRed ${primaryHoverClasses}`)
return (
<button
onClick={props.onClick}
className={`w-64 h-12 rounded-3xl ${classes.join(' ')}`}
>
{props.text}
</button>
)
}
export default Button
擬似要素のstyleを追加するときにはどうしてもclassが多くなってしまうので、自分はこのように別でstringとして指定してそれを追加するようにしています。
@applyじゃダメなの?
同じようなものとしてtailwindには@applyという機能があります。
これによって、カスタムクラスを作成しHTMLタグ内のclassNameを汚さないようにすることもできるのですが、CSSのバンドルサイズも大きくなってしまいtailwindを利用するメリットがなくなってしまうので利用はしない方が良いと思っています。注意点
この運用方法のデメリットとして、vscodeの拡張機能であるTailwind CSS IntelliSenseによるclass名の補完が効かないという点があります。
そのため、tailwindのclass名まだ全然覚えられてないし、覚える気もないという方には向いていないかもしれません。
まとめ
あくまでも「tailwindがclassを複雑にさせるので使わない」という食わず嫌いの方向けにこんな感じで運用するとそんなこともないよという俺流設計なのでもっと良い運用方法などあれば教えてください。
Discussion