01.11 리액트 컴포넌트 CSS 적용 ( CSS Module, Sass, styled-components )

React.js

01.11. 컴포넌트 CSS 적용 ( CSS Module, Sass, styled-components )

  • CSS Module :
        모듈화된 CSS로 CSS 클래스를 만들면 자동으로 고유한 클래스네임을 생성하여 스코프를 지역적으로 제한하는 방법이다.
  • Sass :
        CSS 전처리기 중 하나로, 확장된 CSS 문법을 사용하여 CSS 코드를 더욱 쉽게 작성하는 방식이다. CSS Module 처럼 사용하는 방법도 있다.
  • styled-components : JS 코드 내부에서 스타일을 정의한다.

01.11.1. CSS Module

CSS Module 은 CSS를 모듈화하여 사용하는 방식으로 CSS 클래스를 만들면 자동으로 고유한 클래스네임을 생성하여 스코프지역적으로 제한한다. 모듈화된 CSS를 webpack으로 불러오면 사용자가 정의한 클래스 네임과 고유화된 클래스네임으로 구성된 객체를 반환한다.
{ box: 'src-App__box--mjrNr' }

// 클래스 적용할 때는 아래와 같이 사용한다.
className = {styles.box}
새로운 프로젝트를 생성한 후 CSS Module을 활성화한다.
$ create-react-app study-css
$ cd study-css
$ yarn eject
config/webpack.config.js 파일의 css-loader 설정 부분을 확인한다.
  const getStyleLoaders = (cssOptions, preProcessor) => {
    const loaders = [
      isEnvDevelopment && require.resolve('style-loader'),
      isEnvProduction && {
        loader: MiniCssExtractPlugin.loader,
        options: Object.assign(
          {},
          shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined
        ),
      },
      {
        loader: require.resolve('css-loader'),
        options: cssOptions,
      },
      {
        loader: require.resolve('postcss-loader'),
        options: {
          ident: 'postcss',
          plugins: () => [
            require('postcss-flexbugs-fixes'),
            require('postcss-preset-env')({
              autoprefixer: {
                flexbox: 'no-2009',
              },
              stage: 3,
            }),
          ],
          sourceMap: isEnvProduction && shouldUseSourceMap,
        },
      },
    ].filter(Boolean);
    if (preProcessor) {
      loaders.push({
        loader: require.resolve(preProcessor),
        options: {
          sourceMap: isEnvProduction && shouldUseSourceMap,
        },
      });
    }
    return loaders;
  };
