はじめに
Reactコンポーネントを書いていると、こんなコードに出会ったことはありませんか。
<button
className={
'btn ' +
(isActive ? 'btn-active ' : '') +
(isDisabled ? 'btn-disabled ' : '') +
(variant === 'primary' ? 'btn-primary' : 'btn-secondary')
}
>
送信
</button>
条件が増えるたびに文字列連結が複雑になり、余分な空白やタイポでスタイルが当たらない、といったバグに悩まされた経験がある方も多いはずです。この「className組み立て問題」をシンプルに解決してくれるのが、今回紹介する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
基本的な使い方
文字列を渡す
条件式と組み合わせて、真偽値に応じたクラス名を組み立てられます。
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に置き換えてみてください。そのシンプルさと読みやすさに、きっと手放せなくなるはずです。
