9장 컴포넌트 스타일링 (리액트를 다루는 기술)

김지원·2020년 11월 16일
0

React

목록 보기
16/31

리액트에서 컴포넌트를 스타일링할 때는 다양한 방식을 사용할 수 있습니다.

  • 일반 CSS: 컴포넌트를 스타일링하는 가장 기본적인 방식입니다.
  • Sass: 자주 사용되는 CSS 전처리기 중 하나로 확장된 CSS 문법을 사용하여 CSS 코드를 더욱 쉽게 작성할 수 있도록 해줍니다.
  • CSS Module: 스타일을 작성할 때 CSS 클래스가 다른 CSS 클래스의 이름과 절대 충돌하지 않도록 파일마다 고유한 이름을 자동으로 생성해 주는 옵션입니다.
  • styled-components: 스타일을 자바스크립트 파일에 내장시키는 방식으로 스타일을 작성함과 동시에 해당 스타일이 적용된 컴포넌트를 만들 수 있게 해줍니다.

일반 CSS

CSS를 작성할 때 가장 중요한 점은 CSS 클래스를 중복되지 않게 만드는 것입니다.

중복되는 것을 방지하는 방법:
1. 이름을 지을 때 특별한 규칙을 사용하여 짓기
2. CSS selector를 활용하기

이름 짓는 규칙

컴포넌트 이름-클래스 형태로 지어 중복되는 것을 방지할 수 있다.
비슷한 방식으로 BEM 네이밍이라는 방식도 있습니다.
이름을 지을 때 일종의 규칙을 준수하여 해당 클래스가 어디에서 어떤 용도로 사용되는지 명확하게 작성하는 방식입니다. ex) .card_title-primary

CSS Selector

CSS Selector를 사용하면 CSS 클래스가 특정 클래스 내부에 있는 경우에만 스타일을 적용할 수 있습니다.
ex) .App 안에 들어 있는 .logo에 스타일을 적용하고 싶다면

.App .logo{
...
}

Sass 사용하기

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

create-react-app 구버전에서는 Sass를 사용하려면 추가 작업이 필요했는데, v2 버전부터는 별도의 추가 설정 없이 바로 사용할 수 있습니다.

Sass에서는 두 가지 확장자 .scss 와 .sass를 지원합니다.
두 개의 주요 차이점은 .sass 확장자는 중괄호({})와 세미콜론(;)을 사용하지 않습니다.

설치
node-sass 라이브러리를 설치해주어야 합니다.
이 라이브러리는 Sass를 CSS로 변환해 줍니다.
$npm add node-sass

SassComponent.scss 코드

//변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$green: #40c057;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;

//믹스인 만들기(재사용되는 스타일 블록을 함수처럼 사용할 수 있음)
@mixin square($size) {
  $calculated: 32px * $size;
  width: $calculated;
  height: $calculated;
}

