공부블로그

9강 컴포넌트 스타일링 본문

리액트/리액트를 다루는 기술

9강 컴포넌트 스타일링

떠어영 2021. 1. 20. 04:02

리액트에서 컴포넌트를 스타일링할 때는 다음 방식들을 사용한다.

 

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

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

프로젝트는 일반 css방식으로 만들어져 있다. 소규모 프로젝트를 개발하고 있다면 기본 css 시스템을 사용하는 것만으로도 충분하다.

css를 작성할 때 가장 중요한 점은 css 클래스를 중복되지 않게 만드는 것이다. 이를 방지하기 위한 방법으로는 이름을 지을 때 특별한 규칙을 이용하여 짓는 것과 CSS Selector를 활용하는 것이 있다. 

 

 

9. 1. 1 ) 이름 짓는 규칙

 

예를 들어 App-header처럼 '컴포넌트 이름 - 클래스' 형태로 이름을 지으면 다른 컴포넌트에서 실수로 중복되는 클래스를 만드는 것을 방지할 수 있다.

비슷한 방식으로 해당 클래스가 어디에서 어떤 용도로 사용되는지 명확하게 작성하는 방식인 BEM네이밍도 있다 ( 예: .card_title-primary )

 

 

9. 1. 2 ) CSS Selector

 

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

 

App. css

/*.App안에 들어있는 .logo에 스타일을 적용 (App.js에서 className='logo'라고 작성)*/
.App .logo { 
	animation: App-logo-spin infinite 20s linear;
    height: 40vmin;
}
/*.App안에 들어있는 header, header 클래스가 아닌 header 태그 자체에 스타일을 적용하므로 .생략 */
.App header {
  background-color: #282c34;
  ...
  color: white;
}
.App a {
  /* App 안에 들어있는 a 태그 */
  color: #61dafb;
}

App. js

function App() {
  return (
    <div className="App">
      <header> //header태그
        <img src={logo} className="logo" alt="logo" /> //className='logo' -> css에서 사용
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
        ...

 

9. 2 ) Sass 사용하기

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

Sass에서는 두가지 확장자 .scss 와 .sass를 지원하는데 .scss 문법이 더 자주 사용된다.

 

Sass를 CSS로 변환해주는 node-sass라는 라이브러리를 설치해준다 ( $ yarn add node-sass )

 

SassComponent. scss

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

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

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

 

App 컴포넌트 파일에서 보여주기

import React from "react";
import SassComponent from "./SassComponent"; //가져오기

function App() {
  return (
    <div>
      <SassComponent />
    </div>
  );
}

export default App;

 

9. 2. 1 ) utils 함수 분리하기

 

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

  1. src디렉터리 styles디렉터리 생성 → utils.scss파일 생성
  2. utils.scss 에 SassComponent. scss에서 작성했던 Sass변수, 믹스인민 잘라내서 이동.
  3. SassComponent. scss에서 utils. scss 파일을 불러와서 사용 ( @import './Styles/utils'; )

 

9. 2. 2 ) sass-loader 설정 커스터마이징 하기

 

예를 들어 SassComponent. scss 에서 utils. scss를 불러올 때 @import './Styles/utils'; 형태로 불러왔는데 디렉터리를 많이 만들어 구조가 깊어졌다면 세부 설정을 커스터마이징 하여 해결할 수 있다.

  1. yarn eject 명령어를 통해 세부 설정을 밖으로 꺼내준다.

  2. 위의 명령어로 인해 생성된 config 디렉터리 안의 webpack. config. js 파일에서 'sassRegex' 키워드를 찾아 커스터마이징 된 sass-loader를 넣어준다.

  3. 이제 utils. scss 파일을 불러올 때 상대경로를 입력할 필요 없이 styles 디렉터리 기준 절대 경로를 사용하여 불러올 수 있다. ( @import 'utils. scss'; )

  4. 이것도 귀찮으면 sass-loader의 data옵션을 설정하면 Sass파일을 불러올 때마다 코드의 맨 윗부분에 특정 코드를 포함시켜 준다. 

 

9. 2. 3 ) node_modules에서 라이브러리 불러오기

 

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

상대 경로를 사용하여 불러올 수도 있고, 구조가 깊은 경우에는 ~를 사용할 수도 있다. 물결문자를 사용하면 자동으로 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;
}

해당 클래스는 내가 만든 스타일을 직접 불러온 컴포넌트 내부에서만 작동하고, 특정 클래스가 웹 페이지 전역적으로 사용되는 경우에는 :global을 앞에 입력해준다.

 

CSSModule. js ( 위의 CSSModule을 사용하는 리액트 컴포넌트 )

import React from "react";
import styles from "./CSSModule.module.css";
const CSSModule = () => {
  return (
    <div className={styles.wrapper}>
      // 스타일 클래스를 적용하고 싶은 요소에 {styles.[클래스이름]} 형태로 전달 
      안녕하세요, 저는 <span className="something">CSS Module</span>
      // 글로벌은 그냥 넣어줌
    </div>
  );
};
export default CSSModule;

 

App. js 에서 CSSModule 컴포넌트를 렌더링

import React from "react";
import CSSModule from "./CSSModule";

function App() {
  return (
    <div>
      <CSSModule />
    </div>
  );
}
export default App;

 

( 실행화면 ) 

CSS Module을 사용한 클래스 이름을 두개 이상 적용하고 싶을 땐, CSSModule. js에서 <div className={ `${ styles.wrapper } ${ styles.inverted }` }> 형태로 작성한다. 

// 위의 코드는 템플릿 리터럴 문법을 사용하여 문자열을 합쳐준 것이다. ( const message = ` 이름은 ${ name }입니다 `; )

