상세 컨텐츠

본문 제목

<리액트를 다루는 기술> 9장: 컴포넌트 스타일링

21-22/21-22 리액트 스타터 -2

by dev otcroz 2022. 1. 3. 13:00

본문

728x90

INDEX

9장 컴포넌트 스타일링

1. 가장 흔한 방식, 일반 CSS

1.1 이름 짓는 규칙

1.2 CSS Selector

2. Sass 사용하기

● Sass에서 지원하는 두 가지 확장자 .scss .sass

2.1 utils 함수 분리하기

2.2 sass-loader 설정 커스터마이징

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

3. CSS Module

3.1 classNames

3.2 Sass와 함께 사용하기

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

4. styled-components

4.1 Tagged 템플릿 리터럴

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

4.3 스타일에서 props 조회하기

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

4.5 반응형 디자인

5. Question 개념 정리 및 코드 문제

● 개념 복습 문제

● 코드 문제


컴포넌트 스타일링

: 스타일을 적용해 컴포넌트를 꾸미는 것

 

리액트에서 자주 사용하는 컴포넌트 스타일링 방식

  • 일반 CSS : 가장 기본적인 방식
  • Sass : 자주 사용되는 CSS 전처리기(pre-processor) 중 하나. 확장된 CSS 문법을 사용해 CSS 코드를 쉽게 작성할 수 있게 함.
  • CSS Module : CSS 클래스의 이름이 충돌하지 않도록 파일마다 고유한 이름을 자동으로 생성해 주는 옵션
  • styled-components : 스타일을 자바스크립트 파일에 내장시키는 방식

 

실습  프로젝트 준비 ▶ 일반 CSS 사용 ▶ Sass 사용 ▶ CSS Module 사용 ▶ styled-components 사용

 

프로젝트 준비

터미널에서 다음 명령을 수행한다.

$ yarn create react-app styling-react

$ cd styling-react

$ yarn start


1. 가장 흔한 방식, 일반 CSS

기본적으로 프로젝트는 일반 CSS 방식으로 만들어져 있다.

새로운 스타일링 시스템을 적용하기 불편한 소규모 프로젝트라면 기본 방식을 사용해도 충분하다.

 

아래는 프로젝트 생성 시 기본으로 만들어진 App.js 파일과 App.css 파일이다.

// App.js

import logo from './logo.svg';
import './App.css';

function App() {
  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 {
  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 작성 시 가장 중요한 점   CSS 클래스를 중복되지 않게 만들기!

  • 이름을 지을 때 특별한 규칙을 사용
  • CSS Selector 활용

 

1.1  이름 짓는 규칙

  • 컴포넌트 이름-클래스 형태: 프로젝트에 자동 생성된 App.css에도 사용된 방식.  예) App-header
  • BEM 네이밍 방식: 해당 클래스가 어디에서 어떤 용도로 사용되는지 명확하게 작성하는 방식. CSS 방법론 중 하나.  예) card__title-primary

 

1.2  CSS Selector (CSS 선택자)

CSS Selector는 CSS 스타일 작성 시 특정 태그(HTML 태그, ID를 가진 태그, 클래스에 포함된 태그, 그외 특별한 관계에 있는 태그)를 가리킬 때 사용된다.

CSS 클래스가 특정 클래스 내부에 있는 경우에만 스타일을 적용하려면 .(바깥쪽 태그의 클래스) .(안쪽 태그의 클래스) 형식으로 작성한다.

예) .App 안의 .logo에 스타일을 적용할 때

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

CSS Selector를 이용하는 방식으로 App.css를 수정해보자.

/* App.css */

.App {
  text-align: center;
}

/* .App 안의 .logo */
.App .logo {
  height: 40vmin;
  pointer-events: none;
}

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

/* .App 안의 a 태그 */
.App a {
  color: #61dafb;
}

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

이에 맞춰 컴포넌트의 JSX 부분도 수정한다.

 

수정된 부분

<header> className 삭제

<img className="logo"> className 변경

// App.js

import logo from './logo.svg';
import './App.css';

function App() {
  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
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

이름 짓는 요령

  • 최상위 요소의 클래스를 컴포넌트 이름으로 설정   예) <div className="App">
  • 내부의 클래스는 소문자로 설정   예) .logo
  • 클래스 이름이 불필요한 경우 클래스 지정을 생략 가능   예) <header>

2. Sass 사용하기

