3. 다양한 방식의 컴포넌트 스타일링 방식 (i) CSS, Sass, CSS Module

리액트에서는 컴포넌트를 스타일링 할 때 다양한 방식을 사용 할 수 있습니다. 이 튜토리얼에선 create-react-app 을 사용하여 프로젝트를 직접 만들어서 진행하도록 하겠습니다.

$ npx create-react-app styling-react

가장 흔한 방식, 그냥 CSS

기존의 CSS 스타일링에 있어서 딱히 불편함을 느끼지 않고, 새로운 기술을 배우는게 불필요하다고 생각한다면 그냥 CSS 를 사용하시면 됩니다.

실제로, 그냥 작은 프로젝트라면 다른 시스템을 도입하는것 조차 고민하기도 귀찮습니다. 그런 상황에는 프로젝트에 기본적으로 적용되어있는 CSS 시스템을 사용하는것만으로도 충분합니다.

create-react-app 으로 만든 프로젝트를 보면, src 디렉토리에 App.js 와 App.css 가 있습니다.

App.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
}

export default App;

App.css

.App {
  text-align: center;
}

.App-logo {
  animation: App-logo-spin infinite 20s linear;
  height: 40vmin;
}

.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);
  }
}

우선, CSS 를 작성하게 될 때 가장 중요한점은 중복되는 클래스명 생성하지 않기 입니다. 클래스명이 중복되는것을 방지하기 위해선 꽤 다양한 방식이 있는데, 하나는 네이밍이고 하나는 CSS Selector 의 활용입니다.

create-react-app 으로 만든 App 컴포넌트를 보면, 클래스명이 컴포넌트명-클래스 으로 네이밍이 되어있습니다.

이런 형식 외에도, BEM Naming 이란 방식도 있는데, 컴포넌트마다 CSS 파일을 하나하나 만드는 상황에선 그렇게 권장되지는 않습니다. BEM 은 스타일을 하나의 파일에 몰아서 작성 할 때는 편리하지만, 클래스 파일이 여러개일때는 딱히 이렇게까지 이름을 지을 필요는 없다고 생각합니다.

다른 방식으로는, CSS Selector 를 활용하는 것 입니다. 예를들어서 위 CSS 를 다음과같이 변환을 하면:

App.css

.App {
  text-align: center;
}

/*.App 안에 들어있는 .logo*/
.App .logo {
  animation: App-logo-spin infinite 20s linear;
  height: 40vmin;
}

/* .App 안에 들어있는 header */
.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 안에 들어있는 a 태그 */
.App a {
  color: #61dafb;
}

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

App.js 에서 사용된 클래스명들은 다음과 같이 수정해주면 됩니다.

App.js

import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";

