관리 메뉴

거니의 velog

(23) 컴포넌트 스타일링 3 본문

React_리액트 시작

(23) 컴포넌트 스타일링 3

Unlimited00 2023. 12. 5. 13:44

4. styled-components

* 컴포넌트 스타일링의 또 다른 패러다임은 자바스크립트 파일 안에 스타일을 선언하는 방식이다. 이 방식을 'CSS-in-JS'라고 부르는데, 이와 관련된 라이브러리는 정말 많다. 라이브러리의 종류는 다음 주소에서 확인할 수 있다.

https://github.com/MicheleBertoli/css-in-js

 

GitHub - MicheleBertoli/css-in-js: React: CSS in JS techniques comparison

React: CSS in JS techniques comparison. Contribute to MicheleBertoli/css-in-js development by creating an account on GitHub.

github.com

* 이 절에서는 CSS-in-JS 라이브러리 중에서 개발자들이 가장 선호하는 styled-components를 알아보자.

* styled-conponents를 대체할 수 있는 라이브러리로는 현재 emotion이 대표적이다.
  작동 방식은 styled-conponents와 꽤 비슷하다.

* 한번 설치해 보자.

$ yarn add styled-conponents

* 이 라이브러리를 통해 예제 컴포넌트를 한번 만들어 보자. styled-components를 사용하면 자바스크립트 파일 하나에 스타일까지 작성할 수 있기 때문에 .css 또는 .scss 확장자를 가진 스타일 파일을 따로 만들지 않아도 된다는 큰 이점이 있다.

* src 디렉터리에 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;

* 이제 이 컴포넌트를 App 컴포넌트에서 보여 주자.

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 값으로 전달해 주는 값을 쉽게 스타일에 적용할 수 있다는 것이다.


* VS Code를 사용할 때 styled-components를 위해 컴포넌트 내부에 작성한 스타일이 그저 문자열로 간주되어 코드 신택스 하이라이팅(문법에 따라 에디터 폰트 색상을 입히는 작업)이 제대로 이루어지지 않는다.

* VS Code의 마켓플레이스에서 vscode-styled-components를 검색하여 설치하면 색상이 정상적으로 입혀진다.

* 아직은 styled-components가 어떤 원리로 어떻게 작동하는지 잘 모를 것이다. 앞으로 이어지는 절에서 원리와 사용법을 하나하나 알아보자.


(1) Tagged 템플릿 리터럴

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

* 예를 들어 다음 코드와 실행 결과를 확인해 보자.

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

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

* 만약 다음과 같은 함수를 작성하고 나서 해당 함수 뒤에 템플릿 리터럴을 넣어 준다면, 템플릿 안에 넣은 값을 온전히 추출할 수 있을 것이다.

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

* 해당 코드의 결과를 직접 확인하고 싶다면 크롬 브라우저의 개발자 콘솔을 열어서 자바스크립트 콘솔에 위 코드를 붙여 넣어 보자. 그러면 다음과 같은 결과가 나타날 것이다.

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


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

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

import styled from 'styled-components';

const MyComponent = styled.div`
	font-size: 2rem;
`;

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

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

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

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

// 아예 컴포넌트 형식의 값을 넣어 줌
const StyledLink = styled(Link)`
	color: blue;
`;
* 여기서 사용된 Link 컴포넌트는 나중에 리액트 라우터를 배울 때 사용할 컴포넌트이다.
  이런 식으로 컴포넌트를 styled의 파라미터에 넣는 경우에는 해당 컴포넌트에 className props를
  최상위 DOM의 className 값으로 설정하는 작업이 내부적으로 되어 있어야 한다.
  
  다음 예시 코드를 확인해 보자.
  
  ex)
  
  const Sample = ({ className }) => {
  	return <div className={className}>Sample</div>
  };
  
  const StyledSample = styled(Sample)`
  	font-size: 2rem;
  `;

(3) 스타일에서 props 조회하기

* styled-components를 사용하면 스타일 쪽에서 컴포넌트에서 전달된 props 값을 참조할 수 있다. 이전에 작성했던 Box 컴포넌트를 다시 보자.

* StyledComponent.js > Box 컴포넌트

const Box = styled.div`
  /* props로 넣어준 값을 직접 전달해 줄 수 있다. */
  background: ${(props) => props.color || "blue"};
  padding: 1rem;
  display: flex;
`;

* 이 코드를 보면 background 값에 props를 조회해서 props.color의 값을 사용하게 했다. 그리고 color 값이 주어지지 않았을 때는 blue를 기본 색상으로 설정했다.

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

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

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

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

* 앞에서 작성한 Button 컴포넌트를 다시 한 번 확인해 보자.

import React from "react";
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를 사용하여 서로 다른 스타일을 적용할 수 있다.

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

* 스타일 코드 여러 줄을 props에 따라 넣어 주어야 할 때는 CSS를 styled-components에서 불러 와야 한다. CSS를 사용하지 않고 다음과 같이 바로 문자열을 넣어도 작동하기는 한다.

  ${(props) =>
    props.inverted &&
    css`
      background: none;
      border: 2px solid white;
      color: white;

      &:hover {
        background: white;
        color: black;
      }
    `};

* 이렇게 했을 때는 해당 내용이 그저 문자열로만 취급되기 때문에 VS Code 확장 프로그램에서 신택스 하이라이팅이 제대로 이루어지지 않는다는 단점이 따른다. 그리고 더욱 치명적인 단점은 Tagged 템플릿 리터럴이 아니기 때문에 함수를 받아 사용하지 못해 해당 부분에서는 props 값을 사용하지 못한다는 것이다. 만약 조건부 스타일링을 할 때 넣는 여러 줄의 코드에서 props를 참조하지 않는다면 굳이 CSS를 불러와서 사용하지 않아도 상관 없다. 하지만 props를 참조한다면, 반드시 CSS로 감싸 주어서 Tagged 템플릿 리터럴을 사용해 주어야 한다.


(5) 반응형 디자인

* 이번에는 styled-components를 사용할 때 반응형 디자인을 어떻게 하는지 한번 알아보자. 브라우저의 가로 크기에 따라 다른 스타일을 적용하기 위해서는 일반 CSS를 사용할 때와 똑같이 media 쿼리(query)를 사용하면 된다. 조금 전 작성한 Box 컴포넌트를 다음과 같이 수정해 보자.

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

브라우저 가로 크기가1024px 이상일 때
브라우저 가로 크기가 1024px 미만일 때 가로 크기가 768px로 줄어든다.
브라우저 크기가 768px 미만일 때 가로 크기는 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 all and (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%;`};
`;

/* 단순 변수의 형태가 가니라 여러 줄의 스타일 구문을 조건부로 설정해야 하는 경우에는 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;
  }
`;

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

export default StyledComponent;

styled-components: Advanced Usage

 

styled-components: Advanced Usage

Theming, refs, Security, Existing CSS, Tagged Template Literals, Server-Side Rendering and Style Objects

styled-components.com

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


5. 정리

* 이 장에서는 다양한 리액트 컴포넌트 스타일링 방식을 배워 보았다. 모두 쓸모 있는 기술들이다. 이러한 방식들 중 무엇을 사용할 지 선택하는 것은 여러분의 몫이다.

'React_리액트 시작' 카테고리의 다른 글

(22) 컴포넌트 스타일링 2  (0) 2023.12.05
(21) 컴포넌트 스타일링 1  (1) 2023.12.04
(20) Hooks 2  (1) 2023.12.04
(19) Hooks 1  (0) 2023.12.04
(18) 컴포넌트의 라이프사이클 메서드 2  (1) 2023.12.04