Sass(Syntactically Awesome Style Sheets, 문법적으로 매우 멋진 스타일시트) 특징

  • CSS 전처리기
  • 복잡한 작업을 쉽게
  • 스타일 코드의 재활용성 ↑
  • 코드의 가독성↑, 유지보수를 쉽게

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

현재 사용 중인 버전은 5.0.0이다.

● Sass에서 지원하는 두 가지 확장자 .scss .sass

/* .sass 확장자 문법 */

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

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

 아래의 .scss 문법이 더 자주 사용된다. 중괄호({})와 세미콜론(;)을 사용한다.

/* .scss 확장자 문법 */

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

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

 

컴포넌트에서 Sass를 사용하려면 Sass를 CSS로 변환해주는 Sass 라이브러리를 설치해야 한다. 

프로젝트 디렉터리에서 다음 터미널 명령어를 실행한다.

$ yarn add sass

 

설치가 완료되면 src 디렉터리에 SassComponent.scss 파일을 작성한다.

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

src 폴더에 이 Sass 스타일시트를 사용하는 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 컴포넌트에서 보여주도록 한다.

 

// App.js

import {Component} from 'react';
import SassComponent from './SassComponent';

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

export default App;

작업한 뒤에는 개발 서버를 재시작해야 Sass가 성공적으로 적용된다.

(개발 서버가 구동 중이라면 터미널 창에서 Ctrl + C로 빠져나오고, 다시 yarn start 명령어를 입력한다.)

 

서버 재시작시 다음과 같은 페이지가 나타난다.


2.1  utils 함수 분리하기

여러 파일에서 사용될 수 있는 Sass 변수믹스인은 별도의 파일로 분리하면 필요한 곳에서 쉽게 불러와 사용할 수 있다.

* 믹스인: 함수처럼 재사용할 수 있는 스타일 코드 블록

 

src 디렉터리에 styles 디렉터리를 생성하고, utils.scss 파일을 작성한다.

기존 SassComponent.scss에 작성했던 변수와 믹스인을 잘라내서 이동시킨다.

// 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에서 사용해보자.

scss 파일에서 다른 scss 파일을 불러올 때는 @import 구문을 사용한다.

// SassComponent.scss

@import "./styles/utils";

(...)

utils.scss 파일을 분리하기 전과 같은 결과가 나타난다.


2.2  sass-loader 설정 커스터마이징

① 절대경로로 import할 수 있게 하기

위처럼 다른 파일을 임포트할 때, 프로젝트에 디렉터리가 많아져 구조가 깊어졌다면 상대경로로 임포트하기 불편해진다.

웹팩에서 Sass를 처리하는 sass-loader의 설정을 커스터마이징하면 styles 디렉터리를 기준으로 한 절대 경로를 사용할 수 있다.

create-react-app으로 만든 프로젝트는 구조의 복잡도를 낮추기 위해 세부 설정이 모두 숨겨져 있어,

이를 커스터마이징하려면 프로젝트 디렉터리에서 yarn  eject 명령어로 꺼내주어야 한다.

 

create-react-app에서는 기본적으로 Git 설정이 되어있는데,

yarn eject는 아직 Git에 커밋되지 않은 변화가 있다면 진행되지 않으니 먼저 커밋해주어야 한다.

커밋을 진행하려면 VS Code 좌측의 Git UI를 사용하거나, 터미널에서 다음 명령을 사용한다.

$ git add .

$ git commit -m'Commit before yarn eject'

커밋을 마쳤다면 다음 작업을 실행한다.

 

$ yarn eject

$ react-scripts eject

? Are you sure you want to eject? This action is permanent. (y/N) y

 

위 작업을 마치면 프로젝트 디렉터리에 config라는 디렉터리가 생성된다.

디렉토리 내 webpack.config.js를 열어보자.

sassRegex 키워드를 검색하면 두 번째 탐색 결과에서 다음과 같은 코드가 나타날 것이다.

// config/webpack.config.js

{
  test: sassRegex,
  exclude: sassModuleRegex,
  use: getStyleLoaders(
    {
      importLoaders: 3,
      sourceMap: isEnvProduction
      ? shouldUseSourceMap
      : isEnvDevelopment,
      modules: {
        mode: 'icss',
      },
    },
    'sass-loader'
  ),
  sideEffects: true,
},

use: 에 있는 'sass-loader' 부분을 지우고, 뒷부분에 concat을 통해 커스터마이징된 sass-loader 설정을 추가한다.

