Carrot market 정복 노트 [4] - "컴포넌트 화 시키기"

Jay·2022년 3월 14일
1

이 페이지에서는 각 웹 페이지에 자주 쓰이는 요소들을 어떤 식으로 컴포넌트화 하는지, 어떠한 팁들이 있는지 적어낼 것이다.

먼저, 버튼의 경우 많은 페이지에 쓰이기 때문에 여러 형태의 버튼들을 컴포넌트화 시켜야 한다.

1. submit 버튼 컴포넌트화 시키기

components/button.tsx

import { cls } from "../libs/utils";

interface ButtonProps {
  large?: boolean;
  // 조금은 작은 버튼을 위해 크기 옵션을 위해 선언.
  text: string;
  //버튼내 text 를 위한것.
  [key: string]: any;
  // 어떠한 타입의 형태든지 prop을 보낼수 있게 선언한 이유는 나중에 placeholder , required 등을 설정하게 될경우를 위한것.
}

export default function Button({
  large = false,
  onClick,
  text,
  ...rest
  // ...rest는 지금은 일시적으로 적어둔 이후에 사용 되어질 변수에 대한 선언.
}: ButtonProps) {
  return (
    <button
      {...rest}
      className={cls(
        "w-full bg-orange-500 hover:bg-orange-600 text-white  px-4 border border-transparent rounded-md shadow-sm font-medium focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 focus:outline-none",
        large ? "py-3 text-base" : "py-2 text-sm "
      )}
    >
      {text}
    </button>
  );
}

잠깐! : typescript type 설정중 any라고 설정해 둔 것의 의미는, 어떠한 타입의 형태든지 prop을 보낼수 있게 해주기 위한것. 예를 들어 placeholder , required 등을 설정하게 될경우를 위한것.
적용 방법은 ...(Spread Operator)를 사용하여 이용.

  • point : 크기를 정해주는 large 옵션을 주어 스타일이 조금 다르게 해야한다면 이렇게 옵션으로 주면 된다.

2. FloatingButton 컴포넌트화 시키기

components/floating-button.tsx

import Link from "next/link";
import React from "react";

interface FloatingButton {
  children: React.ReactNode;
  // 각 페이지 내용에 맞는 아이콘을 넣어주기 위해 react 컴포넌트를 타입스크립트로 선언하였다.
  href: string;
  //href 타입 선언은 페이지 이동의 url 설정을 위한 것.
}

export default function FloatingButton({ children, href }: FloatingButton) {
  return (
    <Link href={href}>
      <a className="fixed hover:bg-orange-500 border-0 aspect-square border-transparent transition-colors cursor-pointer  bottom-24 right-5 shadow-xl bg-orange-400 rounded-full w-14 flex items-center justify-center text-white">
        {children}
      </a>
    </Link>
  );
}
  • point : children에는 각 페이지 내용에 맞는 아이콘이 들어가기 위해 선언된 type.

3. input 컴포넌트화 시키기

components/input.tsx

interface InputProps {
  label: string;
  name: string;
  kind?: "text" | "phone" | "price";
  // 기본 text 입력폼, phone# 입력폼, price 입력폼을 위한 옵션
  [key: string]: any;
}

export default function Input({
  label,
  name,
  kind = "text",
  ...rest 
}: InputProps) {
  return (
    <div>
      <label
        className="mb-1 block text-sm font-medium text-gray-700"
        htmlFor={name}
      >
        {label}
      </label>
      {kind === "text" ? (
        <div className="rounded-md relative flex items-center shadow-sm">
          <input
            id={name}
            {...rest}
            className="appearance-none w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-orange-500 focus:border-orange-500"
          />
        </div>
      ) : null}
      {kind === "price" ? (
        <div className="rounded-md relative flex  items-center shadow-sm">
          <div className="absolute left-0 pointer-events-none pl-3 flex items-center justify-center">
            <span className="text-gray-500 text-sm">$</span>
          </div>
          <input
            id={name}
            {...rest}
            className="appearance-none pl-7 w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-orange-500 focus:border-orange-500"
          />
          <div className="absolute right-0 pointer-events-none pr-3 flex items-center">
            <span className="text-gray-500">KRW</span>
          </div>
        </div>
      ) : null}
      {kind === "phone" ? (
        <div className="flex rounded-md shadow-sm">
          <span className="flex items-center justify-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 select-none text-sm">
            +82
          </span>
          <input
            id={name}
            {...rest}
            className="appearance-none w-full px-3 py-2 border border-gray-300 rounded-md rounded-l-none shadow-sm placeholder-gray-400 focus:outline-none focus:ring-orange-500 focus:border-orange-500"
          />
        </div>
      ) : null}
    </div>
  );
}
  • point : type에 따라 원하는 input 스타일을 지정할수 있다.

