상세 컨텐츠

본문 제목

[리액트 스타터 3] 9장. 컴포넌트 스타일링

22-23/22-23 리액트 스타터 3

by 케이비이 2022. 12. 1. 10:24

본문

728x90

9.1 가장 흔한 방식, 일반 CSS

CSS를 작성할 때 가장 중요한 점은 CSS 클래스를 중복되지 않게 만드는 것입니다. 이름을 지을 때 특별한 규칙을 사용하거나 CSS Selector를 활용할 수 있습니다.

 

9.1.1 이름 짓는 규칙

컴포넌트 이름-클래스 형태로 지어집니다. (ex: App-header) BEM 네이밍은 CSS 방법론 중 하나로, 이름을 지을 때 일종의 규칙을 준수하여 해당 클래스가 어디에서 어떤 용도로 사용되는지 명확하게 작성하는 방식입니다. (ex: .card__title-primary)

 

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

 

9.1.2 CSS Selector

 CSS 클래스가 특정 클래스 내부에 있는 경우에만 스타일을 적용할 수 있습니다.

/* .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;
}

 

9.2 Sass 사용하기

Sass는 CSS 전처리기로 복잡한 작업을 쉽게 할 수 있도록 해 주고, 스타일 코드의 재활용성을 높여 줄 뿐만 아니라 코드의 가독성을 높여서 유지 보수를 더욱 쉽게 해 줍니다. Sass에서는 두 가지 확장자 .scss와 .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;
}

.sass 확장자는 중괄호({})와 세미콜론(;)을 사용하지 않지만, .scss 확장자는 기존 CSS를 작성하는 방식과 비교해서 문법이 크게 다르지 않습니다. node-sass라는 라이브러리를 설치해 주면 Sass를 CSS로 변환해 줍니다.

//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;
//SassComponent.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;
}


.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;
    }
  }
}
//App.js
import React, { Component } from 'react';
import SassComponent from './SassComponent';
 
class App extends Component {
  render() {
    return (
      <div>
        <SassComponent />
      </div>
    );
  }
}
 
export default App;

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

 utils.scss 파일에서 선언한 변수와 믹스인을 SassComponent.scss에서 사용해 볼 때 @import 구문을 사용합니다.

//SassComponent.scss
@import './styles/utils';
.SassComponent {
  display: flex;
  .box {
    background: red; // 일반 CSS에서는 .SassComponent .box와 마찬가지
    cursor: pointer;
    transition: all 0.3s ease-in;
    (...)
  }
}

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

Sass의 장점 중 하나는 라이브러리를 쉽게 불러와서 사용할 수 있다는 점입니다. 상대 경로를 사용하여 node_modules까지 들어가서 불러오는 방법입니다.

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

더 쉬운 방법이 있는데, 바로 물결 문자(~)를 사용하는 방법입니다.

@import '~library/styles';

반응형 디자인을 쉽게 만들어 주는 include-media와 매우 편리한 색상 팔레트인 open-color yarn 명령어를 사용해 설치해 보세요.

$ yarn add open-color include-media

9.3 CSS Module

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

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




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

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

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

CSS Module이 적용된 스타일 파일을 불러오면 객체를 하나 전달받게 되는데 CSS Module에서 사용한 클래스 이름과 해당 이름을 고유화한 값이 키-값 형태로 들어 있습니다. 

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

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



.inverted {
  color: black;
  background: white;
  border: 1px solid black;
}



/* 글로벌 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} ${styles.inverted}`}>
      안녕하세요, 저는 <span className="something">CSS Module!</span>
    </div>
  );
};
 
export default CSSModule;

ES6 문법 템플릿 리터럴(Template Literal)을 사용하여 문자열을 합해 주었습니다. 

 

9.3.1 classnames

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

9.3.2 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';

이전과 똑같은 화면이 나타날 것입니다.

 

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

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

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

9.4 styled-components

CSS-in-JS 라이브러리 중에서 개발자들이 가장 선호하는 styled-components를 알아보겠습니다.  styled-components를 사용하면 자바스크립트 파일 하나에 스타일까지 작성할 수 있기 때문에 .css 또는 .scss 확장자를 가진 스타일 파일을 따로 만들지 않아도 된다는 큰 이점이 있습니다.

//StyledComponent.js
import React from 'react';
import styled, { css } from 'styled-components';


const Box = styled.div</span>
  <span class="co44">/* props</span><span class="co44">로</span> <span class="co44">넣어</span> <span class="co44">준</span> <span class="co44">값을</span> <span class="co44">직접</span> <span class="co44">전달해</span> <span class="co44">줄</span> <span class="co44">수</span> <span class="co44">있습니다</span><span class="co44">. */</span>
  <span class="co33">background</span><span class="co34">:</span> <span class="co49">${</span><span class="co33">props</span> <span class="co46">=&gt;</span> <span class="co34">props</span><span class="co33">.</span><span class="co34">color</span> <span class="co35">||</span> <span class="co31">'</span><span class="co31">blue</span><span class="co31">'</span><span class="co49">}</span><span class="co36">;</span>
  <span class="co33">padding</span><span class="co34">:</span> <span class="co32">1rem</span><span class="co36">;</span>
  <span class="co33">display</span><span class="co34">:</span> <span class="co33">flex</span><span class="co36">;</span>
