관리 메뉴

거니의 velog

(22) 컴포넌트 스타일링 2 본문

React_리액트 시작

(22) 컴포넌트 스타일링 2

Unlimited00 2023. 12. 5. 08:39

3. CSS Module

* CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 고유한 값, 즉 [파일 이름]_[클래스 이름]_[해시값] 형태로 자동으로 만들어서 컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해 주는 기술이다. CSS Module을 사용하기 위해 구버전(v1)의 create-react-app에서는 웹 팩에서 css-loader 설정을 별도로 해 주어야 했지만, v2 버전 이상부터는 따로 설정할 필요 없이 .module.css 확장자로 파일을 저장하기만 하면 CSS Module이 적용된다.

* CSSModule.module.css라는 파일을 src 디렉터리에 생성하여 다음과 같이 한 번 작성해 보자.

/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용 가능 */

.wrapper {
    background: black;
    padding: 1rem;
    color: white;
    font-size: 2rem;
}

/* 글로벌 CSS를 작성하고 싶다면 */

:global .something {
    font-weight: 800;
    color: aqua;
}

* CSS Module을 사용하면 클래스 이름을 지을 때 그 고유성에 대해 고민하지 않아도 된다. 흔히 사용하는 단어로 이름을 짓는다고 해도 전혀 문제가 되지 않는다. 해당 클래스는 우리가 방금 만든 스타일을 직접 불러온 컴포넌트 내부에서만 작동하기 때문이다.

* 만약 특정 클래스가 웹 페이지에서 전역적으로 사용되는 경우라면 :global을 앞에 입력하여 글로벌 CSS임을 명시해 줄 수 있다.

* 다 작성했다면 위 CSS Module을 사용하는 리액트 컴포넌트도 작성해 보자!

[CSSModule.js]

import React from "react";
import styles from "./CSSModule.module.css";

const CSSModule = () => {
  return (
    <div className={styles.wrapper}>
      안녕하세요, 저는 <span className="something">CSS Module!</span>
    </div>
  );
};

export default CSSModule;

* CSS Module이 적용된 스타일 파일을 불러오면 객체를 하나 전달받게 되는데 CSS Module에서 사용한 클래스 이름과 해당 이름을 고유화한 값이 키-값 형태로 들어 있다. 예를 들어 위 코드에서 console.log(styles)를 한다면 다음과 같은 결과가 나타난다.

{ wrapper: "CSSModule_wrapper_1SbdQ" }

* 우리가 지정한 클래스 이름 앞뒤로 파일 이름과 해시값이 붙었지요?

* 이 고유한 클래스 이름을 사용하려면 클래스를 적용하고 싶은 JSX 엘리먼트에 className={styles.[클래스 이름]} 형태로 전달해 주면 된다. :global을 사용하여 전역적으로 선언한 클래스의 경우 평상시 해 왔던 것처럼 그냥 문자열로 넣어 준다.

* CSSModule 관련 컴포넌트와 스타일을 모두 작성했다면 App 컴포넌트에서 렌더링해 보자.

import React, { Component } from "react";
import CSSModule from "./CSSModule";

class App extends Component {
  render() {
    return (
      <div>
        <CSSModule />
      </div>
    );
  }
}

export default App;

* CSS Module이 잘 적용되었는가?

* CSS Module을 사용한 클래스 이름을 두 개 이상 적용할 때는 다음과 같이 코드를 작성하면 된다.

[CSSModule.module.css]

/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용 가능 */

.wrapper {
    background: black;
    padding: 1rem;
    color: white;
    font-size: 2rem;
}

.inverted {
    color: black;
    background: white;
    border: 1px solid black;
}

/* 글로벌 CSS를 작성하고 싶다면 */

:global .something {
    font-weight: 800;
    color: aqua;
}

[CSSModule.js]

import React from "react";
import styles from "./CSSModule.module.css";

const CSSModule = () => {
  return (
    <div className={`${styles.wrapper} ${styles.inverted}`}>
      안녕하세요, 저는 <span className="something">CSS Module!</span>
    </div>
  );
};

export default CSSModule;

여러 클래스 이름 사용하기

* 위 코드에서 ES6 문법인 템플릿 리터럴(Template Literal)을 사용하여 문자열을 합해 주었다. 이 문법을 사용하면 문자열 안에 자바스크립트 레퍼런스를 쉽게 넣어 줄 수 있다.

const name = '리액트';
// const message = '제 이름은 ' + name + ' 입니다.';
const message = `제 이름은 ${name} 입니다.`;

