'동적인 카드 만들기'를 통해 CSS Modules, styled-components, Tailwind CSS를 알아보자!

정우시·2023년 5월 1일
3

우아한테크코스

목록 보기
4/14
post-thumbnail

Prologue

우아한테크코스 레벨 2에서 온보딩 미션을 지나 본격적인 미션을 하게 되었습니다. 그것은 바로 페이먼츠 미션!

페이먼츠 미션부터는 UI를 담당하는 HTML과 CSS가 기본적으로 제공되지 않았습니다. 그로 인해 피그마 파일만 보고 이와 관련된 디자인을 직접 해야 했습니다.

리액트에서는 순수 CSS는 잘 사용을 하지 않으며 다양한 CSS 라이브러리들을 사용하는 것으로 알고 있습니다.

저는 이전 온보딩 미션과 이번 미션에서는 CSS Modules를 이용하였습니다. 그러나 다른 크루들을 보니 styled-components를 많이 사용하는 것을 확인하였습니다.

앞으로 다양한 CSS 라이브러리들을 다뤄야 할 일들이 계속해서 생길 것입니다. 따라서 저는 이를 대비해 현업에서 자주 쓰이는 CSS Modules, styled-components, Tailwind CSS를 이번 미션에서 했던 동적인 카드 만들기를 통해 알아보고자 하였습니다.

CSS Modules

소개

CSS Modules는 Glen Maddern과 Mark Dalgleish이 공동으로 개발한 기술로 css-loader와 style-loader를 사용하여 CSS 파일을 모듈화하고 번들링합니다.

파일이름이 .module.css로 끝나게 되면, 자동으로 CSS Modules로 처리가 됩니다. Create React App로 리액트를 시작하는 경우 별도의 설치 없이 .module.css이라는 파일 이름을 통해 바로 사용이 가능합니다.

사용법

1️⃣ Create React App 프로젝트를 생성하거나 Create React App을 사용 안하는 경우 css-loader와 style-loader를 별도로 설치 합니다.

2️⃣ CSS 파일을 생성하고, 파일 이름 뒤에 .module.css를 붙입니다.

BlackButton.module.css

.container {
  margin-top: 30px;
}

3️⃣ CSS 적용을 원하는 파일에 import styles from './파일이름.module.css';의 형식으로 작성하여 import를 합니다.

BlackButton.tsx

import styles from "./BlackButton.module.css";

const BlackButton = () => {
  return <div>BlackButton</div>;
};

export default BlackButton;

4️⃣ 그리고 CSS가 적용되기를 원하는 태그에 className 속성 값으로 styles.명명한 className이라는 형식으로 작성을 하여 CSS 클래스를 적용합니다.

BlackButton.tsx

import styles from "./BlackButton.module.css";

const BlackButton = () => {
  return <div className={styles.container}>BlackButton</div>;
};

export default BlackButton;

4️⃣ 이렇게 작성하면, CSS Modules로 처리된 클래스 이름이 styles객체에 저장되며, 해당 객체를 통해 CSS 클래스 이름을 사용할 수 있습니다.

동적 스타일링 작성 방법

CSS Modules 만으로는 동적인 스타일링은 어렵지만 HTML에서 스타일을 지정할 때 사용하는 속성인 style을 함께 사용함으로써 동적 스타일링이 가능합니다.

리액트에서 style 속성은 자바스크립트 객체를 이용하여 CSS 스타일을 표현합니다. 따라서 처음에는 style={ }이라는 형식으로 표현을 합니다.

이후 자바스크립트 객체 리터럴 표기법을 사용하여 CSS 속성 이름을 작성합니다. 그리고 나서 해당 CSS 속성 이름의 값을 넣어주며 됩니다.

<section style={{ backgroundColor: cardColor }} /> 이러한 형식으로 작성하면 됩니다.

kebab-case(하이픈(-)을 사용한 표기법)으로 작성되지만, 리액트에서는 자바스크립트 객체로 CSS 스타일을 표현할 때 camelCase(카멜케이스, 첫 단어는 소문자로 시작하고 그 다음 단어부터 대문자로 시작하는 표기법)를 사용합니다.

그렇기 때문에 background-color 속성 등 리액트에서 관련 속성들을 이용할 경우 backgroundColor으로 작성됩니다.