<span class="co31">;



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</span>
      <span class="co33">background</span><span class="co34">:</span> <span class="co33">none</span><span class="co49">;</span>
      <span class="co33">border</span><span class="co34">:</span> <span class="co32">2px</span> <span class="co33">solid</span> <span class="co32">white</span><span class="co49">;</span>
      <span class="co33">color</span><span class="co34">:</span> <span class="co32">white</span><span class="co49">;</span>
      <span class="co34">&amp;</span><span class="co32">:hover</span><span class="co49"> {</span>
        <span class="co33">background:</span> <span class="co32">white</span><span class="co49">;</span>
        <span class="co33">color:</span> <span class="co32">black</span><span class="co49">;</span>
<span class="co49">      }</span>
    <span class="co31">};
  & + button {
    margin-left: 1rem;
  }
`;



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



export default StyledComponent;
//App.js
import React, { Component } from 'react';
import StyledComponent from './StyledComponent';
 
class App extends Component {
  render() {
    return (
      <div>
        <StyledComponent />
      </div>
    );
  }
}
 
export default App;

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

9.4.1 Tagged 템플릿 리터럴

해당 코드의 결과를 직접 확인하고 싶다면 크롬 브라우저의 개발자 콘솔을 열어서 자바스크립트 콘솔에 위 코드를 붙여 넣어 보세요.

Tagged 템플릿 리터럴을 사용하면 이렇게 템플릿 사이사이에 들어가는 자바스크립트 객체나 함수의 원본 값을 그대로 추출할 수 있습니다. styled-components는 이러한 속성을 사용하여 styled-components로 만든 컴포넌트의 props를 스타일 쪽에서 쉽게 조회할 수 있도록 해 줍니다.

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

styled-components를 사용하여 스타일링된 엘리먼트를 만들 때는 컴포넌트 파일의 상단에서 styled를 불러오고, styled.태그명을 사용하여 구현합니다.

import styled from 'styled-components';
 
const MyComponent = styled.div`
  font-size: 2rem;