class App extends Component {
  render() {
    return (
      <div className="App">
        <header>
          <img src={logo} className="logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
}

export default App;

이런식으로, 컴포넌트의 최상위 html 요소에는 컴포넌트 이름과 동일한 이름으로 클래스명을 지으시고, 그 하단에서는 소문자로 입력하거나, 딱히 클래스명이 불필요한 경우엔 아예 생략을 할 수도 있죠.

Sass 사용하기

Sass (Syntactically Awesome Style Sheets: 문법적으로 짱 멋진 스타일시트) 는 CSS pre-processor 로서, 복잡한 작업을 쉽게 할 수 있게 해주고, 코드의 재활용성을 높여줄 뿐 만 아니라, 코드의 가독성을 높여주어 유지보수를 쉽게해줍니다.

이전 버전의 create-react-app 으로 만든 프로젝트에서는 Sass 를 사용하기위하여 별도의 작업을 추가적으로 해줬어야 하는데, v2 다음 버전부터는 그냥 바로 사용하실 수 있습니다.

Sass 가 익숙하지 않은 분들은 제가 쓴 포스트Sass 가이드 를 참고해보시는것을 권장드립니다.

Sass 에서는 두가지의 확장자 (.scss/.sass) 를 지원합니다. Sass 가 처음 나왔을떈 sass 만 지원되었고, sass 는 문법이 아주 다른데요:

.sass

$font-stack:    Helvetica, sans-serif
$primary-color: #333

body
  font: 100% $font-stack
  color: $primary-color

.scss

$font-stack:    Helvetica, sans-serif;
$primary-color: #333;

body {
  font: 100% $font-stack;
  color: $primary-color;
}

더 많은 차이점들은 여기 서 비교해볼 수 있습니다. 보통 scss 문법이 더 많이 사용되므로, 우리는 .scss 확장자로 스타일을 작성하겠습니다.

한번 새 컴포넌트를 만들어서 Sass 를 사용해볼게요!

우선, node-sass 라는 라이브러리를 설치해야합니다. 이 라이브러리는 Sass 를 CSS 로 변환해줍니다.

$ yarn add node-sass

다음, src 디렉토리에 SassComponent.scss 파일을 생성하세요.

SassComponent.scss

// 변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
$green: #40c057;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;
// mixin 만들기 (재사용되는 스타일 블록을 함수처럼 사용 할 수 있음)
@mixin square($size) {
  $calculated: 32px * $size;
  width: $calculated;
  height: $calculated;
}

.SassComponent {
  display: flex;
  .box {
    background: red; // 일반 CSS 에선 .SassComponent .box 와 마찬가지
    cursor: pointer;
    transition: all 0.3s ease-in;
    &.red {
      // .red 클래스가 .box 와 함께 사용 됐을 때
      background: $red;
      @include square(1);
    }
    &.orange {
      background: $orange;
      @include square(2);
    }
    &.yellow {
      background: $yellow;
      @include square(3);
    }
    &.green {
      background: $green;
      @include square(4);
    }
    &.blue {
      background: $blue;
      @include square(5);
    }
    &.indigo {
      background: $indigo;
      @include square(6);
    }
    &.violet {
      background: $violet;
      @include square(7);
    }
    &:hover {
      // .box 에 마우스 올렸을 때
      background: black;
    }
  }
}

SassComponent.js 컴포넌트 파일도 만드세요.

SassComponent.js

import React from 'react';
import './SassComponent.scss';

const SassComponent = () => {
  return (
    <div className="SassComponent">
      <div className="box red" />
      <div className="box orange" />
      <div className="box yellow" />
      <div className="box green" />
      <div className="box blue" />
      <div className="box indigo" />
      <div className="box violet" />
    </div>
  );
};

export default SassComponent;

그리고 해당 컴포넌트를 App.js 에서 렌더링하세요:

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

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

export default App;

utils 함수 분리하기

자주 사용 될 수도 있는 Sass 변수 및 믹스인을 따로 파일로 분리해보겠습니다.

src 디렉토리 안에 styles 라는 디렉토리를 생성하고, 그 안에 utils.scss 파일을 만들어서, 기존 SassComponent 스타일 코드에 작성됐던 변수들과 믹스인을 잘라내서 이동시키겠습니다.

src/styles/utils.scss

// 변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
$green: #40c057;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;
// mixin 만들기 (재사용되는 스타일 블록을 함수처럼 사용 할 수 있음)
@mixin square($size) {
  $calculated: 32px * $size;
  width: $calculated;
  height: $calculated;
}

이제, SassComponent.scss 에서 위 파일에서 선언한 것들을 사용해보겠습니다. 다른 scss 파일을 불러올 땐 @import 구문을 사용합니다.

src/SassComponent.scss

@import './styles/utils.scss';
.SassComponent {
  display: flex;
  .box {
    background: red; // 일반 CSS 에선 .SassComponent .box 와 마찬가지
    cursor: pointer;
    transition: all 0.3s ease-in;
    &.red {
      // .red 클래스가 .box 와 함께 사용 됐을 때
      background: $red;
      @include square(1);
    }
    &.orange {
      background: $orange;
      @include square(2);
    }
    &.yellow {
      background: $yellow;
      @include square(3);
    }
    &.green {
      background: $green;
      @include square(4);
    }
    &.blue {
      background: $blue;
      @include square(5);
    }
    &.indigo {
      background: $indigo;
      @include square(6);
    }
    &.violet {
      background: $violet;
      @include square(7);
    }
    &:hover {
      // .box 에 마우스 올렸을 때
      background: black;
    }
  }
}

동일하게 작동하는지 확인해보세요!

sass-loader 설정 커스터마이징하기

이 부분은 Sass 를 사용 할 때 필수적인 작업은 아니지만, 해두면 좋을 수도 있는 작업입니다! 예를들어서, 방금 우리가 SassComponent 에서 styles/utils.scss 를 불러올 때, @import '../styles/utils.scss';

이런식으로 구현을 했었는데요, 만약에 프로젝트에 디렉토리를 깊숙한 구조로 만든다면, (예: src/components/somefeature/ThisComponent.js) import '../../../styles/utils.scss' 이런식으로 한참 거슬러올라가야 한다는 단점이 있습니다.

이 문제점은 방법들은 Webpack에서 Sass 를 처리하는 sass-loader 의 설정을 커스터마이징하여 해결 할 수 있습니다. 그렇지만, create-react-app 으로 만든 프로젝트는 프로젝트 구조의 복잡도를 낮추기 위하여 세부 설정들이 모두 숨어있습니다. 이를 커스터마이징 하기 위해서는 yarn eject 명령어를 통하여 세부설정을 다시 밖으로 꺼내주어야 합니다.

create-react-app 으로 만든 프로젝트는 기본적으로 .git 설정도 되어있는데요, yarn eject 는 아직 git 에 커밋되지 않은 변화가 있다면 진행이 되지 않으니, 먼저 커밋을 해주어야 합니다.

VSCode 의 git UI 를 사용하셔서 현재까지 한 작업을 커밋하시거나, 다음 명령어를 통해서 커밋을 하세요.

$ git add .
$ git commit -m'Commit before yarn eject'

그리고 나서, yarn eject 명령어를 실행합니다.

$ yarn eject
yarn run v1.12.0
warning ../package.json: No license field
$ react-scripts eject
? Are you sure you want to eject? This action is permanent. (y/N) y

이제 config 경로가 프로젝트에 생겼을텐데요, 그 안에 webpack.config.js 를 열어보세요.

그리고, sassRegex 를 찾아보시면

            {
              test: sassRegex,
              exclude: sassModuleRegex,
              use: getStyleLoaders(
                {
                  importLoaders: 2,
                  sourceMap: isEnvProduction && shouldUseSourceMap,
                },
                'sass-loader'
              ),
              sideEffects: true,
            },

이런 블록이 보일텐데, 여기서 use: 부분을 문자열을 다음과 같이 교체하세요:

            {
              test: sassRegex,
              exclude: sassModuleRegex,
              use: getStyleLoaders({
                importLoaders: 2,
                sourceMap: isEnvProduction && shouldUseSourceMap
              }).concat({
                loader: require.resolve('sass-loader'),
                options: {
                  includePaths: [paths.appSrc + '/styles'],
                  sourceMap: isEnvProduction && shouldUseSourceMap
                }
              }),
              // Don't consider CSS imports dead code even if the
              // containing package claims to have no side effects.
              // Remove this when webpack adds a warning or an error for this.
              // See https://github.com/webpack/webpack/issues/6571
              sideEffects: true
            },

getStylesLoaders 에서 두번째 파라미터로는 문자열형태로밖에 받아오지 못하니, 위와 같은 형태로 getStyleLoaders 로 만든 배열 뒷부분에다가 우리가 사용할 loader를 options 와 함께 적용을 해주었습니다.

작성 후, 서버를 껐다가 재시작하세요.

이제, utils.scss 파일을 불러올 때, 현재 수정하고 있는 scss 파일 경로가 어디에 위치하더라도, 앞부분에 상대경로를 입력 할 필요 없이 styles 디렉토리 기준 절대경로로 불러올 수 있습니다.

@import 'utils.scss';

한번, SassComponent.scss 파일에서 기존 import 구문을 위와 같이 수정해보고 적용이 잘 되는지 확인해보세요.

이제 utils 를 사용 하는 컴포넌트가 있다면 위 한줄만 붙여넣어주시면 됩니다.

하지만! 이렇게 utils.scss 를 포함시키는것 조차 귀찮을 수도 있을 것 입니다. 그럴땐, sass-loader 의 data 설정을 사용하시면 됩니다. data 속성은, Sass 파일을 읽을 때 마다 앞부분에 특정 코드를 포함시켜줍니다.

webpack.config.js 를 열어서 data 를 포함시켜보세요:

            {
              test: sassRegex,
              exclude: sassModuleRegex,
              use: getStyleLoaders({
                importLoaders: 2,
                sourceMap: isEnvProduction && shouldUseSourceMap
              }).concat({
                loader: require.resolve('sass-loader'),
                options: {
                  includePaths: [paths.appSrc + '/styles'],
                  sourceMap: isEnvProduction && shouldUseSourceMap,
                  data: `@import 'utils';`
                }
              }),
              // Don't consider CSS imports dead code even if the
              // containing package claims to have no side effects.
              // Remove this when webpack adds a warning or an error for this.
              // See https://github.com/webpack/webpack/issues/6571
              sideEffects: true
            },

이렇게 하고 나면, 모든 scss 파일에서 utils 를 사용 할 수 있게 되므로, SassComponent 에서 맨 위 import 구문을 지워도 정상적으로 작동 할 것입니다.

node_modules 에서 불러오기

yarn혹은 npm 을 통하여 설치한 Sass 라이브러리를 불러오는 방법을 알아봅시다. 가장 기본적인 방식으로는

@import '../../../node_modules/library/styles';

과 같은 형식으로 위로 거슬러 올라가주어야 합니다. 하지만, 이것보다 더욱 쉬운 방법이 있는데요, 바로 물결 ~ 를 사용 하는 것 입니다.

@import '~library/styles';

제가 자주 사용하는 Sass 라이브러리중에서는 반응형 디자인을 쉽게 해주는 include-media 와 색상 팔레트인 open-color 가 있는데요, 만약 이를 사용하게 된다면, 다음과 같이 불러오면 됩니다.

$ yarn add open-color include-media
@import '~include-media/dist/include-media';
@import '~open-color/open-color';

패키지 디렉토리 안에 있는 scss 파일을 불러와야 하므로, node_modules 안으로 들어가서 디렉토리를 확인 후 필요한 파일을 불러오시면 됩니다.

CSS Module

CSS Module 은 CSS 클래스를 불러와서 사용 할 때 [파일이름][클래스이름]_[해쉬값] 형태로 클래스네임을 자동으로 고유한 값으로 만들어주어서 컴포넌트 스타일 중첩현상을 방지해주는 기술입니다. 이를 사용하기 위해선, [파일이름].module.css 이런식으로 파일을 저장하셔야 합니다.

한번, CSSModule.module.css 라는 스타일을 먼저 작성해봅시다.

src/CSSModule.module.css

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

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

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

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

자바스크립트로 컴포넌트도 작성해볼까요?

src/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;

위 코드처럼 styles 를 불러오면 하나의 객체를 전달받게 되는데 그 안에는 CSS Module 에서 사용한 클래스 이름과, 해당 이름을 고유화시킨 값이 key-value 형태로 들어있습니다.

한번 console.log(styles) 를 하면 이런 결과가 나타납니다:

{
  wrapper: "CSSModule_wrapper__CUMkx"
}

그리고, 이걸 사용하기 위해선 className={styles.[클래스이름]} 형태로 설정을 해주시면 됩니다.

다 작성하셨으면 App 컴포넌트에서 CSSModule 컴포넌트를 렌더링해봅시다.

src/App.js

import React, { Component } from 'react';
import CSSModule from './CSSModule';
class App extends Component {
  render() {
    return <CSSModule />;
  }
}

export default App;

이런식으로, 잘 작동하나요?

만약에 CSS Module 을 사용한 클래스이름을 두개 이상 적용 할 때는 이렇게 하시면 됩니다:

src/CSSModule.module.css

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

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

.inverted {
  color: black;
  background: white;
}

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

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

src/CSSModule.js

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

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

export default CSSModule;

CSS Module 이 잘 작동하고 있나요?

classNames

classNames 는 CSS 클래스를 조건부로 설정 할 때 매우 유용한 라이브러리입니다. 그리고, CSS Module 을 사용 할 때 이 라이브러리를 함께 사용 한다면 여러 클래스를 적용할 때 편해집니다.

우선, classNames 를 설치하세요:

$ 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 이나 false 에 따라 highlighted 라는 클래스가 적용 될 것이고, 추가적으로 theme 으로 전달받는 문자열이 그대로 클래스에 적용 될 것입니다.

만약에 이런 라이브러리의 도움을 받지 않는다면 이런 형식으로 처리를 하셔야 될 겁니다:

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

classNames 를 쓰는 것이 훨씬 가독성이 높지요?

추가적으로, CSS Module 과 함께 쓸 땐 어떻게 편해질 수 있는지 알아보겠습니다.

classNames 를 불러올때 classnames/bind 를 사용하면 클래스를 넣어줄 때 마다 styles.[클래스] 형식으로 할 필요 없이, 사전에 미리 styles 에서 받아와서 사용하게끔 설정해두고 cx('class1', 'class2') 형태로 사용 할 수 있게 됩니다.

한번 CSSModule.js 컴포넌트를 다음과 같이 작성해보세요:

src/CSSModule.js

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;

classnames/bind 를 사용하면, CSS Module 을 사용 할 때 클래스를 여러개 설정하거나 또는 조건부로 설정을 하게 될 때, 훨씬 편하게 작성 할 수 있겠죠?

Sass 와 함께 사용하기

Sass 를 사용할때도 파일이름 뒤에 .module.scss 을 입력해주면 CSS Module 로 사용 할 수 있습니다. 한번 파일 이름을 변경해보세요. 스타일 코드도 조금 바꾸겠습니다.

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 styles from './CSSModule.module.scss';

이전과 똑같이 보여지고 있나요?

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

우리가 CSS Module 에서 글로벌 클래스를 정의 할 때 :global 을 썼었던 것 처럼, CSS Module 이 아닌 일반 .css/.scss 파일에서도 :local 을 통하여 CSS Module 을 사용 할 수도 있습니다.

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

styled-components

styled-components 는 현존하는 리액트 CSS-in-JS 관련 라이브러리 중에서 가장 잘나가는 라이브러리입니다. CSS-in-JS 는 이름이 그렇듯, 자바스크립트 파일 안에 CSS 를 작성하는 형태입니다. 해외의 큰 기업 - Atlassian, Reddit, coinbase 등에서도 사용되고 있고, 국내에서도 사용하는곳이 꽤 있습니다 - Channel.io, Huiseoul, Tumblebug 등..

styled-components 의 대체제는 현재 대표적으로 emotion 이 있습니다. 작동 방식은 꽤나 비슷합니다.

취향에 따라 갈릴수도 있지만, 분명히 알아두면 좋은 라이브러리인건 확실합니다.

한번 사용해볼까요?

$ yarn add styled-components

이 라이브러리를 통해 한번 예제 컴포넌트를 만들어보겠습니다. 그냥 하나의 자바스크립트 파일안에 스타일까지 작성 할 수 있기 때문에 .css/.scss 파일 같은걸 만들 고민은 안하셔도 된다는게 큰 이점입니다.

한번 예제 컴포넌트 코드를 작성해보세요:

src/StyledComponent.js

import React from 'react';
import styled, { css } from 'styled-components';

const Box = styled.div`
  /* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
  background: ${props => props.color || 'blue'};
  padding: 1rem;
  display: flex;
`;

const Button = styled.button`
  background: white;
  color: black;
  border-radius: 4px;
  padding: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  font-size: 1rem;
  font-weight: 600;

  /* & 문자를 사용하여 Sass 처럼 자기 자신 선택 가능 */
  &:hover {
    background: rgba(255, 255, 255, 0.9);
  }

  /* 다음 코드는 inverted 값이 true 일 때 특정 스타일을 부여해줍니다. */
  ${props =>
    props.inverted &&
    css`
      background: none;
      border: 2px solid white;
      color: white;
      &:hover {
        background: white;
        color: black;
      }
    `};
  & + button {
    margin-left: 1rem;
  }
`;

const StyledComponent = () => (
  <Box color="black">
    <Button>안녕하세요</Button>
    <Button inverted={true}>테두리만</Button>
  </Box>
);

export default StyledComponent;

Tagged Template Literal

styled-components 에서는 스타일을 입력 할 때 Tagged 템플릿 리터럴(Template Literal) 이라는 ES6 문법을 사용합니다. 이 문법을 사용하는 이유는, `` 를 사용할 때 내부에 JavaScript 객체나 함수가 전달 될 때 이를 따로 추출하기 위함입니다.

예를들어서:

`hello ${{foo: 'bar' }} ${() => 'world'}!`
// 결과: "hello [object Object] () => 'world'!"

위 코드는 [object Object] 이런식으로 문자열로 들어가게되면서 형태를 잃어버리게 되는데요, 만약에 함수를 다음과 같이 만들어서 사용하면 이 템플릿 리터럴 안에 넣어준 값들을 온전히 알아낼 수 있게 됩니다.

function tagged(...args) {
    console.log(args);
}
tagged`hello ${{foo: 'bar' }} ${() => 'world'}!`

이렇게, 사이사이에 들어가는 JavaScript 값의 원본 값을 그대로 추출 할 수 있습니다.

스타일링 된 엘리먼트 만들기

스타일링 된 엘리먼트를 만들 땐, 상단에서 styled 를 불러오고 styled.태그명 을 사용하여 구현합니다:

import styled from 'styled-components';

const MyComponent = styled.div`
  font-size: 2rem;
`;

저 자리에 button 이던, input 이던, 원하는걸 넣으시면 됩니다.

하지만, 만약에 보여줘야 할 태그 형식이 유동적이거나, 아니면 특정 컴포넌트에 스타일링을 해야 하는 상황이라면 다음과 같은 형태로 구현 할 수 있습니다:

// 문자열로 styled 의 인자로 전달
const MyInput = styled('input')`
  background: gray;
`
// 아예 컴포넌트 형식의 값을 넣어줌
const StyledLink = styled(Link)`
  color: blue;
`

스타일에서 props 조회하기

스타일링 한 컴포넌트에 전달하는 props 값을 스타일쪽에서 그대로 사용 하실 수 도 있습니다.

const Box = styled.div`
  /* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
  background: ${props => props.color || 'blue'};
  padding: 1rem;
  display: flex;
`;

props 에 따른 조건부 스타일링

일반 CSS 클래스를 사용했더라면 주로 클래스이름으로 조건부 스타일링을 해왔었을텐데요, styled-components 에서는 그냥 props 로도 처리 가능합니다. 이렇게 말이죠:

import styled, { css } from 'styled-components';
/* 단순 변수의 형태가 아니라 여러줄의 스타일 구문을 조건부로 설정해야 하는 경우엔
css 를 불러와야합니다. 
*/
const Button = styled.button`
  background: white;
  color: black;
  border-radius: 4px;
  padding: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  font-size: 1rem;
  font-weight: 600;

  /* & 문자를 사용하여 Sass 처럼 자기 자신 선택 가능 */
  &:hover {
    background: rgba(255, 255, 255, 0.9);
  }

  /* 다음 코드는 inverted 값이 true 일 때 특정 스타일을 부여해줍니다. */
  ${props =>
    props.inverted &&
    css`
      background: none;
      border: 2px solid white;
      color: white;
      &:hover {
        background: white;
        color: black;
      }
    `};
  & + button {
    margin-left: 1rem;
  }
`;

한번 App에서 렌더링해볼까요?

src/App.js

import React, { Component } from 'react';
import StyledComponent from './StyledComponent';

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

export default App;

반응형 디자인

styled-components 에서 반응형 디자인은 어떻게 하는지 알아봅시다. 일단, 일반 CSS 랑 똑같이 하면 되긴 하는데요:

src/StyledComponent.js 의 Box 컴포넌트

const Box = styled.div`
  /* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
  background: ${props => props.color || 'blue'};
  padding: 1rem;
  display: flex;
  /* 기본적으로는 1024px 에 가운데 정렬을 하고
    가로 크기가 작아짐에 따라 사이즈를 줄이고
    768px 미만으로 되면 꽉 채웁니다 */
  width: 1024px;
  margin: 0 auto;
  @media (max-width: 1024px) {
    width: 768px;
  }
  @media (max-width: 768px) {
    width: 100%;
  }
`;

이런 작업을 함수화하여 훨씬 더 쉽게 할 수도 있답니다:

src/StyledComponent.js 상단부

import React from 'react';
import styled, { css } from 'styled-components';

const sizes = {
  desktop: 1024,
  tablet: 768
};

// 위에있는 size 객체에 따라 자동으로 media 쿼리 함수를 만들어줍니다.
// 참고: https://www.styled-components.com/docs/advanced#media-templates
const media = Object.keys(sizes).reduce((acc, label) => {
  acc[label] = (...args) => css`
    @media (max-width: ${sizes[label] / 16}em) {
      ${css(...args)};
    }
  `;

  return acc;
}, {});

const Box = styled.div`
  /* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
  background: ${props => props.color || 'blue'};
  padding: 1rem;
  display: flex;
  width: 1024px;
  margin: 0 auto;
  ${media.desktop`width: 768px;`}
  ${media.tablet`width: 768px;`};
`;

어떤가요? 훨씬 간단해졌죠? 지금은 media 를 StyledComponent.js 에서 만들어주긴 했지만, 실제로 사용한다면 아예 다로 다른 파일로 분리시켜서 여기저기서 불러와서 사용하는게 훨씬 편할 것입니다.

기타

styled-components 는 정말 유용한 라이브러리이고 프로덕션 레벨에서도 사용가능한 충분히 성장된 라이브러리입니다.

글로벌 색상 관리를 도와줄 수 있는 Theme 이라는 기능도 있는데, 저는 딱히 엄청 선호하지는 않습니다. 단순히 자주사용되는 색상들을 담기 위한 것 이라면 그냥 객체형태로 저장을 해두고 불러와서 사용하는것이 훨씬 간단하며, 이 기능은 사용자가 서비스의 테마를 직접 변경 할 수도 있는 경우에 사용하면 조금 유용 할 수도 있습니다.

서버 사이드렌더링 도 지원이 되고있습니다.

정리

정말 다양한 리액트 컴포넌트 스타일링 방식을 배워보았습니다. 모두 쓸모있는 기술들이며, 이러한 방식들 중에서 뭘 사용 할 지 선택하는것은 여러분의 몫입니다.

results matching ""

    No results matching ""