* 여기서 사용되는 ` 문자는 백틱(Backtick)이라고 부르며, 키보드에서 숫자 키 [1] 왼쪽에 있는 키이다.

* CSS Module 클래스를 여러 개 사용할 때 템플릿 리터럴 문법을 사용하고 싶지 않다면 다음과 같이 작성할 수도 있다.

className={[styles.wrapper, styles.inverted].join(' ')}

(1) classnames

* classnames는 CSS 클래스를 조건부로 설정할 때 매우 유용한 라이브러리이다. 또한, CSS Module을 사용할 때 이 라이브러리를 사용하면 여러 클래스를 적용할 때 매우 편리하다.

* 우선 해당 라이브러리를 설치하자.

$ yarn add classnames

* classnames의 기본적인 사용법을 한번 훑어 보자.

import classNames from "classnames";

classNames("one", "two"); // = 'one two'
classNames("one", { two: true }); // = 'one two'
classNames("one", { two: false }); // = 'one'
classNames("one", ["two", "three"]); // = 'one two three'

const myClass = "hello";
classNames("one", myClass, { myCondition: true }); // = 'one hello myCondition'

* 이런 식으로 여러 가지 종류의 파라미터를 조합해 CSS 클래스를 설정할 수 있기 때문에 컴포넌트에서 조건부로 클래스를 설정할 때 매우 편하다. 예를 들어 props 값에 따라 다른 스타일을 주기가 쉬워진다.

const MyComponent = ({ highlighted, theme }) => {
  <div className={classNames("MyComponent", { highlighted }, theme)}>
    Hello
  </div>;
};

* 이렇게 할 경우, 위 엘리먼트의 클래스에 highlighted 값이 true 이면 highlighted 클래스가 적용되고, false이면 적용되지 않는다. 추가로 theme으로 전달받는 문자열은 내용 그대로 클래스에 적용된다.

* 이런 라이브러리의 도움을 받지 않는다면 다음과 같은 형식으로 처리해야 할 것이다.

const MyComponent = ({ highlighted, theme }) => {
  <div className={`MyComponent ${theme} ${highlighted ? "highlighted" : ""}`}>
    Hello
  </div>;
};

* classnames를 쓰는 것이 가독성이 훨씬 높다.

* 덧붙여 CSS Module과 함께 사용하면 CSS Module 사용이 훨씬 쉬워진다. classnames에 내장되어 있는 bind 함수를 사용하면 클래스를 넣어 줄 때마다 styles.[클래스 이름] 형태를 사용할 필요가 없다. 사전에 미리 styles에서 받아온 후 사용하게끔 설정해 두고 cx('클래스 이름', '클래스 이름2') 형태로 사용할 수 있다.

* 다음 코드는 우리가 만든 CSS Module 컴포넌트에 classnames의 bind 함수를 적용한 예이다.

import React from "react";
import classNames from "classnames/bind";
import styles from "./CSSModule.module.css";

const cx = classNames.bind(styles); // 미리 styles 에서 클래스를 받아 오도록 설정한다.

const CSSModule = () => {
  return (
    <div className={cx("wrapper", "inverted")}>
      안녕하세요, 저는 <span className="something">CSS Module!</span>
    </div>
  );
};

export default CSSModule;

* CSS Module을 사용할 때 클래스를 여러 개 설정하거나, 또는 조건부로 클래스를 설정할 때 classnames의 bind를 사용하면 훨씬 편리하게 작성할 수 있다.


(2) Sass와 함께 사용하기

* Sass를 사용할 때도 파일 이름 뒤에 .module.scss 확장자를 사용해 주면 CSS Module로 사용할 수 있다. CSSModule.module.css 파일의 이름을 CSSModule.module.scss로 한번 변경해 보자. 스타일 코드도 이에 따라 조금 수정해 보자.

/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용 가능 */

.wrapper {
    background: black;
    padding: 1rem;
    color: white;
    font-size: 2rem;
    &.inverted {
        // inverted가 .wrapper와 함께 사용되었을 때만 적용
        color: black;
        background: white;
        border: 1px solid black;
    }
}

/* 글로벌 CSS를 작성하고 싶다면 */

:global {
    // :global로 감싸기
    .something {
        font-weight: 800;
        color: aqua;
    }
    // 이후에도 여러 다른 클래스를 만들 수 있다...
}

* 그리고 나서 CSSModule.js 상단에도 .css 파일 대신 .scss 파일을 불러오자.

import React from "react";
import classNames from "classnames/bind";
import styles from "./CSSModule.module.scss";

const cx = classNames.bind(styles); // 미리 styles 에서 클래스를 받아 오도록 설정한다.

const CSSModule = () => {
  return (
    <div className={cx("wrapper", "inverted")}>
      안녕하세요, 저는 <span className="something">CSS Module!</span>
    </div>
  );
};

export default CSSModule;

* 이전과 똑같은 화면이 나타난다.


(3) CSS Module이 아닌 파일에서 CSS Module 사용하기

* CSS Module에서 글로벌 클래스를 정의할 때 :global을 사용했던 것처럼 CSS Module이 아닌 일반 .css/.scss 파일에서도 :local을 사용하여 CSS Module을 사용할 수 있다.

:local .wrapper {
	/* 스타일 */
}

:local  {
    .wrapper {
    	/* 스타일 */
    }
}

'React_리액트 시작' 카테고리의 다른 글

(23) 컴포넌트 스타일링 3  (1) 2023.12.05
(21) 컴포넌트 스타일링 1  (1) 2023.12.04
(20) Hooks 2  (1) 2023.12.04
(19) Hooks 1  (0) 2023.12.04
(18) 컴포넌트의 라이프사이클 메서드 2  (1) 2023.12.04