본문 바로가기

스타일 적용

React에서 스타일을 적용하는 방식은 크게 세 가지가 있습니다.

  • CSS
  • CSS-in-CSS(Sass 등)
  • CSS-in-JS (styled-components)

각 방식은 장단점이 있으며, 프로젝트의 규모와 팀의 선호도에 따라 선택할 수 있습니다.

1. CSS Module

일반적인 React 프로젝트에서 CSS를 작성할 때 컴포넌트 간 스타일이 충돌하는 문제가 자주 발생합니다. 이를 해결하기 위한 방법 중 하나가 CSS Modules입니다.

1.1 일반 CSS의 한계

부모 컴포넌트에서 import한 CSS는 자식 컴포넌트까지 자동으로 적용되며, 자식 컴포넌트에 새로 CSS를 적용하면 import 순서에 따라 스타일이 덮어써질 수 있습니다. 이는 컴포넌트 기반 개발을 하는 React에서 큰 문제가 될 수 있습니다.

간단한 예제를 통해 살펴보겠습니다. App → A → B 순서로 컴포넌트가 중첩되어 있고, 각 컴포넌트는 자신만의 CSS 파일을 import하고 있습니다.

// App.jsx
import './App.css';
import A from './A.jsx';

function App() {
    return (
        <div>
            hello
            <A/>
        </div>
    )
}

export default App
/* App.css */
div {
    background-color: red;
}
// A.jsx
import './A.css'
import B from './B.jsx';

function A() {
    return (
        <div>
            hello
            <B/>
        </div>
    );
}

export default A;
/* A.css */
div {
    background-color: green;
}
// B.jsx
import './B.css'

function B() {
    return (
        <div>
            Component B
        </div>
    );
}

export default B;
/* B.css */
div {
    background-color: blue;
}

위 코드에서 App은 빨간색, A는 초록색, B는 파란색 배경을 의도했지만, 실제로 실행하면 모든 div가 파란색으로 표시됩니다. 이는 CSS가 컴포넌트별로 분리되지 않고 전역으로 적용되기 때문입니다.

마지막으로 로드된 B.css의 스타일이 모든 div 요소에 적용되어, 의도했던 컴포넌트별 스타일링이 무너집니다.

index.js에 index.css를 import 하게 되면 전역으로 CSS가 적용됩니다. App.js에 App.css를 import하게 되면 App 컴포넌트에 들어있는 모든 컴포넌트에 적용됩니다.

1.2 CSS Module 사용하기

이러한 문제를 해결하기 위해 CSS Module을 사용할 수 있습니다. CSS Module은 다음과 같은 방식으로 사용합니다.

  1. 파일 확장자를 .module.css로 변경합니다.
  2. 태그 선택자 대신 클래스 선택자를 사용합니다.
  3. 클래스를 적용할 때는 styles.클래스명 형식으로 사용합니다.

이전에 작성한 코드에 CSS Module을 적용해보겠습니다.

// App.jsx
import styles from './App.module.css';
import A from './A.jsx';

function App() {
    return (
        <div className={styles.background}>
            hello
            <A/>
        </div>
    )
}

export default App
/* App.module.css */
.background {
    background-color: red;
}
// A.jsx
import styles from './A.module.css'
import B from './B.jsx';

function A() {
    return (
        <div className={styles.background}>
            hello
            <B/>
        </div>
    );
}

export default A;
/* A.module.css */
.background {
    background-color: green;
}
// B.jsx
import styles from './B.module.css'

function B() {
    return (
        <div className={styles.background}>
            Component B
        </div>
    );
}

export default B;
/* B.module.css */
.background {
    background-color: blue;
}

이제 실행하면 각 컴포넌트가 의도한 대로 독립적인 배경색을 가지게 됩니다. App은 빨간색, A는 초록색, B는 파란색으로 표시됩니다.

개발자 도구를 확인해보면 같은 .background라는 클래스 이름을 사용했지만, 각각 App_background__x7d2k, A_background__a3f1j와 같이 고유한 해시값이 자동으로 붙은 것을 확인할 수 있습니다.

이처럼 CSS Module을 사용하면 클래스 이름이 자동으로 생성되기 때문에 클래스 이름을 고민하는 시간을 줄일 수 있고, CSS 작성에만 집중할 수 있습니다. 특히 여러 개발자가 함께 작업하는 프로젝트에서 클래스 이름 충돌로 인한 문제를 예방할 수 있어 매우 유용합니다.

