はじめに
Reactコンポーネントを書いていると、こんなコードに出会ったことはありませんか。
<button
className={
'btn ' +
(isActive ? 'btn-active ' : '') +
(isDisabled ? 'btn-disabled ' : '') +
(variant === 'primary' ? 'btn-primary' : 'btn-secondary')
}
>
送信
</button>
条件が増えるたびに文字列連結が複雑になり、余分な空白やタイポでスタイルが当たらない、といったバグに悩まされた経験がある方も多いはずです。この「className組み立て問題」をシンプルに解決してくれるのが、今回紹介するclsxです。
とはいえ、読むより触った方が早いと思います。チェックボックスで条件を切り替えながらclsx()の出力がその場でどう変わるか確認できるサンプルを用意したので、先に挙動を見たい方はこちらからどうぞ。
clsxとは
clsxは、条件付きでclassName文字列を構築するための極小ユーティリティです。人気ライブラリclassnamesの高速・軽量な代替品として作られており、gzip圧縮後わずか239バイトという小ささが最大の特徴です。文字列・オブジェクト・配列など様々な形式の引数を受け取り、偽値(false、null、undefined、0など)を自動的に除外して、有効なクラス名だけをスペース区切りの文字列に結合してくれます。
主な特徴
- 圧倒的な軽量さ - 通常版で239バイト、文字列のみを扱う
clsx/lite版に至っては140バイトとバンドルサイズへの影響がほぼありません - 柔軟な引数形式 - 文字列、オブジェクト、配列、それらのネストした組み合わせまで自由に渡せます
- 偽値の自動除外 -
falseやnull、undefined、空文字列などは結合結果から自動的に取り除かれます - フレームワーク非依存 - ReactだけでなくVueやSvelteなど、className(class)文字列を扱うあらゆる場面で利用できます
インストール
npm、yarn、pnpmいずれのパッケージマネージャーでもインストール可能です。
# npm
npm install clsx
# yarn
yarn add clsx
# pnpm
pnpm add clsx
clsxのサンプルを動かす
下のプレイグラウンドでは、active・disabled・サイズ(size-sm / size-lg)という3種類の条件をチェックボックスとラジオボタンで切り替えられます。操作するたびにclsx()が組み立てたクラス名の文字列が画面にリアルタイムで表示されるので、条件付きオブジェクト構文({ 'クラス名': 真偽値 })がどう評価されているかを実際の挙動で確認できます。
要点となる部分だけを抜き出すと、次のようになります。activeとdisabledはオブジェクト構文で真偽値を渡し、サイズは三項演算子で文字列を出し分けています。
import clsx from 'clsx';
function buildClassName({ isActive, isDisabled, size }) {
return clsx('btn', {
'btn-active': isActive,
'btn-disabled': isDisabled,
}, size === 'lg' ? 'size-lg' : 'size-sm');
}
buildClassName({ isActive: true, isDisabled: false, size: 'sm' });
// => 'btn btn-active size-sm'
実際に動かせるものが下です。初期状態では「Active」だけがONになっており、btn btn-active size-smというクラス文字列が生成されています。「Active」のチェックを外すとbtn-activeが消え、「Disabled」を入れるとbtn-disabledが追加されます。「Large」のラジオボタンを選ぶとsize-smがsize-lgに切り替わる様子もその場で確認できます。
すべてのチェックを外すとbtn size-smまで文字列が短くなり、clsxが偽値のキーを黙って除外していることが分かるはずです。逆に「Active」と「Disabled」を両方入れるとbtn btn-active btn-disabled size-smのように、真になった条件のクラス名だけがスペース区切りで連結されます。オブジェクトのキーを増やすだけで、条件が何個に増えても書き方は変わりません。
基本的な使い方
文字列を渡す
条件式と組み合わせて、真偽値に応じたクラス名を組み立てられます。
import clsx from 'clsx';
clsx('foo', true && 'bar', 'baz');
// => 'foo bar baz'
clsx('foo', false && 'bar', 'baz');
// => 'foo baz'
オブジェクトを渡す
キーがクラス名、値が真偽値のオブジェクトを渡すと、trueのキーだけが採用されます。可読性が高く、最もよく使われる形式です。
clsx({
foo: true,
bar: false,
baz: isActive,
});
// isActive が true の場合 => 'foo baz'
配列を渡す
配列やネストした配列もサポートしており、mapなどで動的に生成したクラス名リストをそのまま渡せます。
clsx(['foo', 0, false, 'bar']);
// => 'foo bar'
clsx('foo', [1 && 'bar', { baz: false, bat: null }, ['hello', ['world']]]);
// => 'foo bar hello world'
軽量版 clsx/lite
文字列の連結だけで十分な場面では、さらに軽量なclsx/liteが利用できます。オブジェクトや配列は渡せず、文字列のみを受け付ける代わりに140バイトまでサイズを削減できます。
import clsx from 'clsx/lite';
clsx('foo', true && 'bar', 'baz');
// => 'foo bar baz'
実践的なユースケース
Reactコンポーネントのバリアント管理
ボタンコンポーネントなど、propsに応じてスタイルを切り替えるパターンで威力を発揮します。
import clsx from 'clsx';
function Button({ variant = 'primary', isDisabled, isFullWidth, children }) {
return (
<button
disabled={isDisabled}
className={clsx('btn', {
'btn-primary': variant === 'primary',
'btn-secondary': variant === 'secondary',
'btn-disabled': isDisabled,
'btn-full-width': isFullWidth,
})}
>
{children}
</button>
);
}
冒頭の文字列連結と比べて、どの条件がどのクラス名に対応するかが一目で分かるようになりました。
Tailwind CSSとの組み合わせ
Tailwind CSSのようなユーティリティファーストなCSSフレームワークと組み合わせる際も、条件分岐が整理されて見通しがよくなります。
function Alert({ type, message }) {
return (
<div
className={clsx(
'rounded-md p-4 text-sm',
type === 'error' && 'bg-red-50 text-red-700',
type === 'success' && 'bg-green-50 text-green-700',
type === 'warning' && 'bg-yellow-50 text-yellow-700'
)}
>
{message}
</div>
);
}
動的なリストとの組み合わせ
タブやメニューなど、選択状態を持つ一覧UIでも簡潔に記述できます。
function TabList({ tabs, activeTab, onSelect }) {
return (
<ul className="tab-list">
{tabs.map((tab) => (
<li
key={tab.id}
className={clsx('tab-item', { 'tab-item-active': tab.id === activeTab })}
onClick={() => onSelect(tab.id)}
>
{tab.label}
</li>
))}
</ul>
);
}
まとめ
clsxは、条件付きのclassName組み立てという地味ながらもフロントエンド開発で頻出する課題を、極小のバンドルサイズとシンプルなAPIで解決してくれるライブラリです。文字列・オブジェクト・配列と柔軟な書き方に対応しているため、既存のプロジェクトにも無理なく導入できます。
「classNameの三項演算子や文字列連結がごちゃごちゃしてきた」と感じたら、まずは一箇所だけでもclsxに置き換えてみてください。そのシンプルさと読みやすさに、きっと手放せなくなるはずです。