style-loader는 스타일을 불러와 웹 페이지에서 활성화하는 역할이다.
css-loader는 css 파일에서 import와 url(...) 문을 webpack의 require 기능으로 처리하는 역할을 한다.
postcss-loader는 모든 웹 브라우저에서 입력한 CSS 구문이 제대로 작동할 수 있도록 자동으로 -webkit, -mos, -ms 등 접두사를 붙여준다.
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
// using the extension .module.css
{
    test: cssModuleRegex,
    use: getStyleLoaders({
        importLoaders: 1,
        sourceMap: isEnvProduction && shouldUseSourceMap,
        modules: true,
        getLocalIdent: getCSSModuleLocalIdent,
    }),
},
config/webpack.config.js 파일의 내용에서 .module.css 파일을 사용하면 된다라고 작성되어있다.
/* scr/Test.module.css 파일을 신규 생성한 후 아래 코드 작성 */
.box {
    display: inline-block;
    width: 100px;
    height: 100px;
    border: 1px solid silver;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

/* src/App.js 코드를 수정한다. */
import React, { Component } from 'react';
import styles from './Test.module.css'

class App extends Component {
  render() {
    return (
      <div className={styles.box}></div>
    );
  }
}

export default App;
작성 후 프로젝트를 실행한다.
$ yarn start
개발자도구의 Elements 탭을 열어 코드를 살펴보면 클래스네임이 고유하게 설정되어 CSS 클래스가 중복되어 충돌이 일어나지 않는다.
<div class="Test_box__bxmdE"></div>
※ 클래스가 여러개일 경우
<div className={[styles.box, styles.blue].join(' ')}></div>
yarn 에서 classnames 라이브러리 사용
$ yarn add classnames

import React, { Component } from 'react';
import classnames from 'classnames';
import styles from './Test.module.css'

class App extends Component {
  render() {
    return (
      <div className={classnames(styles.box, styles.blue)}></div>
    );
  }
}

export default App;
classNames의 사용방법은 다음과 같다.
classNames('foo','bar'); // => 'foo bar'
classNames('foo',{ bar : true }); // => 'foo bar' 여러 형식을 받아 올 수 있다.
classNames({ foo-bar : true }); // => 'foo-bar'
classNames({ foo-bar : false }); // => '' false, null, 0, undefined는 무시된다.
classNames({ foo : true },{ bar : true }); // => 'foo bar'
classNames(['foo', 'bar']); // => 'foo bar'
const isBar = true;
classNames('foo',{ bar : isBar }); // => 'foo bar'
CSS Module은 고유한 클래스네임을 만들어 스코프를 제한한다. classnames 라이브러리를 사용하면 더욱 편하게 지정할 수 있다. 

01.11.2. Sass

Sass(Syntactically awesome style sheets, 문법적으로 매우 멋진 스타일시트)는 CSS에서 사용할 수 있는 문법을 확장하여 중복 코드를 줄여 가독성이 좋은 코드를 작성할 수 있다.
$ yarn add node-sass sass-loader

  const getStyleLoaders = (cssOptions, preProcessor) => {
    const loaders = [
      isEnvDevelopment && require.resolve('style-loader'),
      isEnvProduction && {
        loader: MiniCssExtractPlugin.loader,
        options: Object.assign(
          {},
          shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined
        ),
      },
      {
        loader: require.resolve('css-loader'),
        options: cssOptions,
      },
      {
        loader: require.resolve('postcss-loader'),
        options: {
          ident: 'postcss',
          plugins: () => [
            require('postcss-flexbugs-fixes'),
            require('postcss-preset-env')({
              autoprefixer: {
                flexbox: 'no-2009',
              },
              stage: 3,
            }),
          ],
          sourceMap: isEnvProduction && shouldUseSourceMap,
        },
      },
      {
        // sass-loader 추가
        loader: require.resolve('sass-loader'),
        options: { /* 추후 입력 */ },
      },
    ].filter(Boolean);
    if (preProcessor) {
      loaders.push({
        loader: require.resolve(preProcessor),
        options: {
          sourceMap: isEnvProduction && shouldUseSourceMap,
        },
      });
    }
    return loaders;
  };
마우스를 올릴때나 클릭할 때 다른 스타일을 적용하려면 다음과 같이 CSS 코드를 작성해야하지만,
.box:hover {
    background: red;
}

.box.active {
    background: yellow;
}
Sass 의 현재 선택자 참조 기능으로 작성할 수 있다. & 문자를 아래와 같이 사용한다.
.box {
    display: inline-block;
    width: 100px;
    height: 100px;
    border: 1px solid silver;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    &:hover {
        background: red;
    }
    
    &:active {
        background: yellow;
    }
}

// 상단에서 작성한 src/Test.css 파일의 확장자를
// src/Test.scss 로 변경하고 App.js 의 import 도 수정한다.
import styles from './Test.scss'

import React, { Component } from 'react';
import classnames from 'classnames';
import styles from './Test.scss'

class App extends Component {
  render() {
    const isBlue = true;
    return (
      <div className={classnames('box', { blue: isBlue})}>
          { /* div 요소 추가 */ }
          <div className={classnames('box-inside')}></div>
      </div>
    );
  }
}

export default App;
상단의 예시처럼 div 속에 있는 또 다른 div에 CSS 적용하는 방법은 아래와 같다.
/* 기존 CSS 문법 */
.box .box-inside { ... }
/* Sass 문법 */
.box {
    ...
    .box-inside { ... }
}

01.11.2.1. 변수사용

Sass에서 자주 사용하는 값을 변수에 담을 수 있다. 
$size: 100px;

.box {
    display: inline-block;
    /* 상단에서 설정한 $size를 사용 */
    width: $size; 
    height: $size;
    (...)
}

01.11.2.2. 믹스인 사용

자주 사용하는 값은 변수에 넣고, 자주 사용하는 구문은 믹스인으로 다시 재사용할 수 있다.
$size: 100px;

/* name은 사용자가 입력하는 값 */
@mixin name { 
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%)
}