저는 style 속성을 CSS Modules과 함께 이용하여 동적으로 카드가 변경되도록 만들었습니다.

PostCssCardPreview.tsx

import { useState } from "react";
import styles from "./CardPreview.module.css";
import BlackButton from "../Button/BlackButton/BlackButton";

const PostCssCardPreview = () => {
  const [cardColor, setCardColor] = useState("");

  const handleBlackButton = () => {
    cardColor === "" ? setCardColor("black") : setCardColor("");
  };

  return (
    <article>
      <section className={styles.card} style={{ backgroundColor: cardColor }}>
        <div className={styles.chip} />
      </section>
      <BlackButton onClick={handleBlackButton} />
    </article>
  );
};

export default PostCssCardPreview;

PostCssCardPreview.module.css

.card {
  display: flex;
  padding: 0px 12px 4px 12px;
  align-items: center;
  margin: 0 auto 0 auto;
  width: 208px;
  height: 123px;
  border-radius: 5px;
  border: none;
  cursor: pointer;
  transition: 0.3s;
  box-shadow: 3px 3px 10px #667085;
}

.chip {
  width: 40px;
  height: 26px;
  background-color: #cbba64;
  border-radius: 4px;
}

장점

  • 로컬 스코프
    CSS Modules에서 정의된 클래스는 로컬 스코프 내에서만 유효합니다. 즉, 다른 모듈에서 같은 클래스 이름을 사용해도 각각의 모듈에서는 서로 다른 클래스로 취급됩니다. 이로 인해 CSS의 전역 네임스페이스 충돌을 방지할 수 있습니다.

  • 이름 충돌 방지
    CSS Modules에서는 자동으로 고유한 클래스 이름을 생성하므로, 클래스 이름이 충돌하는 것을 방지할 수 있습니다.

→ 위의 사진처럼 HTML상에서 div의 class에 hash값이 붙어 클래스가 생성된 것을 볼 수 있습니다.

단점

  • 파일 증가

리액트에서는 컴포넌트 단위로 개발을 하는 만큼 컴포넌트의 양이 많을 수록 관련된 CSS 파일을 계속해서 따로 만들어 주어야 한다.

  • 동적 변경 어려움

동적으로 원하는 값을 할당하려면 style 속성을 사용해야 한다.

styled-components

소개

styled-components 또한 CSS Modules를 개발한 Glen Maddern과 Max Stoiber에 의해 개발된 기술로 자바스크립트 파일 내에서 CSS를 사용할 수 있게 해주는 대표적인 CSS-in-JS 라이브러리입니다.

사용법

1️⃣ npm install styled-components을 통해 설치를 합니다.

2️⃣ styled 이름으로 styled-components를 import 합니다.

import styled from "styled-components";

3️⃣ CSS의 적용을 원하는 태그를 정합니다.

styled.div

4️⃣ 그리고 나서 백틱을 통해 CSS 속성을 문자열로 표기합니다.

styled.div`
 margin-top: 30px;
`

5️⃣ 내가 원하는 이름으로 컴포넌트화를 합니다.

const Container = styled.div`
  margin-top: 30px;
`;

6️⃣ 마지막으로 컴포넌트에 적용을 하면 됩니다.

import styled from "styled-components";

const Container = styled.div`
  margin-top: 30px;
`;

const BlackButton = () => {
  return <Container>Black Button</Container>;
};

export default BlackButton;

동적 스타일링 작성 방법

styled-components는 태그를 컴포넌트화하여 스타일링을 하기 때문에 props를 통해 동적으로 스타일링을 할 수 있습니다.

1️⃣ Card라는 스타일드 컴포넌트를 하나 만듭니다.

import styled from "styled-components";

const Card = styled.section`
  background-color: ${(props) => props.cardColor};
`;

2️⃣ tsx로 작성하기 때문에 props에 대한 타입이 필요합니다.

import styled from "styled-components";

type cardColor = {
  cardColor: string;
};

const Card = styled.section<cardColor>``;

3️⃣ props를 받아서 cardColor이 동적으로 변하게 합니다. 이때 props는 달러 기호(${})를 이용해서 표현합니다.

import styled from "styled-components";

type cardColor = {
  cardColor: string;
};

