리액트를 다루는 기술 9장

riverkim·2022년 6월 10일
1
post-thumbnail

이 글은 책 리액트를 다루는 기술을 개인적으로 정리한 글 입니다.

컴포넌트 스타일링

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

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

가장 흔한 방식, 일반 CSS

import React, { Component } from ‘react‘;
import logo from./logo.svg‘;
import./App.css‘;


class App extends Component {
  render() {
    return (
      <div className="App">

      </div>
    );
  }
}



export default App;
/* css */
.App {
  text-align: center;
}
 
...

CSS를 작성할 때 가장 중요한 점은 CSS 클래스를 중복되지 않게 만드는 것
CSS 클래스가 중복되는 것을 방지하는 여러 가지 방식이 있는데,
그중 하나는 이름을 지을 때 특별한 규칙을 사용하여 짓는 것이고
또 다른 하나는 CSS Selector를 활용하는 것

이름 짓는 규칙

프로젝트에 자동 생성된 App.css를 읽어 보면 클래스 이름이 컴포넌트 이름-클래스 형태로 지어져 있음(예: App-header)
클래스 이름에 컴포넌트 이름을 포함시킴으로써 다른 컴포넌트에서 실수로 중복되는 클래스를 만들어 사용하는 것을 방지 함
비슷한 방식으로 BEM 네이밍(BEM Naming)이 있음
BEM 네이밍은 CSS 방법론 중 하나
이름을 지을 때 일종의 규칙을 준수하여 해당 클래스가 어디에서 어떤 용도로 사용되는지 명확하게 작성하는 방식 -> 예).card__title-primary

CSS Selector

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

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

Sass 사용하기

Sass(Syntactically Awesome Style Sheets)(문법적으로 매우 멋진 스타일시트)는
CSS 전처리기로 복잡한 작업을 쉽게 할 수 있도록 해 주고,
스타일 코드의 재활용성을 높여 줄 뿐만 아니라 코드의 가독성을 높여서 유지 보수를 더욱 쉽게 해 줌

Sass에서는 두 가지 확장자 .scss와 .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를 작성하는 방식과 비교해서 문법이 크게 다르지 않음

보통 .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 함수 분리하기

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

src 디렉터리에 styles라는 디렉터리를 생성하고,
그 안에 utils.scss 파일을 생성

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

/* 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.scss */

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

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

생략

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

생략

CSS Module

CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 고유한 값, 파일이름_클래스이름__해시값 형태로 자동 생성
-> 컴포넌트 스타일 클래스 이름 중첩 방지

따로 설정할 필요 없이 .module.css 확장자로 파일을 저장하기만 하면 CSS Module이 적용

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

/* 글로벌 CSS를 작성하고 싶다면 */

:global .something {
  font-weight: 800;
  color: aqua;
}
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을 사용하면 클래스 이름을 지을 때 그 고유성에 대해 고민 X
해당 클래스는 우리가 방금 만든 스타일을 직접 불러온 컴포넌트 내부에서만 작동

만약 특정 클래스가 웹 페이지에서 전역적으로 사용되는 경우라면 :global을 앞에 입력하여 글로벌 CSS임을 명시

classnames

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

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으로 전달받는 문자열은 내용 그대로 클래스에 적용됩니다.

CSS Module과 함께 사용하면 CSS Module 사용이 훨씬 쉬워짐

classnames에 내장되어 있는 bind 함수를 사용하면 클래스를 넣어 줄 때마다 styles.[클래스 이름] 형태를 사용할 필요가 없음
사전에 미리 styles에서 받아 온 후 사용하게끔 설정해 두고 cx('클래스 이름', '클래스 이름2') 형태로 사용

import React from 'react';
import classNames from 'classnames/bind';
import styles from './CSSModule.module.css';
 
//  CSSModule 컴포넌트에 classnames의 bind 함수를 적용
const cx = classNames.bind(styles); // 미리 styles에서 클래스를 받아 오도록 설정하고
 
const CSSModule = () => {
return (
  <div className={cx('wrapper', 'inverted')}>
    안녕하세요, 저는 <span className="something">CSS Module!</span>
    </div>
);
};
 
export default CSSModule;

CSS Module을 사용할 때 클래스를 여러 개 설정하거나, 또는 조건부로 클래스를 설정할 때 classnames의 bind를 사용하면 훨씬 편리

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

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

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

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

styled-components

컴포넌트 스타일링의 또 다른 패러다임은 자바스크립트 파일 안에 스타일을 선언하는 방식 -> CSS-in-JS

CSS-in-JS 라이브러리 중에서 개발자들이 가장 선호하는 styled-components

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

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

import React from 'react';
import styled, { css } from 'styled-components';


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

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;

Tagged 템플릿 리터럴

스타일을 작성할 때 백틱을 사용하여 만든 문자열에 스타일 정보를 넣어주는 문법 -> Tagged 템플릿 리터럴

Tagged 템플릿 리터럴이 일반 템플릿 리터럴과 다른 점 -> 템플릿 안에 자바스크립트 객체나 함수를 전달 할 때 온전히 추출 가능

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

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

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

이렇게 styled.div 뒤에 Tagged 템플릿 리터럴 문법을 통해 스타일을 넣어 주면,
해당 스타일이 적용된 div로 이루어진 리액트 컴포넌트가 생성

Hello와 같은 형태로 사용 가능

div가 아닌 button이나 input에 스타일링을 하고 싶다면 styled.button 혹은 styled.input 같은 형태로 뒤에 태그명을 넣어 줌


// 사용해야 할 태그명이 유동적이거나 특정 컴포넌트 자체에 스타일링해 주고 싶다면
// 태그의 타입을 styled 함수의 인자로 전달
const MyInput = styled("input")`
	background:gray;
`

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

스타일에서 props 조회하기

styled-components를 사용하면 스타일 쪽에서 컴포넌트에게 전달된 props 값을 참조 가능

// props로 넣어 준 값을 직접 전달해 줄 수 있음 
// background 값에 props를 조회해서 props.color의 값을 사용
// 그리고 color 값이 주어지지 않았을 때는 blue를 기본 색상으로 설정

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

이렇게 만들어진 코드는 JSX에서 사용될 때 다음과 같이 color 값을 props로 넣어 줄 수 있음

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

props에 따른 조건부 스타일링

styled-components에서는 조건부 스타일링을 간단하게 props로도 처리할 수 있음

import React from 'react';
import styled, { css } from 'styled-components';


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

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;
// props를 사용하여 서로 다른 스타일을 적용
<Button>안녕하세요</Button>
<Button inverted={true}>테두리만</Button>

반응형 디자인

styled-components를 사용할 때 반응형 디자인

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

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


import React from 'react';
import styled, { css } from 'styled-components';
 

// 일반 CSS에서 할 때랑 큰 차이가 없습니다. 
// 작업을 여러 컴포넌트에서 반복해야 한다면 작업을 함수화하여 간편하게 사용 가능

// 아래는 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%;`};
`;

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

정리

다양한 리액트 컴포넌트 스타일링 방식이 있고, 상황에 따라 선택해서 사용

profile
Hello!

0개의 댓글