.SassComponent{
  display: flex;
  .box{ //일반 CSS에서는 .SassComponent .box와 마찬가지
    background: red;
    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;

utils 함수 분리하기

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

src/styles/utils.scss 코드

//변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
$green: #40c057;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;

//믹스인 만들기(재사용되는 스타일 블록을 함수처럼 사용할 수 있음)
@mixin square($size) {
  $calculated: 32px * $size;
  width: $calculated;
  height: $calculated;

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

이 작업은 Sass를 사용할 때 반드시 해야 하는 것은 아니지만, 해두면 유용합니다.

예를 들어 SassComponent에서 utils를 불러올 때 프로젝트에 디렉터리를 많이 만들어서 구조가 깊어졌다면 상위 폴더로 한참 거슬러 올라가야하는 단점이 있습니다.
ex) @import '../../../styles/utils';

이 문제점은 웹팩에서 Sass를 처리하는 sass-loader의 설정을 커스터마이징하여 해결할 수 있습니다.

create-react-app으로 만든 프로젝트를 커스터마이징하려면 npm run eject 명령어를 통해 세부 설정을 밖으로 꺼내 주어야 합니다.

eject을 하면 프로젝트 디렉터리에 config라는 디렉터리가 생성되었을 것입니다.

그 디렉터리 안에 있는 webpack.config.js에서 "sassRegex"를 찾아 concat을 통해 커스터마이징된 sass-loader 설정을 넣어 줍니다.

 {
              test: sassRegex,
              exclude: sassModuleRegex,
              use: getStyleLoaders(
                {
                  importLoaders: 3,
                  sourceMap: isEnvProduction
                    ? shouldUseSourceMap
                    : isEnvDevelopment,
                }.concat({
                  loader: require.resolve("sass-loader"),
                  options: {
                    sassOptions: {
                      includePaths: [paths.appSrc + "/styles"],
                    },
                    sourceMap: isEnvProduction && shouldUseSourceMap,
                  },
                })
              ),

@import 'utils.scss' 이제부터 utils.scss를 사용하는 컴포넌트가 있다면 위 한 줄만 넣어 주면 됩니다.

node_modules에서 라이브러리 불러오기

Sass의 장점 중 하나는 라이브러리를 쉽게 불러와서 사용할 수 있다는 점입니다.

npm을 통해 설치한 라이브러리를 사용하는 가장 기본적인 방법은?
상대 경로를 사용하여 node_modules까지 들어가서 불러오는 방법입니다.
@import '../../../node_modules/library/styles';

이보다 더 쉬운 방법은
@import '~library/styles';

물결 문자를 사용하면 자동으로 node_modules에서 라이브러리 디렉터리를 탐지하여 스타일을 불러올 수 있습니다.

ex) 반응형 디자인을 쉽게 만들어주는 include-media와 편리한 색상 팔레트인 open-color를 설치
$npm add open-color include-media

utils.scss 코드에 라이브러리 불러오기

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

CSS Module

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

v2 버전 이상부터는 css-loader 설정을 할 필요 없이 .module.css 확장자로 파일을 저장하기만 하면 CSS Module이 적용됩니다.

CSS Module.module.css 코드

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

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

/*글로벌 CSS를 작성하고 싶다면*/
:global .something {
    font-weight: 800;
    color:aqua;
}

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

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

CSS Module.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에서 사용한 클래스 이름과 해당 이름을 고유화한 값이 키-값 형태로 들어 있습니다.
ex) { wrapper: "CSSModule_wrapper__1SbdQ" }

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

CSS Module을 사용한 클래스 이름을 두 개 이상 적용할 때는

<div className={`${styles.wrapper} ${styles.inverted}`}>

템플릿 리터럴 문법을 사용하고 싶지 않다면

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

classnames

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

설치
$ npm add classnames

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

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

라이브러리의 도움을 받지 않는다면?

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

Sass와 함께 사용하기

Sass를 사용할 때도 파일 이름 뒤에 .module.scss확장자를 사용해 주면 CSS Module로 사용할 수 있습니다.

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

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

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

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

styled-components

컴포넌트 스타일링의 또 다른 패러다임은 자바스크립트 파일 안에 스타일을 선언하는 방식입니다.
이 방식을 'CSS-in-JS'라고 부릅니다.
이와 관련된 라이브러리는 정말 많습니다.
이 중에서 개발자들이 가장 선호하는 styled-components를 알아보겠습니다.

설치
$npm install styled-components

이점: styled-components를 사용하면 자바스크립트 파일 하나에 스타일까지 작성할 수 있기 때문에 .css 또는 .scss 확장자를 가진 스타일 파일을 따로 만들지 않아도 된다.

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

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 = () => {
  return (
    <Box color="black">
      <Button>안녕하세요</Button>
      <Button inverted={true}>테두리만</Button>
    </Box>
  );
};

export default StyledComponent;

Tagged 템플릿 리터럴

스타일을 작성할 때 ` 을 사용하여 만든 문자열에 스타일 정보를 넣어 주었습니다.
여기서 사용한 문법을 Tagged 템플릿 리터럴이라고 부릅니다.
CSS Module을 배울 때 나온 일반 템플릿 리터럴과 다른 점은 템플릿 안에 자바스크립트 객체나 함수를 전달 할 때 온전히 추출할 수 있다는 것입니다.

EX)

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

템플릿에 객체를 넣거나 함수를 넣으면 형태를 잃어 버리게 됩니다.
객체는 "[object Object]"로 변환되고, 함수는 함수 내용이 그대로 문자열화되어 나타납니다.

함수를 작성하고 나서 해당 함수 뒤에 템플릿 리터럴을 넣어 준다면, 템플릿 안에 넣은 값은 온전히 추출할 수 있습니다.

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

styled-components는 이러한 속성을 사용하여 styled-components로 만든 컴포넌트의 props를 스타일 쪽에서 쉽게 조회할 수 있도록 해줍니다.

사용해야 할 태그명이 유동적이거나 특정 컴포넌트 자체에 스타일링해 주고 싶다면 다음과 같은 형태로 구현할 수 있습니다.

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

반응형 디자인

일반 CSS를 사용할 때와 똑같이 media 쿼리를 사용하면 됩니다.
@media (max-width: 1024px) { width: 768px; }

이러한 작업을 여러 컴포넌트에서 반복해야한다면 조금 귀찮을 수도 있습니다.
그럴때는 이 작업을 함수화하여 간편하게 사용할 수 있습니다.
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`
    @medial (max-width: ${sizes[label] / 16}em) {
      ${css(...args)}
    }
  `;
  return acc;
}, {});

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

...

실제로 사용한다면 아예 다른 파일로 모듈화한 뒤 여기저기서 불러와 사용하는 방식이 훨씬 편할 것입니다.

0개의 댓글

관련 채용 정보