본문 바로가기
Recap/bookshelf

[리팩토링 #01.] 중복되는 코드를 Dumb component로 만들어 리팩토링하기

by yerin.dev 2024. 2. 25.

중복되는 코드를 Dumb component로 만들어 리팩토링하기

Tailwindcss

Tailwind를 사용해서 스타일링을 하다보니 클래스네임이 너무 길어져서 정신이 없는 경우가 많다. 더군다나 버튼 같이 자주 쓰이고 스타일링도 필요한 요소들은 어떻게든 깔끔한 코드를 만들고 싶은 욕구를 불러 일으킨다. 이참에 기존의 정신없던 코드를 리팩토링 해봤다.

 

 

 

1단계(코드 수정 전) : 일반적인 <button> 태그에 긴 클래스네임을 달아서 스타일링을 한다. 😵‍💫

 

 

// LoginPage.tsx

<button className="py-2 h-[48px] rounded-lg border border-l-border dark:border-d-border text-l-text-primary dark:text-d-text-primary hover:bg-l-bg-secondary dark:hover:bg-d-bg-secondary">회원가입 하기</>
<button className="bg-[#ff6a6b] text-d-text-primary hover:bg-[#fb7777]">구글 로그인 하기</>

 

 

물론 tailwind.config.js에 color명 같은 것은 따로 설정해 두는 것은 필수!

 

 

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  darkMode: "class",
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {
      colors: {
        "l-text-primary": "#000000",
        "l-text-secondary": "#666666",
        // .. 생략
        accent: "#ffbb55",
        "accent-fade": "#F3B251",
        error: "rgb(239 68 68)",
      },
    },
  },
  plugins: [],
};

 

 

 

2단계 : 공통적인 스타일링이 들어간 <button>이 여러 번 사용될 경우 btn 등의 클래스네임을 설정하고, 클래스들을 index.css로 옮긴다. 이때 @layer components안에 selector를 넣어주며 @apply를 써서 tailwind 유틸리티 클래스들을 사용한다. 사실 이 정도만 해도 '깔끔한 코드'는 만들 수 있다. 다만 index.css를 너무 적극적으로 활용하는 것은 tailwind의 편리함을 최대한으로 이용할 수 없는 방법이라고 생각해서 다음 단계로 넘어가게 되었다.

 

 

// index.css
@layer components {
  .btn {
      @apply py-2 h-[48px] rounded-lg border border-l-border dark:border-d-border text-l-text-primary dark:text-d-text-primary hover:bg-l-bg-secondary dark:hover:bg-d-bg-secondary;
    }

  .btn-home {
      @apply rounded-[100px] w-[200px] first-of-type:border-l-text-secondary first-of-type:dark:border-d-text-secondary hover:bg-l-text-primary  hover:text-l-bg-primary hover:dark:bg-d-bg-secondary
    last-of-type:bg-accent 
    last-of-type:hover:bg-[#F3B251] last-of-type:hover:dark:bg-[#F3B251] last-of-type:dark:text-l-text-primary;
    }
  }

 

 

 

3단계 : 아예 <Button> 컴포넌트를 만든다. 이때 이 컴포넌트는 dumb component(presentational component)이다. 로직에 대해서는 신경쓰지 않고, 어떻게 렌더링되어야 하는지만 알고 있는 컴포넌트다. 상위 컴포넌트를 통해서 그때그때 상황에 맞는 prop을 내려받아서 렌더링한다.


코드의 깔끔함 만으로 보면 그냥 클래스로 묶어서 css 파일에 정리하는 게 가장 깔끔한 방법일 수도 있지만, 이렇게 하면 컴포넌트 내에서 스타일링까지 할 수 있다. 무엇보다 이 컴포넌트를 사용하는 쪽에서의 가독성도 더 좋아진다.

 

 

 

import React from "react";
import { cn } from "../../lib/utils";
// cn 헬퍼함수(twMerge와 clsx 모두 사용)을 불러온다.

// theme에 따라 색깔이나 모양 등이 달라진다. 
type ButtonProps = {
  children: React.ReactNode;
  theme?: string;
  onClick?: () => void;
  disabled?: boolean;
  type?: "submit";
};

export default function Button({
  children,
  theme,
  onClick,
  disabled,
  type,
}: ButtonProps) {
  let customClassNames;
  switch (theme) {
    case "accent":
      customClassNames = "bg-accent hover:bg-accent-fade";
      break;
    case "reverse":
      customClassNames =
        "bg-d-bg-primary text-d-text-primary dark:bg-l-bg-primary dark:text-l-text-primary hover:bg-d-bg-secondary hover:dark:bg-l-bg-secondary";
      break;
    case "rounded-accent":
      customClassNames =
        "rounded-[100px] w-[200px] hover:bg-l-text-primary  hover:text-l-bg-primary hover:dark:bg-d-bg-secondary bg-accent hover:bg-accent-fade hover:dark:bg-[#F3B251] dark:text-l-text-primary";
      break;
    // ...생략
  }

  return (
    <button
      className={cn(
        "py-2 h-[48px] rounded-lg border border-l-border dark:border-d-border text-l-text-primary dark:text-d-text-primary hover:bg-l-bg-secondary dark:hover:bg-d-bg-secondary w-full",
        customClassNames
      )}
      onClick={onClick}
      disabled={disabled}
      type={type}
    >
      {children}
    </button>
  );
}

 

 

 

리팩토링을 진행하면서 '클린 코드', '좋은 코드'란 상황에 따라 달라지는 것이구나라는 생각이 든다. 2단계의 코드보다 더 길어졌지만 따로 컴포넌트로 분리함으로써 전체적인 코드의 파악이 더 쉬워졌고 유지보수하기 좋은 코드를 만들어냈다. 다른 컴포넌트들도 조금씩 이런 dumb component로 만들어서 ui와 로직을 분리하는 게 좋을 것 같다.