9장 컴포넌트 스타일링

Posted by : at

Category : React


2021-07-03

9장 컴포넌트 스타일링


리액트에서 컴포넌트를 스타일링할 때는 다양한 방식을 사용할 수 있다. 여러 방식 중에서 딱히 정해진 방식은 없다. 이번 장에서는 어떤 방식이 있는지 알아보고 자주 사용하는 방식을 하나씩 사용해 보도록 한다.



  • 일반 CSS : 컴포넌트를 스타일링하는 가장 기본적인 방식이다.

  • Sass : 자주 사용되는 CSS 전처리기 중 하나로 확장된 CSS 문법을 사용하여 CSS 코드를 더욱 쉽게 작성할 수 있도록 한다.

  • CSS Module : 스타일을 작성할 때 CSS 클래스가 다른 CSS 클래스의 이름과 절대 충돌하지 않도록 파일마다 고유한 이름을 자동으로 생성해 주는 옵션이다.

  • styled-components : 스타일을 자바스크립트 파일에 내장시키는 방식으로 스타일을 작성함과 동시에 해당 스타일이 적용된 컴포넌트를 만들 수 있게 해준다.

해당 챕터는 실습 코드만 인용하여 사용하겠음.



9.1 가장 흔한 방식, 일반 CSS


App.css

.App {
  text-align: center;
}

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

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

해당 파일처럼 .css 파일을 따로 만들어 import 하는 방식을 가장 일반적인 방식으로 사용한다. 이때 가장 중요한 점은 CSS 클래스를 중복되지 않게 만드는 것이다. CSS 클래스가 중복되는 것을 방지하는 여러가지 방식이 있는데, 그중 하나는 이름을 지을 때 특별한 규칙을 사용하는 것이고, 또 다른 하나는 CSS Selector를 사용하는 것이다.


App.css 파일을, 읽어보면 클래스 이름이 컴포넌트 이름-클래스 형태로 지어져 있다. (예시 : .App-logo ) 클래스 이름에 컴포넌트 이름을 포함시킴으로써 다른 컴포넌트에서 실수로 중복되는 클래스를 만들어 사용하는 것을 방지할 수 있다. 비슷한 방식으로 BEM 네이밍 방식이 있다.


CSS Selector를 사용하면 CSS 클래스가 특정 클래스 내부에 있는 경우에만 스타일을 적용할 수 있다.

App.js 중 header

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

