본문 바로가기

React/CSS

CSS #4 - SASS (3) with CSS Module

classnames를 함께 사용하면 가독성 있는 소스 작성이 가능해요.

classnames

classnames(https://www.npmjs.com/package/classnames)는 각 태그에 대해서, 혹은 컴포넌트에 대해서 클래스 명칭을 적용하기 위해서 사용하는 className을 더욱더 가독성 있고, 쉽게 작성할 수 있게 도와줍니다. classnames function은 string일수도 있고, object일수도 있는 여러 개의 아규먼트를 받아서 해당 부분을 여러 개의 클래스 명칭을 적용하도록 돕습니다. 또한 bind version을 사용하는 것을 통해서 CSS-Module의 스타일 적용을 돕습니다. 클래스 명칭을 dynamic allocation 하고 싶다면 다음과 같이 작성될 수 있습니다.

 

const [isRed, setIsRed] = useState<boolean>(false);

const changeColor = () => {
	if(isRed) {
		setIsRed(true);    
	} else {
		setIsRed(false);	
	}
}

return (
	<div className={'wrapper ' + isRed ? 'red' : 'green'}>
	/* this is the same as <div className={`wrapper ${isRed}` ? 'red' : 'green'}> */
	/* but, don't edit like this 
		{isRed ? <div className={'wrapper red'}> : <div className={'wrapper green'}>} 
	*/
		<button onclick={changeColor}>
			myButton
		</button>
	</div>
);

 

해당 방식이 잘못된 것은 아니나 multi-class를 적용하기 위해서 강제로 space(' ')를 wrapper뒤에 붙여주는 것이 필요하고, 만약에 적용 자체가 없는 경우 ('green'이 아니라 ''을 사용하고 싶다면)에는 사실 빈칸이 하나 존재하는 셈입니다. 그러면 'green' 대신에 ' green'을 사용하면 문제가 해결되는 것일까요? 그보다는 classnames를 적용하는 것이 더 가독성 있는 코드로 다가올 수 있습니다.

 

import classNames from 'classnames';

...

return (
	<div className={classNames(wrapper, { red: isRed, green: !isRed })}>
    
...

 

해당 방식은 value에 의해서 key를 제어하는 것을 의미하며, key를 바꾸는 것도 아래 방식처럼 제공합니다.

 

let buttonType = 'primary';
classNames({ [`btn-${buttonType}`]: true });

 

CSS-Module에서도 효과적으로 적용할 수 있습니다. 보통 styles로 css module을 사용하는데, 작성된 module.css에서 적용할 클래스 명칭이 hyphen(-)을 가지고 있다면 곤란한 상황이 연출됩니다.

 

import styles from './App.module.scss';


// 1) syntax error
	<div className={styles.wrapper + ' ' + styles.yellow-green}/>
    
// 2) bypass the error
	<div className={styles.wrapper + ' ' + styles['yellow-green']}/>

// 3) code convention but bad
	<div className={`${styles['wrapper']} ${styles['yellow-green']}`}/>

 

그나마 3번 방식이 소스 구성을 할 때 통일성을 주장할 수 있는 방식일 듯합니다. 클래스 명칭을 적용하기 위해서 작성된 CSS에 prefix로 styles를 붙이는 듯한 작성은 클래스 명칭이 길어지고 적용해야 하는 부분이 많아진다면 소스가 난잡해지게 됩니다. 클래스 명칭이 길어지는 것은 근본적으로 해결할 수 없는 문제지만, prefix처럼 붙는 styles를 classnames/bind를 사용하는 것으로 해소할 수 있습니다.

 

import classNames from 'classnames/bind';
import styles from './App.module.scss';

const cx = classNames.bind(styles);

	<div className={cx('wrapper', 'yellow-green')}>

CRA에 SASS-Module 적용하기

// 프로젝트를 생성할 디렉토리(가칭 : my-directory)로 이동
cd my-directory

// 생성할 프로젝트 폴더(가칭 : my-app-css-module)
npx create-react-app my-app-css-module

// + scss를 사용하기 위해서 https://ansrlm.tistory.com/20에서 설명했던 sass를 설치해야 합니다.
npm install sass

 

# via npm
npm install classnames

# via Bower
bower install classnames

# or Yarn (note that it will automatically save the package to your `dependencies` in `package.json`)
yarn add classnames

 

디렉토리 이동과 리파지토리 생성을 한 이후에 classnames 패키지를 설치합니다.

App.jsx에 CSS-Module을 적용하기 위해서, 그리고 SASS를 함께 사용하기 위해서 우선 css파일의 이름을 바꾸고, SCSS문법으로 작성하게 됩니다. 파일 명칭을 App.css -> App.module.scss로 변경하는 것을 수반합니다.

 

// App.module.scss

.App {
  text-align: center;
  div {
    color: greenyellow;
  }
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

.MyClass {
  color: greenyellow;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

 

App.module.scss을 사용하기 위해서 App.jsx도 다음과 같이 변경합니다.

 

// App.jsx

import React from 'react';
import classNames from 'classnames/bind';
import logo from './logo.svg';
import styles from './App.module.scss';

const cx = classNames.bind(styles);

const App = () => (
  <div className={cx('App', 'MyClass')}>
    <header className={styles['App-header']}>
      <img src={logo} className={styles['App-logo']} alt="logo" />
      <div>
        {styles.App}
      </div>
      <p className={`${styles['App-red']} ${styles['App-bold']}`}>
        Edit
        {' '}
        <code>src/App.js</code>
        {' '}
        and save to reload.
      </p>
      <a
        className={styles['App-link']}
        href="https://reactjs.org"
        target="_blank"
        rel="noopener noreferrer"
      >
        Learn React
      </a>
    </header>
  </div>
);

export default App;

 

classnames가 적용된 방법, 적용되지 않은 방법, styles를 적용할 때 object key position을 사용하는 방법, object dot operator를 사용한 방법이 섞여 있으니 참고하시면 되겠습니다. (적용 안 되는 class인 App-red / App-bold도 있습니다.)

Bundler output

webpack의 결과물은 build/static/{css, js}에 존재합니다.

 

`${file_name}_${class_name}__${hash}`로 구성되어 있는 것을 확인하실 수 있습니다.

 

또한 앞서 작성했었던 App.jsx에는 있지만 App.module.scss에는 존재하지 않는 App-red / App-blod는 undefined로 할당되는 것을 확인할 수 있습니다.

 

※ 잘못된 부분이나 궁금한 점 댓글로 작성해 주시면 최대한 답변하겠습니다. 읽어주셔서 감사합니다.

※ CSS #5 - CSS in JS (1)에서 CSS in JS 중 하나인 styled-component를 소개하고 inline style와의 차이점 비교를 진행할 예정입니다.

'React > CSS' 카테고리의 다른 글

CSS #6 - CSS-in-JS (2) Styled-Component with Storybook  (0) 2021.06.27
CSS #5 - CSS-in-JS (1) Pros and Cons  (2) 2021.06.20
CSS #3 - SASS (2) with CSS Module  (0) 2021.05.01
CSS #2 - SASS (1)  (0) 2021.04.19
CSS #1 - CSS priority  (1) 2021.04.18