[TIL/React] clsx로 조건부 className 쉽게 사용하기

clsx는 왜쓰나?

요즘 유행하는 tailwind css를 활용할 때, className을 조건에 따라서 분기하는 경우가 많다. 나는 현재 회사 프로젝트에서 다음과 같이 className을 조건에 따라 분기하여 활용했다.

interface AnswerButtonProps {
    label: string;
    isSelected: boolean;
    handleTap: () => void;
}

export function AnswerButton({
    label,
    isSelected,
    handleTap,
}: AnswerButtonProps) {
    const selectedClassName = "bg-blue-500";
    return (
        <button
            onClick={handleTap}
            className={`w-full flex flex-row justify-center p-4 rounded-lg mt-4 shadow ${
                isSelected ? selectedClassName : "bg-white"
            }`}
        >
            <h1
                className={`w-1/5 text-2xl font-bold text-start ${
                    isSelected ? "text-white" : "text-black"
                }`}
            >
                {label}
            </h1>
        </button>
    );
}

크게 문제될 건 없지만, 분기가 다양해지거나 조금 복잡해지기만 하면 머리가 화끈해진다. 이때 좀 더 간단한 문법으로 이러한 조건부 className을 적용할 수 있다. clsx 는 이럴 때 사용하는 유틸리티 패키지이다.

npm 주소 : https://www.npmjs.com/package/clsx

clsx

A tiny (239B) utility for constructing className strings conditionally.
Also serves as a faster & smaller drop-in replacement for the classnames module.

className의 조건 분기를 위해 사용되는 아주 작은 크기의 유틸리티 패키지로서, 이를 활용하면 보다 간결하고 직관적인 문법으로 조건부 className을 생성할 수 있다.

사용방법은 꽤나 간단하다. 다음에는 간단하게 이를 어떻게 사용할 수 있는지에 대해 알아보자.

clsx 사용 방법

공식 설명에서는 다음과 같이 Usage 예제를 제공한다.

import clsx from 'clsx';
// or
import { clsx } from 'clsx';

// 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'

기본적으로 string을 리턴하는 clsx()함수를 활용하는데, 이때 파라미터로 classNames를 갖는다. clsx의 코드를 까보면, 다음과 같이 정의된 것을 확인할 수 있다.

clsx()함수는 ClassValue 타입의 inputs 파라미터를 여러 개 가질 수 있는 함수이며, string타입을 리턴하는 함수다. 이때 ClassValue의 경우 배열 형태의 ClassArray, Dictionary, string, number 등 다양한 타입을 가질 수 있다.

export type ClassValue = ClassArray | ClassDictionary | string | number | bigint | null | boolean | undefined; 
export type ClassDictionary = Record<string, any>; 
export type ClassArray = ClassValue[];

export function clsx(...inputs: ClassValue[]): string; 
export default clsx;

예제를 보면 알 수 있듯이, clsx() 함수는 다양한 타입으로 className을 파라미터로 받아서, falsy한 값은 제외하고 나머지 모든 값을 하나의 className으로 만들어주는 역할을 수행한다.

작업하던 코드에 clsx를 적용하면 다음과 같은 모습이 된다.

import { clsx } from "clsx";

interface AnswerButtonProps {
    label: string;
    isSelected: boolean;
    handleTap: () => void;
}

export function AnswerButton({
    label,
    isSelected,
    handleTap,
}: AnswerButtonProps) {
    const selectedClassName = "bg-blue-500";
    return (
        <button
            onClick={handleTap}
            className={clsx(
                "w-full flex flex-row justify-center p-4 rounded-lg mt-4 shadow",
                isSelected && selectedClassName
            )}
        >
            <h1
                className={clsx(
                    "w-1/5 text-2xl font-bold text-start",
                    isSelected ? "text-white" : "text-black"
                )}
            >
                {label}
            </h1>
        </button>
    );
}

해당 코드는 선택가능한 버튼 컴포넌트로서, 선택되었을 경우 이를 나타내기위해 컬러를 변경하는 컴포넌트 코드이다. 템플릿 리터럴을 사용하던 기존의 방법보다 더 보기 편하고 단순한 구조를 가진다. 이보다 더 복잡한 분기가 필요한 상황이 오더라도 clsx를 활용한다면 보다 간결한 코드로 이를 해결할 수 있을 것 같다.