CSS Module

React CSS Module


들어가며

여러 사람과 협업하거나, 진행하는 업무의 규모가 커질수록 CSS 작성에 신경 써야 하는 부분이 있다.
그것은 바로 작성하는 CSS의 전역 오염이나 다른 CSS의 영향도를 신경 써가며 CSS를 작성해야 하는데, 이게 여간 까다롭고 귀찮은 일이다.

이번 글에서는 이런 문제를 해결할 방법의 하나인 CSS Module을 소개하겠다.

CSS Module 적용법


보통 React 프로젝트 환경을 구성하면 CRA를 이용하여 구성하게 될 텐데, 여기에 CSS Module을 적용하여 사용할 계획이라면 별다른 구성의 추가 없이 간단하게 적용할 수 있다.

CSS 파일명을 Component1.css 에서 Component1.module.css로 바꿔주기만 하면 CSS Module 적용을 위한 준비는 끝이다.

이제 작업하던 jsx 파일에서 기존의 코드를 바꿔주기만 하면 된다.

import './Component1.css';

export const Component1 = () => {
  return (
    <div className="component-class01">
      <p>Component1</p>
    </div>
  );
};

위의 코드를 아래처럼 수정해보자.

import Style from './Component1.module.css';

export const Component1 = () => {
  return (
    <div className={Style['component-class01']}>
      <p>Component1</p>
    </div>
  );
};

이렇게 수정한 후 화면을 확인해 보면 아래처럼 component-class01 이라는 클래스 명이 고유한 값으로 변환된 결과물로 나온 것을 볼 수 있다.

<div class="Component1_component-class01__DFBwT">
  <p>Component1</p>
</div>

CSS 또한 작성한 코드가 Loader를 거쳐 component-class01의 클래스 명도 아래처럼 고유하게 변환된다.

.Component1_component-class01__DFBwT {
  background: green;
}

:global/:local


하지만 불행히도 우리는 업무를 진행하다 보면 고유한 CSS Module의 클래스 명이 아닌 전역적인 클래스 명이 필요한 때도 있다.

그럴 때는 CSS Module에서 :global을 이용하면 된다.

:global .page-wrap {
  // ...
}

또한 정말 흔치 않은 경우이지만 전역적인 클래스 명을 사용함과 동시에 전역 클래스의 자식 요소는 고유한 클래스를 사용하고 싶을 때도 있다.

이런 경우에는 :local을 이용하면 된다.(하지만 이런 일은 없을 거라 생각된다.)

:global .page-wrap :local .component-class01 {
  // ...
}

장점과 단점


CSS Module의 장점은 명확하다.

  • 클래스 명의 전역 오염 방지
  • 고유한 클래스로 인하여 복잡한 클래스 명을 생각하며 작성하지 않음
  • 컴포넌트별 CSS 관리의 편리함

그러나 장점이 있으면 단점도 있는 법 CSS Module의 단점은 뭐가 있을까?

첫째로 코드를 작성하다 보면 동적인 클래스 명을 작성하게 되는 경우가 많은데 이럴 때의 결과물이 좋지 않다.

import Style from './Component1.module.css';

export const Component1 = () => {
  const type01 = false;

  return (
    <div className={`${Style['component-class01']} ${type01 && Style['type01']}`}>
      <p>component1</p>
    </div>
  );
};

이런 식의 코드가 작성된다면, 특정 조건으로 type01의 클래스가 추가되거나 삭제되거나 하게 된다.
그런데 type01의 클래스가 부여되면 괜찮지만 없을 때의 결과물을 보면 아래 코드처럼 ‘이게 뭐람…’ 싶은 결과물이 나오게 된다.

<div class="Component1_component-class01__DFBwT false">
  <p>component1</p>
</div>

두 번째로 컴포넌트의 부모 화면의 CSS 파일에서 예외 처리를 위한 컴포넌트의 CSS를 만져야 할 때가 있는데, CSS Module을 사용하면 보통의 방법으로는 할 수 있는 방법이 없다.

HTML의 tag 선택자를 사용하거나 div[class*="component-class01"] 처럼 작성해야 하는데 이래서는 CSS Module을 사용하는 의도와도 맞지 않고, 이렇게 사용한다고 하더라도 오히려 더 불편할 뿐이다.

단점을 보완할 방법

위의 단점들을 보면 ‘왜 굳이 CSS Module을 사용해야 하는가?’ 하는 생각이 들 수도 있다.
그러나 많은 사람이 불편함을 느끼게 되면 당연히 해결법이 있기 마련이다.

CSS Module을 사용하면 항상 세트처럼 따라오는 모듈이 하나 있는데 바로 classNames라는 모듈이다.

이 모듈을 사용하게 되면 위에서 이야기한 첫 번째의 단점을 완전히 해결할 수 있으며, 더욱 편리한 동적 클래스를 작성할 수 있게 된다.

아래 예시를 보자.

import Style from './Component1.module.css';
import classNames from 'classnames/bind';

const cx = classNames.bind(Style);

export const Component1 = () => {
  return (
    <div className={cx('component-class01', { type01: true })}>
      <p>component1</p>
    </div>
  );
};

이렇게 작성하게 되면 기본적으로 component-class01이라는 클래스 명이 있으면서 true/false 조건에 따라 type01이라는 클래스가 부여되게 된다.

classNames의 더 많은 사용법은 아래 문서에서 확인할 수 있다.

classNames

두 번째의 단점을 해결할 방법으로는 조금 번거롭지만, 컴포넌트 작성 시 부모에게 클래스를 받아 바인딩하게끔 만들어 준다면 충분히 해결할 수 있다.

import { useState, useEffect } from 'react';
import Style from './Component1.module.css';
import classNames from 'classnames/bind';

const cx = classNames.bind(Style);

export const Component1 = ({ pageClass }) => {
  const [componentClass, setComponentClass] = useState(null);

  useEffect(() => {
    const moduleClass = cx('component-class01', { type01: true });
    const classResult = !pageClass ? `${moduleClass}` : `${moduleClass} ${pageClass}`;

    setComponentClass(classResult);
  }, [pageClass]);

  return (
    <div className={componentClass}>
      <p>component1</p>
    </div>
  );
};

이렇게 컴포넌트를 작성하였다면 부모의 페이지에서 아래처럼 자식 컴포넌트를 스타일링 할 수 있는 클래스를 props로 전달해 주기만 하면 부모 페이지의 CSS 파일에서 컴포넌트를 스타일링 할 수 있다.

import { Component1 } from './component/Component1';
import style from './Page01.module.css';
import classNames from 'classnames/bind';

const cx = classNames.bind(style);

const App = () => {
  return (
    <>
      <Component1 pageClass={cx('app-page')} />
    </>
  );
};

export default App;

부모 페이지에서 고유화한 CSS 클래스를 props로 전달하여 컴포넌트에 바인딩하는 방식이다.

마치며


이번에 진행하게 된 프로젝트에서 CSS Module 사용하면서 부모 페이지에서 자식 컴포넌트의 CSS를 제어하기 힘든 부분으로 인한 불편함으로 처음에는 CSS Module에 대한 개인적인 평가는 좋지 않았다.

그러나 이러한 불편함을 해결하고자 이런저런 생각을 해보게 되었고, 현재 프로젝트에 적용하지는 않았지만, 위의 방법으로 충분히 해결될 거라 생각되고, 위의 방법을 적용하게 되면 앞으로는 CSS Module을 좀 더 잘 활용할 수 있지 않을까 생각된다.


추천 글