// config/webpack.config.js

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

SassComponent.scss 파일에서 import 구문을 다음과 같이 수정해 본다. 똑같이 적용되는 것을 확인할 수 있다.

@import 'utils.scss';

 

② additionalData 옵션: 특정 코드 항상 포함시키기

sass-loaderadditionalData 옵션을 설정하면 Sass 파일을 불러올 때마다 코드의 맨 윗부분에 특정 코드를 포함시켜 준다.

이것을 이용하면 import 구문을 쓰지 않고도 utils.scss 파일을 불러오는 행위를 자동화할 수 있다!

 

webpack.config.js를 열어서 sass-loader 옵션의 additionalData 필드를 다음과 같이 설정해보자. (아래에서 5번째줄)

// config/webpack.config.js

{
  test: sassRegex,
  exclude: sassModuleRegex,
  use: getStyleLoaders(
  {
    importLoaders: 3,
    sourceMap: isEnvProduction
    ? shouldUseSourceMap
    : isEnvDevelopment,
    modules: {
      mode: 'icss',
    },
  }).concat({
      loader: require.resolve("sass-loader"),
      options: {
      sassOptions: {
      includePaths: [paths.appSrc + "/styles"],
      },
      additionalData: "@import 'utils';",
    },
  }),
  sideEffects: true,
},

 


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

라이브러리를 사용하는 방법

① 상대 경로 사용  비추천

스타일 파일이 깊숙한 디렉터리에 위치한 경우 ../를 매우 많이 적어야 해 번거롭다.

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

물결 문자(~) 사용  추천!

자동으로 node_modules 폴더에서 라이브러리 디렉터리를 탐지하여 스타일을 불러올 수 있다.

@import '~library/styles';

 

연습  유용한 Sass 라이브러리 두 가지 설치하고 사용해보기

라이브러리를 다음 yarn 명령어를 사용해 설치한다.

$ yarn add open-color include-media

 

설치한 라이브러리를 utils.scss에서 불러온다.

※ node_modules 내부 라이브러리의 scss 파일의 경로는 라이브러리의 공식 매뉴얼에서 알려주지 않는 경우도 많으니, 직접 경로로 들어가서 확인하길 바람.

// utils.scss

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

라이브러리를 사용하기 위해 SassComponent.scss 파일을 수정한다.

// SassComponent.scss

.SassComponent {
  display: flex;
  background: $oc-gray-2;
  @include media("<768px") {
    background: $oc-gray-9;
  }
  (...)
}

이 코드는 SassComponent의 배경색을 open-colors 팔레트 라이브러리에서 불러온 후 설정하고, 화면 가로 크기가 768px 미만이 되면 배경색을 어둡게 바꿔 준다. 코드를 저장하고 나면 다음과 같은 결과물이 나타난다.

(좌) 가로 길이가 768px 이상        (우) 가로 길이 768px 미만


3. CSS Module

CSS를 불러와서 사용할 때 클래스 이름을 고유한 형태로 만들어(자동으로 [파일 이름]_[클래스 이름]_[해시값]으로 변환),

컴포넌트 스타일 클래스 이름이 중첩되지 않도록 해 주는 기술이다.

구버전(v1)의 create-react-app에서는 웹팩에서 css-loader 설정을 별도로 해야 했지만,

v2 버전 이상부터는 .module.css 확장자로 파일을 저장하기만 하면 CSS Module이 적용된다.

 

CSSModule.module.css 파일을 src 디렉터리에 생성해 다음과 같이 작성해보자.

/* CSSModule.module.css */

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

.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;
  }
  // 여기에 다른 클래스를 만들 수도 있겠죠?
}

CSS Module을 사용하면 클래스 이름을 지을 때 중복 여부를 고민하지 않아도 된다. 흔한 단어로 지어도 상관없다.

해당 클래스는 .module.css로 작성한 스타일을 직접 불러온 컴포넌트 내부에서만 작동하기 때문이다.

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

 

위의 CSS Module을 사용하는 리액트 컴포넌트도 작성해보자.

// CSSModule.js

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에서 사용한 클래스 이름, 해당 이름을 고유화한 값키-값 형태로 들어 있다.

위 코드에서 console.log(styles)를 하면 다음과 같이 출력된다.

{ wrapper: "CSSModule_wrapper__15bdQ" }

 

