-
나와 동료를 위한 React 재사용 컴포넌트Frontend 2023. 8. 2. 22:38반응형
서론
Frontend 개발자로 일을 시작한지 벌써 7년차입니다. 여태까지 일하면서 여러가지 케이스를 많이 봤는데요.
Frontend 개발자 관점에서 일을 어렵게 하는 방법은 어떤 것이 있을까요?
저는 재사용 컴포넌트 없이 개발하는 상황이 가장 어렵고 힘들다고 생각합니다.
개발자마다 각각의 기준을 가지고 있기 때문인데요.
이를 효과적으로 통제하기 위한 방법은 UI프레임워크나 라이브러리를 사용하는 것입니다.
대부분의 경우 디자인, 테마 등 이미 구현된 많은 기능들로 신규서비스의 초기 개발단계에서 많이 사용합니다.
하지만 이러한 상황이 발생할 수 있습니다.
1. 만드는 프로젝트에 적합한 기능이 없는 경우 (많이 발생하지는 않습니다.)
2. 간단한 UI인 것 같은데, import 용량이 큰 경우 (용량이 크면 당연히 안좋겠죠.)
3. 눈에 보여지는 것과는 다르게 DOM depth가 깊어지는 경우 (브라우저 메모리를 많이 사용하여 성능에 악영향을 끼칩니다.)
신규서비스의 초기 개발단계에서는 개발속도를 위해 어쩔 수 없다며 무심코 넘어가면
사용자에게 초기 화면을 보여줄 때(FCP, First Contentful Paint), 늦게 나오거나
늦게 나온것을 감수한 사용자가 화면이 보이자마자 이벤트를 발생 시킬 때(TTI, Time To Interactive), 아무 응답이 없는 상황이 발생하여
답답함을 참지 못한 사용자는 서비스를 이탈할 수 있습니다.
만약 사용자의 네트워크 속도가 느린 환경이라면, 이것 역시 시너지 효과를 일으켜 다시는 서비스를 거들떠 보지도 않는 상황이 발생합니다.
고생해서 개발을 했지만...
신규서비스가 사용자에게 매력적이지 않은 것이라면, 차라리 다행일 수도 있습니다.
하지만 느린 속도 때문에 외면 받는 것이라면, 이거는 정말 큰 문제입니다.
위의 상황을 조금이라도 회피하기 위해서 우리는 경량화와 프로젝트에 적합한 기능들을 재사용할 수 있는 컴포넌트가 필요합니다.
반응형본론
재사용 컴포넌트를 만들 환경
- Frontend
- Next.js 13.4.12 - Dependency
- tailwindcss
- tailwind-merge
- class-variance-authority
- clsx
상황1. 우기는 기획자에게 검은 화면 정중앙에 빨간색으로 된 버튼을 요청받았고, 버튼의 텍스트는 "버튼" 입니다.
정말 간단해보이는 기획서에 신이 난 우기는, 인정을 받고 싶은 생각에 재빠르게 개발하기로 마음 먹었습니다.
그리고 재사용을 고려하지 않은 button 컴포넌트를 작성하기로 했습니다.
재사용을 고려하지 않은 button 컴포넌트 작성
"use client"; export default function UiTemplate() { return ( <main className='flex w-full justify-center items-center h-screen'> <button className='w-10 h-10 bg-red-600'> 버튼 </button> </main> ); }
우기: 요구하신 화면이에요! 빠르게 잘했죠? 상황2. 우기는 기획자에게 검은 화면 정중앙에 빨간색으로 된 버튼 옆에, 컬러만 파란색으로 변경된 같은 버튼을 요청받았습니다. 물론 이번에도, 버튼의 텍스트는 "버튼" 입니다.
이번에도 정말 간단해보이는 기획서에 신이 난 우기는, 능력을 인정 받은 것 같다는 생각에 또다시 재빠르게 개발하기로 마음 먹었습니다.
그리고 이번에도 재사용을 고려하지 않은 button 컴포넌트를 작성하기로 했습니다.
기존 코드를 복사 및 붙여넣기 한 후, 파란색이 들어갈 수 있도록 css를 바꿔주면 되겠죠?
"use client"; export default function UiTemplate() { return ( <main className='flex w-full justify-center items-center h-screen'> <button className='w-10 h-10 bg-red-600'> 버튼 </button> <button className='w-10 h-10 bg-blue-600'> 버튼2 </button> </main> ); }
우기: 요구하신 화면이에요! 이번에도 빠르게 잘했죠? 물론 기획자가 전체흐름을 보여주지 않고, 단일 페이지만 넘기는 경우는 이런 판단을 할 수 있습니다.
또한, 위의 설명을 보면 빨간색, 파란색이라고만 되어있는데 이와같이 불명확한 요구사항의 경우
갑작스럽게 디자인이 변경될 가능성이 존재합니다.
이처럼 명확한 지시사항이 없다면 모든 개발자가 같은 크기와 같은 컬러를 사용하지는 않을겁니다.
이 방법은 개발자가 자유롭다는 장점이 있지만, 바꿔말하면 자유롭기 때문에 개발자들의 심미적 레벨에 따라
구성되는 컴포넌트가 완전히 달라질 수 있습니다.
우리가 만드는 서비스가 규모가 작고, 컬러만 변하는 상황이라면 아직까지 큰 일이 아니라고 생각할 수 있습니다.
하지만 만들어진 페이지가 점점 늘어나고, 서비스 오픈을 목전에 둔 상황에서 갑자기 모든 버튼들의 크기와 컬러가
모두 변경되어야 하는 상황이라면 어떻게 할까요?
방법이 아예 없는건 아닙니다. 약간의 귀찮음과 위험을 감수하고 전체 찾기 후, 열심히 찾아서 변경하거나 일괄변경으로 되기는 합니다.
다만, 이런 일이 자주 생긴다면 어떨까요?
첫 번째, 서비스에 핵심이 되는 기능은 개발하지 못하고, 모든 화면에 존재하는 컴포넌트를 변경되는 디자인에 맞춰서 변경한다.
두 번째, 서비스에 기능이 추가되지 않는 것은 기획자의 잘못이니 나는 모르겠고, 다른 직원들에게 뒷말을 하기 시작한다.
세 번째, 개발자들이 반란을 일으켰다.
위의 상황이 발생했다면, 규모가 작은 회사일 수록 더욱 치명적일 것입니다.
또한, 모든 일에 남탓을 하기 시작하면 끝이 없습니다.
이런 일이 발생해도 대처가 가능하도록 하는 것이 개발자가 가져야 할 소명의식입니다.
그렇다면, 이 위기를 극복하기 위해서는 어떻게 해야할까요?
상황1-1. 우기는 기획자에게 검은 화면 정중앙에 빨간색으로 된 버튼을 요청받았고, 버튼의 텍스트는 "버튼" 입니다.
(다만, 디자인이 달라질 수 있다는 것과 동료들을 위해 재사용을 고려한 button 컴포넌트를 작성해야 할 것 같습니다.)재사용을 고려한 button 컴포넌트 작성
import { cn } from "@/utility/cn"; import { VariantProps, cva } from "class-variance-authority"; import Link from "next/link"; import React from "react"; const buttonVariants = cva("activate:scale-95 inline-flex items-center", { variants: { intent: { primary: ["bg-blue-500", "text-white"], secondary: ["bg-white", "text-gray-800"], red: ["bg-red-600"], blue: ["bg-blue-600"], cyan: ["bg-cyan-600"], }, variant: { default: ["hover:bg-slate-700"], outline: [ "bg-transparent", "border", "border-slate-200", "hover:bg-amber-100", "light:border-amber-700", "light:text-slate-100", "dark:bg-cyan-700", "dark:border-slate-700", "dark:text-slate-100", "dark:hover:bg-cyan-500", ], link: ["bg-transparent", "text-slate-900", "underline-offset-4", "hover:underline", "dark:text-slate-300"], }, size: { default: ["h-10", "py-2", "px-4"], sm: ["h-9", "px-2", "rounded-md"], md: ["h-10", "px-5", "rounded-md"], lg: ["h-11", "px-8", "rounded-md"], }, }, compoundVariants: [ { intent: "primary", size: "sm", className: "uppercase", }, ], defaultVariants: { variant: "default", size: "default", }, }); export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> { href?: string; } const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( ({ className, children, href, variant, size, intent, ...props }, ref) => { if (href) { return ( <Link href={href}> <button className={cn(buttonVariants({ intent, variant, size, className }))} {...props}> {children} </button> </Link> ); } else { return ( <button className={cn(buttonVariants({ intent, variant, size, className }))} ref={ref} {...props}> {children} </button> ); } } ); Button.displayName = "Button"; export { Button, buttonVariants };
tailwind merge를 합니다.
import { ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export const cn = (...inputs: ClassValue[]) => { return twMerge(clsx(inputs)); };
재사용을 고려한 button 컴포넌트를 import하고 속성 값을 채웁니다.
"use client"; import { Button } from "../@component/ui/button"; export default function UiTemplate() { return ( <main className='flex w-full justify-center items-center h-screen'> <Button intent={"red"} size={"sm"}> 버튼 </Button> </main> ); }
우기: 재사용 컴포넌트는 만들고나니 유용하네요. 상황2-1. 우기는 기획자에게 검은 화면 정중앙에 빨간색으로 된 버튼 옆에, 컬러만 파란색으로 변경된 같은 버튼을 요청받았습니다. 물론 이번에도, 버튼의 텍스트는 "버튼" 입니다. (다만, 이번에는 재사용 컴포넌트를 사용할 것입니다.)
마찬가지로 재사용을 고려한 button 컴포넌트를 import하고 속성 값을 채웁니다.
"use client"; import { Button } from "../@component/ui/button"; export default function UiTemplate() { return ( <main className='flex w-full justify-center items-center h-screen'> <Button intent={"red"} size={"sm"}> 버튼 </Button> <Button intent={"blue"} size={"sm"}> 버튼 </Button> </main> ); }
우기: 이번에도 빠르게 구현했어요! 상황3. 우기는 평화롭게 재사용 컴포넌트로 개발을 하던 와중에 상황이 발생했습니다.
바로, 빨간색과 파란색으로 된 모든 버튼들의 텍스트 컬러를 오렌지색으로 변경해야 합니다.재사용 컴포넌트로 화면을 개발하지 않았다면, 아마 이 상황에서 꽤나 고생을 하고 있을겁니다.
하지만 재사용 컴포넌트로 만들었다면 아래의 두 가지 방법 중 하나를 고를 수 있습니다.
1. intent에 red와 blue에 텍스트 컬러를 추가로 기입
2. 재사용 컴포넌트에 className을 추가하는 방법
1번 방법
... intent: { primary: ["bg-blue-500", "text-white"], secondary: ["bg-white", "text-gray-800"], red: ["bg-red-600", "text-orange-400"], blue: ["bg-blue-600", "text-orange-400"], cyan: ["bg-cyan-600"], }, ...
2번 방법
"use client"; import { Button } from "../@component/ui/button"; export default function UiTemplate() { return ( <main className='flex w-full justify-center items-center h-screen'> <Button intent={"red"} size={"sm"} className='text-orange-400'> 버튼 </Button> <Button intent={"blue"} size={"sm"} className='text-orange-400'> 버튼 </Button> </main> ); }
우기: 이번에도 빠르게 적용했어요! 재사용 컴포넌트를 사용한 덕분에 모든 페이지를 전부 찾아가며 바꾸거나
위험을 감수하고 일괄적용을 하는 방법을 선택하지 않아도 됩니다.
이처럼 재사용 컴포넌트는 당사자인 개발자, 동료들의 평화와 회사의 안녕을 위해서 작성해야 합니다.
또한 보통 재사용 컴포넌트는 UI 측면에서 말하는 경우가 많기 때문에, 예제를 UI 측면에서만 만들었는데,
특수한 기능이 내장되어 포함된 경우도 재사용 컴포넌트라고 말할 수 있습니다.
다만, 특수한 기능이 내장된 경우는 추가구현 없이 다양한 화면에서 바로 사용할 수 있어야 합니다.
그렇지 않다면, 무수히 많은... 자칭 재사용 컴포넌트로 인해 개발자가 고통받을 수 있습니다.
결론
지금 위의 코드를 이해하려고 하지 않아도 좋습니다.
쓰다보면 익숙해질테고, 중요한건 모두를 위해 재사용이 가능하도록 컴포넌트를 만드는 것입니다.
그렇기 때문에 위의 방식이 아닌, 다른 방식으로 만들어도 프로젝트에 적합하기만 하다면 좋습니다.
많은 개발자분들께서 다양한 시도를 하며, 서비스 또는 프로젝트가 잘 되도록 좋은 라이브러리를 만들어내는 현 상황이
우리 모두에게 좋은 상황인 것만은 분명합니다.
반응형 - Frontend