.box {
    display: inline-block;
    width: $size;
    height: $size;
    border: 1px solid silver;
    position: fixed;
    // top: 50%;
    // left: 50%;
    // transform: translate(-50%, -50%);
    /* mixin으로 생성하고 include로 불러온다. */ 
    @include name();
    
    (...)
}

01.11.2.3. 변수와 믹스인 전역적 사용

스타일만 관리하는 디렉터리를 만들어 전역적으로 쓰는 코드를 따로 분리하고, 컴포넌트 스타일 파일에 불러와 사용할 수 있다.

/* src/styles/utils.scss 파일 내용 */
$size: 100px;

/* name은 사용자가 입력하는 값 */
@mixin name { 
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%)
}

@import './styles/utils';

.box {
    display: inline-block;
    width: $size;
    height: $size;
    border: 1px solid silver;
    position: fixed;

    @include name();
    (...)
}
하지만 파일의 디렉터리가 깊어지면 불러올 코드가 길어지고 불편하기 때문에 sass-loader를 설정할 때 includePaths를 설정하여 경로를 간소화할 수 있다.
/* config/paths.js */
module.exports = {
  (...)
  /* 스타일 디렉터리 적용 */
  styles: resolveApp('src/styles'),
};

/* config/webpack.config.js 파일 */
{
    loader: require.resolve('sass-loader'),
    options: { 
        includePaths: [paths.styles]
    },
},

/* Test.scss 의 확장자를 Test.css 로 변경 */
@import 'utils';
앞선 방법보다 훨씬 간단하게 전역코드를 불러올 수 있다.

01.11.3. styled-components

자바스크립트 파일 안에 스타일을 선언하는 방식으로 'CSS in JS' 라고 한다. 관련 라이브러리가 많지만 사용자 수가 많은 styled-components를 설치하여 사용한다.

/* src/components/StyledButton.js 파일 생성 후 아래 코드 작성 */
import React from 'react';
import styled from 'styled-components';

const Wrapper = styled.div`
 border: 1px solid sliver;
 display: inline-block;
 padding: 1rem;
 border-radius: 3px;
 &:hover {
     background: black;
     color: white;
 }
 `;

const StyledButton = ({children, ...rest}) => {
    return (
        <Wrapper {...rest}>
            {children}
        </Wrapper>
    );
};

export default StyledButton;


/* App.js 코드 수정 */
import React, { Component } from 'react';
import StyledButton from './components/StyledButton';

class App extends Component {
  render() {
    return (
      <div>
          <StyledButton>버튼</StyledButton>
      </div>
    );
  }
}

export default App;
styled-components의 최대 장점은 자바스크립트 내부에서 스타일을 정의하기 때문에 자바스크립트와 스타일 사이의 벽이 허물어져 동적 스타일링이 더욱 편해진다는 것이다.

※ StyledButton.js 파일에서 익숙하지 않은 문법을 확인할 수 있다.
` ` - ES6의 Tagged Template Literals 문법으로 backquote(₩) 사이에 ${자바스크립트 표현}이 들어가면 끊어서 함수 인자로 전달한다. 이 문법을 사용한 이유는 CSS 적용을 할 때 props에 접근하기 위해서이다. Tagged Template Literals 문법을 사용하지 않으면 함수가 문자열 자체로 들어가기 때문에 Tagged Template Literals 문
... - ES6 전개 연산자(spread operator)는 표현식(expression)은 2개 이상의 인수arguments(함수 호출 용)나 2개 이상의 요소elements(배열 리터럴 용) 또는 2개 이상의 변수(비구조화 할당 용)가 예상되는 곳에 확장될 수 있도록 한다.

댓글

이 블로그의 인기 게시물

01.9 React 컴포넌트 라이프사이클

01.7 React ref (DOM에 이름 달기)