4. item 컴포넌트화 시키기.

import Link from "next/link";

interface ItemProps {
  title: string;
  id: number;
  price: number;
  comments: number;
  hearts: number;
  // 아이템 단위의 컴포넌트화 시키기 위힘이기에, title 부터 hearts 아이콘까지 type 설정을 해주었다.
}

export default function Item({
  title,
  price,
  comments,
  hearts,
  id,
}: ItemProps) {
  return (
    <Link href={`/items/${id}`}>
    // ${id} 나중 게시된 판매 목록들에 대한 넘버를 지정해주어 key 로 사용되어 질것.
      <a className="flex px-4 pt-5 cursor-pointer justify-between">
        <div className="flex space-x-4">
          <div className="w-20 h-20 bg-gray-400 rounded-md" />
          <div className="pt-2 flex flex-col">
            <h3 className="text-sm font-medium text-gray-900">{title}</h3>
            <span className="font-medium mt-1 text-gray-900">${price}</span>
          </div>
        </div>
        <div className="flex space-x-2 items-end justify-end">
          <div className="flex space-x-0.5 items-center text-sm  text-gray-600">
            <svg
              className="w-4 h-4"
              fill="none"
              stroke="currentColor"
              viewBox="0 0 24 24"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth="2"
                d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
              ></path>
            </svg>
            <span>{hearts}</span>
          </div>
          <div className="flex space-x-0.5 items-center text-sm  text-gray-600">
            <svg
              className="w-4 h-4"
              fill="none"
              stroke="currentColor"
              viewBox="0 0 24 24"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth="2"
                d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
              ></path>
            </svg>
            <span>{comments}</span>
          </div>
        </div>
      </a>
    </Link>
  );
}
  • point : 아이템 단위의 컴포넌트 구성 페이지. heart 와 comment type은 나중을 위한 count 용도로 number type으로 지정.

5. item 컴포넌트화 시키기.

import { cls } from "../libs/utils";

interface MessageProps {
  message: string;
  reversed?: boolean;
  // 대화창에서의 대화 상대와 자신의 메세지를 구분시키기 위한 type 설정.
  avatarUrl?: string;
}

export default function Message({
  message,
  avatarUrl,
  reversed,
}: MessageProps) {
  return (
    <div
      className={cls(
        "flex  items-start",
        reversed ? "flex-row-reverse space-x-reverse" : "space-x-2"
      )}
    >
      <div className="w-8 h-8 rounded-full bg-slate-400" />
      <div className="w-1/2 text-sm text-gray-700 p-2 border border-gray-300 rounded-md">
        <p>{message}</p>
      </div>
    </div>
  );
}
  • point : reversed prop에 따라 ui 스타일링을 다르게 할수 있음.

6. layout.tsx에서의 추가 코드

 const router = useRouter();
 // useRouter을 통하여 현재 user 가 어떤 페이지에 있는지 확인후 하단 tab bar의 색을 바꿔 주기 위해 선언되었다.
  const onClick = () => {
    router.back();
  }; // 뒤로 가기 버튼을 위한 화살표 함수.
router.pathname === "/pageName" ? "참 조건" : "아닐시 조건"
  • point : useRouter통하여 특정 페이지에 대한 조건적 스타일링 적용 예시.

이외에도 컴포넌트들을 더욱 세분화 할수 있을것 같다. 이와 같이 컴포넌트화 작업은 큰 규모의 프로젝트 할때에 아주 중요한 포인트가 될것 같다. 그러기 위해 더 생산성 있는 코드를 만들기 위한 많은 고민과 이에 관한 서적들을 읽어보면 좋을것 같다.

profile
React js 개발자

0개의 댓글