`;

이렇게 styled.div 뒤에 Tagged 템플릿 리터럴 문법을 통해 스타일을 넣어 주면, 해당 스타일이 적용된 div로 이루어진 리액트 컴포넌트가 생성됩니다. 그래서 나중에 <MyComponent>Hello</MyComponent>와 같은 형태로 사용할 수 있습니다.

 

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

// 태그의 타입을 styled 함수의 인자로 전달
const MyInput = styled(‘input‘)</span>
  <span class="co33">background</span><span class="co34">:</span> <span class="co32">gray</span><span class="co36">;</span>
<span class="co31">
// 아예 컴포넌트 형식의 값을 넣어 줌
const StyledLink = styled(Link)</span>
  <span class="co33">color</span><span class="co34">:</span> <span class="co32">blue</span><span class="co36">;</span>
<span class="co31">

9.4.3 스타일에서 props 조회하기

styled-components를 사용하면 스타일 쪽에서 컴포넌트에게 전달된 props 값을 참조할 수 있습니다.

const Box = styled.div</span>
  <span class="co44">/* props</span><span class="co44">로</span> <span class="co44">넣어</span> <span class="co44">준</span> <span class="co44">값을</span> <span class="co44">직접</span> <span class="co44">전달해</span> <span class="co44">줄</span> <span class="co44">수</span> <span class="co44">있습니다</span><span class="co44">. */</span>
  <span class="co33">background</span><span class="co34">:</span> <span class="cd2 co49">${</span><span class="cd2 co33">props</span> <span class="cd2 co46">=&gt;</span> <span class="cd2 co34">props</span><span class="cd2 co33">.</span><span class="cd2 co34">color</span> <span class="cd2 co35">||</span> <span class="cd2 co31">'</span><span class="cd2 co31">blue</span><span class="cd2 co31">'</span><span class="cd2 co49">}</span><span class="cd2 co36">;</span>
  <span class="co33">padding</span><span class="co34">:</span> <span class="co32">1rem</span><span class="co36">;</span>
  <span class="co33">display</span><span class="co34">:</span> <span class="co33">flex</span><span class="co36">;</span>
<span class="co31">;

이 코드를 보면 background 값에 props를 조회해서 props.color의 값을 사용하게 했습니다. 그리고 color 값이 주어지지 않았을 때는 blue를 기본 색상으로 설정했습니다. JSX에서 사용될 때 다음과 같이 color 값을 props로 넣어 줄 수 있습니다.

 

9.4.4 props에 따른 조건부 스타일링

 styled-components에서는 조건부 스타일링을 간단하게 props로도 처리할 수 있습니다. 스타일 코드 여러 줄을 props에 따라 넣어 주어야 할 때는 CSS를 styled-components에서 불러와야 합니다. CSS를 사용하지 않고 다음과 같이 바로 문자열을 넣어도 작동하기는 합니다.

${props =>
  props.inverted &&
  `
      background: none;
      border: 2px solid white;
      color: white;
      &:hover {
        background: white;
        color: black;
      }
    `};

props를 참조한다면, 반드시 CSS로 감싸 주어서 Tagged 템플릿 리터럴을 사용해 주어야 합니다.

 

9.4.5 반응형 디자인

브라우저의 가로 크기에 따라 다른 스타일을 적용하기 위해서는 일반 CSS를 사용할 때와 똑같이 media 쿼리(query)를 사용하면 됩니다. 

//StyledComponents.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%;
  }
`;

일반 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: 100%;`};
`;

media를 한번 선언하고 나니까 이를 사용할 때 스타일 쪽의 코드가 훨씬 간단해진 것을 볼 수 있습니다. 지금은 media를 StyledComponent.js에서 만들어 주었지만, 실제로 사용한다면 아예 다른 파일로 모듈화한 뒤 여기저기서 불러와 사용하는 방식이 훨씬 편할 것입니다.

 


Quiz

1. CSS 이름을 지을 때 특별한 규칙을 사용하거나 CSS Selector를 활용할 수 있습니다.

2. 이름 짓는 규칙은 컴포넌트 이름-클래스 형태로 지어집니다.

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

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

5. CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 [파일 이름]_[클래스 이름]__[해시값] 형태로 자동으로 만들어줍니다.

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

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

 

Code Quiz

1. CSS Module을 사용한 클래스 이름을 두 개 이상 적용할 때 두번째 코드를 수정해주세요.

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



.inverted {
  color: black;
  background: white;
  border: 1px solid black;
}
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;

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


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

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

 

 

 

1번 정답

 

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;

2번 정답

@import './styles/utils';
.SassComponent {
  display: flex;
  .box {
    background: red; // 일반 CSS에서는 .SassComponent .box와 마찬가지
    cursor: pointer;
    transition: all 0.3s ease-in;
    (...)
  }
}
728x90

관련글 더보기