이 고유한 클래스 이름을 사용하려면 ▶ JSX 엘리먼트에 className={styles.[클래스이름]} 형태로 전달해주면 된다.

전역적으로 선언한 :global 클래스의 경우 ▶ 전형적인 방식대로 문자열을 넣는다.

 

CSSMocule 관련 컴포넌트와 스타일을 App 컴포넌트에서 렌더링해보자.

// App.js

import {Component} from 'react';
import CSSModule from './CSSModule';

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

export default App;

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

/* CSSModule.module.css */

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

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

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

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

 

// CSSModule.js

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

이 문법을 사용하면 문자열 안에 자바스크립트 레퍼런스를 쉽게 넣어줄 수 있다.

const name = '리액트';
// const message = '제 이름은 ' + name + '입니다.';
const message = `제 이름은 ${name}입니다.`;

` 문자백틱(Backtick)이라고 부른다. (키보드의 숫자 키 [1] 왼쪽에 위치)

 

템플릿 리터럴 문법 대신 다음과 같이 작성할 수도 있다.

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

3.1  classnames

: CSS를 조건부로 설정하거나, CSS Module 사용 시 여러 클래스를 적용할 때 편리하게 사용할 수 있는 라이브러리

$ 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이면 highlighted 클래스가 적용되고, false이면 적용되지 않는다.

추가로 theme로 전달받는 문자열은 내용 그대로 클래스에 적용된다.

이런 라이브러리의 도움을 받지 않는다면 다음과 같은 복잡한 형식으로 처리해야 할 것이다.

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

 

classnames를 CSS Module과 함께 사용하기

classnames에 내장된 bind 함수를 사용하면 클래스를 넣어줄 때마다 styles.[클래스 이름] 형태를 사용할 필요가 없다.

변수를 정의해 미리 styles에서 클래스를 받아오면 cx('클래스 이름', '클래스 이름2') 형태로 간단히 사용할 수 있다.

 

예) 앞서 만든 CSSModule 컴포넌트에 classnames의 bind 함수 적용하기

// CSSModule.js

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;

3.2  Sass와 함께 사용하기

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

CSSModule.module.css 파일의 이름을 CSSModule.Module.scss 변경하고 코드를 수정해보자.

/* CSSModule.module.xcss */

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

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

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

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

CSSModule.js 파일의 상단에서도 .css 파일 대신 .scss 파일을 불러온다.

import styles from './CSSModule.module.scss';

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

CSS Module에서 글로벌 클래스를 정의할 때 :global을 사용했던 것처럼

CSS Module이 아닌 일반 .css/.scss 파일에서도 :local을 사용하여 CSS Module을 사용할 수 있다.

:local .wrapper {
  /* 스타일 코드 작성 */
}

:local {
  .wrapper {
    /* 스타일 코드 작성 */
  }
}

 


4. styled-components

CSS-in-JS 방식: 자바스크립트 파일 안에 스타일을 선언하는 방식

이와 관련된 라이브러리의 종류는 https://github.com/MicheleBertoli/css-in-js 에서 확인할 수 있다.

 

그중 가장 많은 선호를 얻고 있는 라이브러리인 styled-components을 알아보자.

* styled-components를 대체할 수 있는 라이브러리로는 현재 emotion이 대표적이며, 둘의 작동 방식은 비슷하다.

  • 자바스크립트 파일 하나에 스타일까지 작성할 수 있어 .css 또는 .scss 확장자를 가진 스타일 파일을 따로 만들지 않아도 된다.
  • 일반 className을 사용하는 CSS/Sass와 비교했을 때, props에 전달되는 값을 쉽게 스타일에 적용할 수 있다.

 

이 라이브러리는 다음의 터미널 명령어로 설치할 수 있다.

$ yarn add styled-components

 

src 디렉터리에 StyledComponent.js 파일을 생성한 뒤 다음 예제 컴포넌트를 만들어보자.

/* StyledComponent.js */

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;
// App.js

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

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

export default App;

실행하면 다음과 같은 화면이 나타난다.

 

VS Code에서 styled-components를 편집할 때 tip

 컴포넌트 내부에 작성한 스타일을 문자열로 취급되어 코드 신택스 하이라이팅(문법에 따라 에디터 폰트 색상을 입히는 작업)이 제대로 이루어지지 않을 수 있다. VS Code의 마켓플레이스에서 vscode-styled-components를 검색하여 설치하면 색상이 정상적으로 입혀진다.

(좌) 적용 전        (우) vscode-styled-components 적용 후


4.1  Tagged 템플릿 리터럴

앞서 작성한 코드에서 `(백틱 문자)를 사용하여 만든 문자열에 스타일 정보를 넣어주었다.