주의

module.css를 사용할 때 주의할 점은 적용하고자 하는 컴포넌트 이름과 파일명을 반드시 일치시켜줘야 한다는 것입니다. 예를 들어 Detail.jsx 파일에만 적용해주고 싶은 module.css의 파일명은 Detail.module.css여야합니다.

2. styled-components

styled-components는 CSS-in-JS의 대표적인 라이브러리입니다. 컴포넌트 단위로 스타일을 작성할 수 있어 React의 컴포넌트 기반 개발 방식과 잘 어울리며, JavaScript의 모든 기능을 활용할 수 있다는 장점이 있습니다.

2.1 styled-components 설치

npm install styled-components

2.2 기본 사용법

styled-components는 템플릿 리터럴을 사용하여 CSS를 작성합니다. 생성된 스타일은 고유한 클래스 이름을 가진 컴포넌트가 됩니다.

import styled from 'styled-components';

// 스타일이 적용된 h1 컴포넌트 생성
const Title = styled.h1`
  font-size: 24px;
  color: #333;
  margin-bottom: 16px;
`;

// 스타일이 적용된 button 컴포넌트 생성
const Button = styled.button`
  background-color: #007bff;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    background-color: #0056b3;
  }
`;

function App() {
  return (
    <div>
      <Title>안녕하세요!</Title>
      <Button>클릭하세요</Button>
    </div>
  );
}

export default App;

위 코드의 핵심을 정리하면 다음과 같습니다.

  1. styled.h1, styled.button처럼 styled.태그명 형태로 작성합니다.
  2. 백틱(`) 안에 일반 CSS 문법으로 스타일을 작성합니다.
  3. &:hover처럼 Sass 문법도 사용할 수 있습니다.
  4. 생성된 컴포넌트는 일반 React 컴포넌트처럼 사용합니다.

Tagged Template Literal

styled-components에서 CSS를 작성할 때 사용하는 문법을 tagged template literal이라고 합니다.

const Title = styled.h1`
  font-size: 24px;
  color: #333;
`;

tagged template literal은 템플릿 리터럴(백틱 문자열)을 함수의 인자로 전달하는 JavaScript 문법입니다. styled.h1이 함수이고, 백틱 안의 CSS 문자열이 인자로 전달됩니다.

이 문법의 장점은 문자열 안에서 ${}를 사용하여 JavaScript 표현식을 삽입할 수 있다는 것입니다. 이를 활용한 동적 스타일링은 2.5절에서 자세히 다룹니다.

참고 Template literals - JavaScript | MDN

2.3 전역 스타일 적용하기

styled-components의 createGlobalStyle()을 사용하면 전역 스타일을 설정할 수 있습니다. 전역 스타일은 모든 컴포넌트에 공통으로 적용되는 스타일을 말합니다. 예를 들어, 웹페이지의 배경색이나 글꼴 설정 등은 모든 컴포넌트에서 공통으로 사용되는 스타일이므로 전역으로 관리하는 것이 효율적입니다. 먼저 createGlobalStyle() 함수를 사용하여 전역 스타일 컴포넌트를 생성합니다. App.js(현재 최상위 컴포넌트) 상단에 추가해주면 모든 하위 컴포넌트에 스타일이 적용됩니다. 이 글로벌 스타일은 다른 스타일보다 나중에 로딩이 되어 같은 가중치라면 기본적으로 우선순위가 높다는 특징이 있습니다.

// App.js
import { createGlobalStyle } from 'styled-components';
import Example from './Example';

const GlobalStyle = createGlobalStyle`
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }

  body {
    font-family: Arial, sans-serif;
    line-height: 1.5;
    color: red;
    background-color: #f4f4f4;
  }