이외에도 className={ [ styles.wrapper, styles.inverted ] .join(' ') }으로 작성하는 방법이 있다.

 

 

9. 3. 1 ) classnames

 

classnames는 CSS클래스를 조건부로 설정할 때 매우 유용한 라이브러리이다. 

$ yarn add classnames 로 해당 라이브러리를 설치한다.

import classNames from 'classnames';

classNames('one','two'); // = 'one two'

const myClass = 'hello';
classNames('one', myClass, { myCondition: true }); // = 'one hello myCondition'

//highlighted가 참이어야 highlighted 클래스가 적용된다, theme으로 전달받는 문자열은 내용 그대로 클래스에 적용
const MyComponent = ({highlighted, theme}) => {
	<div className= {classNames('MyComponent', {highlighted}, theme)}> Hello </div>
}; 

여러가지 종류의 파라미터를 조합해 CSS클래스를 설정할 수 있기 때문에 컴포넌트에서 조건부로 클래스를 설정할 때 매우 편리하다.

 

또한 CSS Module과 함께 사용이 훨씬 쉬워진다. classnames에 내장되어 있는 bind함수를 사용하면 styles.[ 클래스 이름 ] 형태를 쓸 필요가 없어진다.

const cx = classNames. bind( styles ); // 미리 styles에서 클래스를 받아오도록 설정

<div className={ cx( 'wrapper', 'inverted' )}>

 

 

9. 3. 2 ) Sass와 함께 사용하기

 

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

 

CSSModule.module.css에서  &.inverted { ... }, :global { ... } 로 작성해준다.

/*자동으로 고유해지므로 흔히 사용하는 단어를 클래스 이름으로 마음대로 사용 가능하다*/
.wrapper {
	...
}
&.inverted { /*inverted가 wrapper와 함께 사용되었을 때만 적용*/
	...
}
/*글로벌 css를 작성할 때*/
:global{
 	...
}

9. 3. 3 ) CSS Module이 아닌 파일에서 CSS Module 사용하기 

 

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

( :local .wrapper { /*스타일*/ } 또는 :local { .wrapper{ /*스타일*/ } } )

 

9. 4 ) styled-components

자바스크립트 파일 안에 스타일을 선언하는 'CSS-in-JS' 중에서 가장 많이 쓰이는 styled-components 라이브러리

$ yarn add styled-components 로 해당 라이브러리를 설치한다.

 

StyledComponent. js

import React from "react";
import styled, { css } from "./styled-components";

const Box = styled.div`
  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;

  &:hover {
    background: rgba(255, 255, 255, 0.9);
  }

  ${(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의 가장 큰 장점은 'props값으로 전달해 주는 값을 쉽게 스타일에 적용' 할 수 있다는 것이다.

 

 

9. 4. 1 ) Tagged 템플릿

 

스타일을 작성할 때 `를 사용하여 만든 문자열에 스타일 정보를 넣어준다. 

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

 

 

9. 4. 2 ) 스타일링된 엘리먼트 만들기

 

styled.태그명 뒤에 Tagged 템플릿 리터럴 문법을 통해 스타일을 넣어 주면 해당 스타일이 적용된 태그로 이루어진 리액트 컴포넌트가 생성된다. 그 후에 <Box>hello</Box>형태로 사용할 수 있다.

하지만 사용해야 할 태그명이 유동적이거나 특정 컴포넌트 자체에 스타일링 해주고 싶다면 아래와 같이 작성한다.

const MyInput = styled( 'input' )` ... ` //태그의 타입을 styled 함수의 인자로 전달

const StyledLink = styled( Link )` ... ` //아예 컴포넌트 형식의 값을 넣어줌

 

 

9. 4. 3 ) 스타일에서 props 조회하기

 

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

const Box = styled. div `

    background: ${ props => props. color || 'blue' } ;

    ... //props값을 조회해서 props.color의 값을 사용, color값이 주어지지 않았을때는 blue사용

` ;

이 코드는 <Box color='balck'>...</Box>와 같이 사용된다.

 

 

9. 4. 4 )  props에 따른 조건부 스타일링

 

일반 CSS클래스에서는 className을 사용하여 조건부 스타일링을 해 왔는데, styled-components에서는 조건부 스타일링을 간단하게 props으로 처리할 수 있다.

StyledComponents. js - Button의 스타일에서 ${ props => props. inverted (inverted ={ true }이면 적용) && css` background: none; ...` (css를 불러와서 Tagged 템플릿으로 감싸주기) }; 

                                                           

 

9. 4. 5 ) 반응형 디자인

 

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

 

StyledComponents. js - Box //기본적으로는 가로 크기 1024px에 가운데 정렬을 하고, 가로 크기가 작아짐에 따라 크기를 줄이고 768px 미만이 되면 꽉 채운다.

const Box = styled.div`
  background: ${(props) => props.color || "blue"};
  padding: 1rem;
  display: flex;

  width: 1024px;
  margin: 0 auto;
  @media (max-width: 1024px) {
    width: 768px;
  }
  @media (max-width: 768px) {
    width: 100%;
  }
`;

 

이런 작업을 함수화할 수도 있다.

const sizes = {

    desktop: 1024,

    tablet: 768

};

const media = Object.keys( sizes ). reduce(( acc, label )) => {

     acc[label] = ( ... args ) => css`

         @media ( max-width: ${ sizes[ label ] / 16}em) {

               ${ css( ...args )};

}

` ;

return acc;

}, { });

 

그리고  ${ media. desktop`width: 768px;`} ${ media. tablet`width: 1024px;`} 로 작성한다.

실제로 사용할 때는 아예 다른 파일로 모듈화한 뒤 불러와 사용하는 방식이 편하다.