이 문법을 Tagged 템플릿 리터럴이라고 한다.

CSS Module에서 다룬 일반 템플릿 리터럴과 달리, 템플릿 안에 자바스크립트 객체나 함수를 전달할 때 온전히 추출할 수 있다.

 

일반 템플릿에 객체를 넣거나 함수를 넣으면 형태를 잃어버린다.

객체는 "[object Object]"로 변환되고 함수는 함수 내용이 그대로 문자열화되어 나타난다.

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

작성한 함수 뒤에 템플릿 리터럴을 넣어 준다면, 템플릿 안에 넣은 값을 온전히 추출할 수 있다.

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

 

 

위 코드를 (크롬 브라우저의 개발자 콘솔 등의) 자바스크립트 콘솔에 붙여 넣으면 다음과 같은 결과가 나타난다.

이와 같은 방법으로 styled-components로 만든 컴포넌트의 props를 스타일 쪽에서 쉽게 조회할 수 있다.


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

styled-components를 사용해 엘리먼트를 만드는 법

import styled from 'styled-components';

const MyComponent = styled.div`
  font-size: 2rem;
`;
  • 컴포넌트 파일 상단에서 styled 함수를 임포트
  • styled.태그명을 사용하여 구현  예) styled.div   styled.button   styled.input
  • styled.div 뒤에 Tagged 템플릿 문법을 통해 스타일을 넣어주면,
    해당 스타일이 적용된 div 태그로 이루어진 리액트 컴포넌트가 생성된다.
    후에 <MyComponent>Hello</MyComponent>와 같은 형태로 사용할 수 있다.

 

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

태그의 타입을 styled 함수의 인자로 전달

const MyInput = styled('input')`
  background: gray;
`

컴포넌트 형식의 값을 styled 함수의 인자로 전달  * Link는 후에 리액트 라우터를 다룰 때 사용하는 컴포넌트

const StyledLink = styled(Link)`
  color: blue;
`

컴포넌트(Sample)를 styled의 파라미터에 넣는 경우,

해당 컴포넌트에 className props최상위 DOMclassName 값으로 설정하는 작업이 내부적으로 되어 있어야 한다.

const Sample = ({ className }) => {
  return <div className={classname}>Sample</div>;
};

const StyledSample = styled(Sample)`
  font-size: 2rem;
`;

4.3  스타일에서 props 조회하기

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

이전에 작성했던 Box 컴포넌트를 보자. (코드 3번째 줄)

/* StyledComponents.js - Box 컴포넌트 */

const Box = styled.div`
  /* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
  background: ${props => props.color || 'blue'};
  padding: 1rem;
  display: flex;
`;
  • background 값을 설정할 때: props를 조회해서 props.color의 값을 사용하게 했다.
  • color 값이 주어지지 않았을 때에는 blue를 기본 색상으로 사용하도록 설정했다.

이렇게 만들어진 컴포넌트(Box)는 JSX에서 사용될 때 다음과 같이 props(color)의 값("black")을 사용할 수 있다.

<Box color="black">(...)</Box>

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

일반 CSS 클래스를 사용한 조건부 스타일링 ▶ className을 사용한다.

styled-components에서 조건부 스타일링  ▶ 간단하게 props로 처리한다.

 

Button 컴포넌트를 살펴보자.

/* StyledComponents.js - Button 컴포넌트 */

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에 따라 서로 다른 스타일을 조건부로 적용할 수 있다.

<Button>안녕하세요</Button>
<Button inverted={true}>테두리만</Button>

 

컴포넌트 스타일 코드를 여러 줄로 작성할 때

props 값에 따라 서로 다른 스타일을 적용하려는 경우

반드시 styled-components에서 임포트CSS로 감싸고, Tagged 템플릿 리터럴을 사용해야 한다.

 

props를 참조하지 않는 경우

굳이 CSS를 사용하지 않고 바로 문자열을 넣어도 작동하기는 한다.

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

    단점

  • 작성한 코드가 문자열로만 취급되어 VS Code 확장 프로그램에서 신택스 하이라이팅이 제대로 이루어지지 않는다.
  • Tagged 템플릿 리터럴이 아니므로 함수를 받아 사용하지 못해 해당 부분에서는 props 값을 사용하지 못한다.