`;

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

export default App;
// Example.js
export default function Example() {
  return (
    <>
      <h1>hello world 3</h1>
      <span>hello world 4</span>
    </>
  );
}

2.4 Reset css

styled-reset | npm

Reset css는 styled-reset이라는 패키지를 다운받은 뒤 styled-reset이 제공하는 reset을 글로벌 CSS에 적용합니다. 단독으로 사용하셔도 무방합니다.

normalize 코드도 제공하고 있습니다.

styled-normalize | npm

2.4.1 styled-reset 설치하기

npm i styled-reset

2.4.2 글로벌 스타일에 styled-reset 적용하기

import { createGlobalStyle } from 'styled-components';
import Example from './Components/Example';
import reset from 'styled-reset';

const GlobalStyle = createGlobalStyle`
    ${reset}
    
    span {
    color: red;
    font-size: 12px;
    }

    a{
        text-decoration : none;
        color : inherit;
    }

    button{
        border : none;
        cursor : pointer;
    }

    * {
    box-sizing: border-box;
    }
`;

function App() {
  return (
    <>
      <GlobalStyle />
      <h1>hello world 1</h1>
      <span>hello world 2</span>
    </>
  );
}

export default App;

2.5 props를 활용한 동적 스타일링

styled-components의 강력한 기능 중 하나는 props를 통한 조건부 스타일링입니다. 문자열 리터럴 안에 자바스크립트 코드를 넣을 때 $를 사용하듯이 ${} 안에 코드를 입력해주면 됩니다.

import React from 'react';
import styled from 'styled-components';

const Button = styled.button`
  background-color: ${(props) => (props.type ? '#007bff' : '#6c757d')};
  color: white;
  padding: ${(props) => (props.size === 'large' ? '12px 24px' : '8px 16px')};
  border: none;
  border-radius: 4px;
  font-size: ${(props) => (props.size === 'large' ? '16px' : '14px')};
  cursor: pointer;

  &:hover {
    opacity: 0.8;
  }
`;

function App() {
  return (
    <div>
      <Button>기본 버튼</Button>
      <Button type="primary">주요 버튼</Button>
      <Button size="large">큰 버튼</Button>
    </div>
  );
}

export default App;

2.6 스타일 확장하기

기존 스타일을 확장하여 새로운 컴포넌트를 만들 수 있습니다.

const Button = styled.button`
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
`;

const PrimaryButton = styled(Button)`
  background-color: #007bff;
  color: white;
`;

const DangerButton = styled(Button)`
  background-color: #dc3545;
  color: white;
`;

2.7 그 외 다양한 문법

아래와 같이 Sass문법을 사용할 수도 있습니다.

// Sass 문법
a {
  /* style 1 */
  &::after {
    /* style 2 */
  }
}

// 컴파일 후
// a { /* style 1 */ }
// a::after { /* style 2 */ }
// Styled-components
const UniAfter = styled.div`
  &:after {
    content: '!!';
  }
`;

또한 아래와 같은 CSS 확장도 가능합니다.

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

const One = css`
  color: red;
`;

const Two = css`
  border: 1px solid black;
`;

const Three = styled.div`
  ${One}
  ${Two}
`;

const App = () => {
  return <Three>Lorem ipsum dolor</Three>;
};

export default App;

2.8 실습: 다양한 버튼 만들기

다음과 같은 버튼들을 styled-components를 사용하여 만들어보세요.

[요구사항]

  • 기본 버튼 스타일을 정의하고, 이를 확장하여 다른 스타일의 버튼을 만듭니다
  • props를 활용하여 색상(color)을 조절할 수 있게 만듭니다
  • fullWidth의 값이 true라면 버튼의 넓이는 100%가 됩니다.
  • size의 값으로 ‘large’ 가 전달되면 hover시 버튼의 크기는 1.5배가 됩니다.

[기본 코드]

<StyledButton color="danger" size="large" fullWidth>
  hello
</StyledButton>

Transient props(임시 속성)

만약 style을 위한 props의 양이 많아져서 최종적으로 돔 트리에 랜더링 될 props가 불필요하게 많다면 임시 속성으로 만들어 props의 랜더링을 막을 수 있습니다. 이는 styled-components 만의 고유한 기능이며, props의 이름 앞에 $를 붙이면 지정됩니다.

그리고 이와 같은 방법은 react가 명확히 boolean 속성값을 가지는 속성으로 인식하지 못하는 사용자 정의 속성의 경우도 true, false 와 같은 boolean 값을 가지도록 하는 수단이 될 수 있습니다.

<StyledProps size="large" $fullwidth={false}>
  버튼입니다.
</StyledProps>

위의 fullwidth는 앞에 $가 없다면 boolean 값 대신 문자열(’false’)을 이용해야합니다.