const Card = styled.section<cardColor>`
  background-color: ${(props) => props.cardColor};
`;

4️⃣ 마지막으로 버튼을 클릭하면 Card 컴포넌트에 cardColor 상태를 전달하여 배경색이 변경되도록 합니다.

import { useState } from "react";
import styled from "styled-components";
import BlackButton from "../Button/BlackButton/BlackButton";

type cardColor = {
  cardColor: string;
};

const Card = styled.section<cardColor>`
  background-color: ${(props) => props.cardColor};
`;

const StyledComponentsCardPreview = () => {
  const [cardColor, setCardColor] = useState("");

  const handleBlackButton = () => {
    cardColor === "" ? setCardColor("black") : setCardColor("");
  };

  return (
    <article>
      <Card cardColor={cardColor}>
      </Card>
      <BlackButton onClick={handleBlackButton} />
    </article>
  );
};

export default StyledComponentsCardPreview;

장점

  • 스타일과 컴포넌트가 결합하여 관리가 용이

styled-components는 스타일과 컴포넌트를 하나의 파일에서 관리할 수 있습니다. 이는 스타일과 컴포넌트의 연관성을 명확히 하고, 관리를 편리하게 합니다.

  • CSS 클래스 이름 충돌 방지

styled-components는 컴포넌트 내부에서만 스타일이 유효하므로, CSS 클래스 이름 충돌이 발생하지 않습니다. 이는 전역 스타일과 스타일 우선순위 문제를 해결할 수 있습니다.

  • 동적인 스타일링 용이

props를 통해 동적으로 스타일을 변경할 수 있으며, 상태와 함께 사용하면 더욱 다양한 스타일링이 가능합니다.

단점

  • 코드량이 많아질 수록 가독성 저하
    하나의 파일에서 스타일링과 컴포넌트를 관리할 수 있다는 점이 장점이 될 수 있지만, 반대로 코드량이 많아질수록 가독성이 저하가 될 수 있습니다.

Tailwind CSS

Tailwind CSS는 Adam Wathan, Steve Schoger, David Hemphill, Jonathan Reinink의 4 명으로 구성된 개발자 팀이 만들었습니다.

Tailwind CSS는 2017년 처음 공개되었으며, 매우 인기 있는 CSS 프레임워크 중 하나입니다.

Tailwind CSS의 가장 큰 특징은 CSS를 정의하는 것이 아니라 잘 정의된 클래스를 이용해 스타일링을 할 수 있다는 점입니다. (ex. padding-top: 16px → pt-4 )

따라서 HTML 태그에서 바로 해당 클래스만 적으면 빠르고 간편하게 스타일링이 바로 적용이 됩니다.

사용법

1️⃣ npm install -D tailwindcss를 설치합니다.

2️⃣ npx tailwindcss init을 하게 되면 tailwind.config.js이 생성이 됩니다.

3️⃣ 템플릿 파일들에 대한 경로를 아래와 같이 설정합니다.

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

4️⃣ 제일 상위에 있는 CSS 파일에 아래와 같은 내용을 첨부합니다.

@tailwind base;
@tailwind components;
@tailwind utilities;

5️⃣ 원하는 요소에 className 속성을 지정하여 Tailwind CSS의 클래스를 적용하면 됩니다.

const BlackButton = () => {
  return (
    <div className="mt-7" />
  );
};

export default BlackButton;

Tailwind CSS를 이용하면서 알게된 Tip

  • Tailwind CSS를 사용하기 위해서는 최상위 CSS 파일에 @tailwind base;, @tailwind components;, @tailwind utilities;를 적게 되는데 이 3개의 코드를 적게 되면 버튼의 기본 모양이 없어집니다.

Tailwind CSS가 아닌 다른 CSS의 버튼

Tailwind CSS의 버튼

크롬 개발자 도구를 보면 @tailwind base;를 통해 버튼의 기본적인 스타일링이 초기화가 되었다는 것을 알 수 있습니다.

해당 익스텐션을 이용하면 Tailwind CSS와 관련된 클래스들을 자동 완성 해줍니다.

만약 설치를 했는데 적용이 안되는 경우 Visual Studio Code의 settings.json에서 관련된 설정을 수정을 해주시면 됩니다.