4.5  반응형 디자인

styled-components를 사용한 반응형 디자인을 살펴보자.

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

 

Box 컴포넌트를 다음과 같이 수정한다.

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

이런 작업을 여러 컴포넌트에서 반복해야 할 때는 이 작업을 함수화하여 간편하게 사용할 수 있다.

styled-components 매뉴얼에서 제공하는 유틸 함수를 따라 사용해보자.

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를 StyledCompoent.js에 만들어두었지만, 아예 다른 파일로 분리해 모듈화한 뒤 필요한 곳에 불러와 사용하면 훨씬 실용적일 것이다.

 


5. Question 개념 정리 및 코드 문제

● 개념 복습 문제

1. CSS를 작성할 때 가장 중요한 점은 (클래스 이름)을 중복되지 않게 만드는 것이다.

 

Sass

2. Sass가 지원하는 2가지 확장자 중 (.sass)보다 (.scss) 문법이 더 자주 사용되며, 이 문법에서는 (중괄호)와 (세미콜론)을 사용한다.

3. (믹스인)이란 함수처럼 재사용할 수 있는 스타일 코드 블록을 말한다.

4. scss 파일에서 다른 scss 파일을 불러올 떄는 (@import) 구문을 사용한다.

5. sass-loader의 설정을 커스터마이징하면 다른 파일을 임포트할 때 (styles) 디렉터리 기준 절대 경로를 사용하거나, Sass 파일을 불러올 때마다 특정 코드를 포함시키도록 할 수 있다. 이것은 config 디렉터리의 (webpack.config.js) 파일에서 설정할 수 있다.

6. 파일을 임포트할 때 (물결 문자(~))를 사용하면 자동으로 node_modules 폴더에서 라이브러리를 탐지하여 스타일을 불러온다.

 

CSS Module

7. (.module.css) 확장자로 파일을 저장하기만 하면 CSS Module이 적용된다.

8. [파일 이름]_[클래스 이름]_[해시 값]으로 변환된 고유한 클래스 이름을 사용하려면 JSX 엘리먼트에 (className={styles.[클래스이름]}) 형태로 전달하면 된다. (:global) 키워드를 사용해 전역적으로 선언한 클래스의 경우 그냥 문자열을 넣는다.

9. ES6 문법 템플릿 리터럴: 문자열을 감싸는 따옴표로 (`(백틱 문자))를 사용한다. 사용 형태: `${변수}`

10. (classnames) 라이브러리를 사용하면 여러 종류의 파라미터를 조합해 CSS 클래스를 설정할 수 있어 컴포넌트에서 (조건부)로 클래스를 설정할 때 유용하다. 이 라이브러리에 내장된 (bind 함수)를 사용하면 styles.클래스이름 형태를 사용할 필요 없이 간단하게 지정할 수 있다.

11. Sass를 사용할 때도 파일 이름 뒤에 (.module.scss) 확장자를 사용하면 CSS Module로 사용할 수 있다.

12. CSS Module이 아닌 일반 .css/.scss 파일에서도 (:local)을 사용하여 CSS Module을 사용할 수 있다.

 

styled-components

13. 일반 className을 사용하는 CSS/Sass와 비교했을 때, (props)에 전달되는 값을 쉽게 스타일에 적용할 수 있다.

14. Tagged 템플릿 리터럴은 일반 템플릿 리터럴과 달리, 자바스크립트 (객체)나 (함수)를 전달할 때 온전히 추출할 수 있다.

15. (styled 함수)를 임포트하고, (styled.태그명) 뒤에 (Tagged 템플릿) 문법을 통해 스타일을 넣어주어 엘리먼트를 만들 수 있다.

16. (props)에 따라 조건부 스타일링을 할 수 있다.

 

● 코드 문제

1. 다음 ES6 문법 템플릿 리터럴로 작성된  코드를 classnames 라이브러리의 bind 함수를 이용해 간단히 작성해보자.

// CSSModule.js

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. 다음 컴포넌트에서 color(글자 색상) 값을 설정할 때 props를 조회하여 props.fontColor의 값을 사용하게 하고,

fontColor 값이 주어지지 않았을 때에는 red를 기본 색상으로 사용하도록 설정하라.

/* StyledComponents.js - Box 컴포넌트 */

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

Corner React Starter #2

Editor 유즈

728x90

관련글 더보기