/* .App 안에 들어 있는 header
    header 클래스가 아닌 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;
}

이런 식으로 컴포넌트 최상위 html 요소에는 컴포넌트의 이름으로 클래스 이름을 짓고 그 내부에서는 소문자를 입력하거나, header와 같은 태그를 사용하여 클래스 이름이 불필요한 경우에는 아예 생략 가능하다.



9.2 Sass 사용하기


Sass(Syntactically Awesome Style Sheets) 는 CSS 전저치리기로 복잡한 작업을 쉽게 할 수 있도록 해 주고, 스타일 코드의 재활용성을 높여 줄 뿐만 아니라 코드의 가독성을 높여서 유지 보수를 더욱 쉽게 해준다.


Sass에서는 두 가지 확장자 .scss.sass 를 지원한다.

.sass

$front-stack : Helvetiac, sans-serif
$primary-color : #333

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


.scss

$front-stack : Helvetiac, sans-serif;
$primary-color : #333;

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

주요 차이점은 .sass 확장자는 중괄호와 세미콜론을 사용하지 않는다. 반면 .scss 확장자는 기존 css를 작성하는 방식과 비교해서 문법이 크게 다르지 않다.


Sass 를 사용하기 위해서는 node-sass 라는 라이브러리를 설치해야 한다.

$ yarn add node-sass


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

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;


여러 파일에서 사용될 수 있는 Sass 변수 및 믹스인은 다른 파일로 따로 분리하여 작성한 뒤 필요한 곳에서 쉽게 불러와 사용 가능하다.

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

@import './styles/utils.scss'; 를 통해서 SassComponent.scss 파일에 import 해주도록 한다.


Sass 를 사용할 때 방금과 같이 utils를 불러오는 작업에서 @import 를 통해서 가져오는데, 경로가 길어진다는 단점이 있다. 이를 해결하는 방법은 Webpack에서 Sass를 처리하는 sass-loader의 설정을 커스터마이징 하는 것이다.


CRA(create-react-app)은 프로젝트의 구조의 복잡도를 낮추기 위해 세부 설정들이 숨어있는데 이를 커스터마이징 하기 위해서는 yarn eject 가 필요하다. yarn eject는 git에 커밋되지 않은 변화가 있으면 진행되지 않기 때문에 먼저 커밋 해줘야 한다.


$ 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라는 키워드를 찾아서 다음과 같이 수정한다.

webpack.config.js - sassRegex

    {
              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
                }
              }),
              sideEffects: true
    },

이렇게 설정하면 utils.scss 파일을 불러올 때 현재 수정하고 있는 scss 파일 경로가 어디에 위치하더라도 앞부분에 상대 경로를 입력할 필요 없이 styles 디렉터리 기준 절대 경로를 사용하여 불러올 수 있다.


     {
              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';`
                }
              }),
              sideEffects: true
    },

data: @import 'utils';

위의 코드를 통해서 기본적으로 import를 하도록 설정할 수 있다.


Sass의 장점 중 하나는 라이브러리를 쉽게 불러와서 사용할 수 있다는 점이다. yarn을 통해 설치한 라이브러리를 사용하는 가장 기본적인 방법은 상대 경로를 사용하여 node_modules까지 들어가서 불러오는 방법이다.

@import ‘../../../node_modules/library/styles’;


디렉토리의 경로를 표시하는 것은 번거로운 일이므로 물결 문자를 통해서 해결 가능하다. 물결 문자를 사용하면 자동으로 node_modules에서 라이브러리 디렉터리를 탐지하여 스타일을 불러올 수 있다.

@import ‘~library/styles’;



9.3 CSS Module

CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 고유한 값, 즉 [파일이름]_[클래스 이름]__[해시값] 형태로 자동으로 만들어서 컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해 주는 기술이다. .module.css 확장자로 파일을 저장하면 CSS Module이 적용된다.

CSSModule.module.css

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

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

/* 글로벌 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}>
      안녕하세요, 저는 <span className="something">CSS Module!</span>
    </div>
  );
};

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

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 문법 템플릿 리터럴을 사용하여 문자열을 합해 준 것이다.



9.4 styled-components


컴포넌트 스타일링의 또 다른 패러다임은 자바스크립트 파일 안에 스타일을 선언하는 방식이다. 이 방식을 'CSS-in-JS' 라고 한다. 관련된 라이브러리가 많지만 이중 가장 선호하는 styled-componens 에 대해서 알아보도록 하겠다.


해당 패키지를 yarn을 통해서 설치해 준다.

$ yarn add styled-components


StyledComponents.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;

styled-components와 일반 className을 사용하는 CSS/Sass를 비교했을 때, 가장 큰 장점은 props 값으로 전달해 주는 값을 쉽게 스타일에 적용할 수 있다는 것이다.

  • VS code를 사용할 때 styled-components를 위해 컴포넌트 내부에 작성한 스타일이 문자열로 간주되어 하이라이팅이 제대로 이루어지지 않는데 이는 vscode-styled-components extension을 설치하여 해결 가능하다.


styled-compoents는 Tagged 템플릿 리터럴을 사용하는데 템플릿 안에 자바스크립트 객체나 함수를 전달할 때 온전히 추출할 수 있는 기능이다. 이를 통해서 styled-components로 만든 컴포넌트의 props를 스타일 쪽에서 쉽게 조회할 수 있도록 해준다.

Tagged 템플릿 리터럴 설명 바로가기 MDN



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;
  }
`;

스타일 코드 여러 줄을 props에 따라 넣어 주어야 할 때는 CSS Styled-components에서 불러와야 한다.



반응형 디자인을 하는데 있어서는 일반 CSS랑 큰 차이가 없다. 하지만 이런 작업을 여러 컴포넌트에서 반복한다면 귀찮을 수 있기 때문에 styled-components 메뉴얼에서 제공하는 유틸 함수의 예시를 살펴보도록 하겠다.

예시

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;`};
`;

About YeongJun
YeongJun

Hi I am YeongJun, a Web Developer and Designer.

Email : dudwns1045@naver.com

Website : http://gitbub.com/dudwns9331

About yeongjun

강원대학교 컴퓨터공학과 컴퓨터과학전공 R.O.T.C 60th 127 학군단, 프론트엔드 개발 준비중 :D

Categories
Useful Links