{
  // other settings
  "tailwindCSS.includeLanguages": {
    "javascript": "javascript",
    "html": "HTML"
  },
  "editor.quickSuggestions": {
    "strings": true
  }
}

Tailwind CSS를 사용하게 되면 해당 클래스들이 길게 늘어지는 경우가 생깁니다. 이는 가독성을 떨어뜨립니다. 이를 위해 클래스들을 정렬시켜주는 플러그인이 있습니다.

이것을 사용하게 되면 Tailwind CSS 클래스가 포함된 클래스 속성에 대한 템플릿을 스캔한 다음 권장 클래스 순서 에 따라 해당 클래스를 자동으로 정렬합니다.

해당 플러그인의 사용을 원하는 경우 터미널에npm install -D prettier prettier-plugin-tailwindcss라는 명령어를 통해 사용할 수 있습니다.

<!-- Before -->
<button className="rounded border-black 2g-white p-1 text-black border-2">...</button>

<!-- After -->
<button className="2g-white rounded border-2 border-black p-1 text-black">...</button>
  • 미리 정의된 클래스가 아닌 커스텀마이징 가능

대괄호([])를 이용하면 내가 원하는 사이즈나 디자인으로 커스텀마이징을 할 수 있습니다.

<div className="h-[26px] w-[40px] bg-[#cbba64]" />

동적 스타일링 작성 방법

대표적으로 조건문을 통해 동적인 스타일링이 가능하다.

import { useState } from "react";
import BlackButton from "../Button/BlackButton/BlackButton";

const TailwindCssCardPreview = () => {
  const [cardColor, setCardColor] = useState(true);

  const handleBlackButton = () => {
    cardColor === true ? setCardColor(false) : setCardColor(true);
  };
  return (
    <article>
      <section
        className={`${
          cardColor ? "bg-white" : "bg-black"
        } px-3 pb-1 pt-0 transition duration-300`}
      >
        <div className="h-[26px] w-[40px] rounded bg-[#cbba64]" />
      </section>
      <BlackButton onClick={handleBlackButton} />
    </article>
  );
};

export default TailwindCssCardPreview;

장점

  • 빠른 개발 속도

미리 정의된 클래스를 이용하기 때문에 안정된 디자인을 빠르게 구현할 수 있습니다.

  • 일관된 디자인

클래스 기반 스타일링을 사용함으로써, 스타일 규칙이 다른 스타일 규칙과 충돌할 가능성이 낮습니다.

단점

  • 학습 비용 有

미리 정의된 클래스를 사용을 해야 하기 때문에 러닝 커브가 있을 수 있습니다.

  • 적용되는 클래스가 많아질 경우 가독성 하락

하나의 태그에 적용되는 Tailwind CSS 클래스가 많아질 경우 가독성이 하락 할 가능성이 있습니다.

이로 인해 클래스들이 하나의 태그에서 중복될 가능성이 있습니다.

Epilogue

소규모 프로젝트인 경우 스타일드 컴포넌트와 같은 CSS-in-JS 방식이 유용하다고 생각합니다. 왜냐하면 빠르게 작성하고 빠르게 테스트를 해야하기 때문입니다.

하지만 프로젝트의 규모가 커질 수록 CSS 파일을 분업화 할 수 있는 CSS Modules와 같은 CSS-in-CSS 방식이 더 좋다고 생각합니다.

이와 더불어 Tailwind CSS를 사용하면 빠르게 스타일링된 코드를 작성할 수 있습니다. 다만 동적인 스타일링은 다른 기술에 비하면 아쉽기 때문에 무조건 Tailwind CSS가 좋다고 할 수는 없습니다.

우아한테크코스에서는 미션을 진행하면서 왜 이 기술을 사용했는지에 대한 리뷰를 받습니다.

본인이 맡고 있는 프로젝트에 알맞은 기술을 적용시키는 것도 개발자의 역량 중 하나라고 생각합니다.

앞으로 CSS 뿐만 아니라 프로젝트의 성격을 파악한 후 알맞는 기술이 무엇인지 고민해보는 것이 중요하다는 사실을 다시 한번 느끼게 되었습니다.


깃허브: practice-css

profile
프론트엔드 공부하고 있는 정우시입니다.

0개의 댓글