이 페이지에서는 각 웹 페이지에 자주 쓰이는 요소들을 어떤 식으로 컴포넌트화 하는지, 어떠한 팁들이 있는지 적어낼 것이다.
먼저, 버튼의 경우 많은 페이지에 쓰이기 때문에 여러 형태의 버튼들을 컴포넌트화 시켜야 한다.
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)를 사용하여 이용.
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>
);
}
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>
);
}
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>
);
}
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>
);
}
const router = useRouter();
// useRouter을 통하여 현재 user 가 어떤 페이지에 있는지 확인후 하단 tab bar의 색을 바꿔 주기 위해 선언되었다.
const onClick = () => {
router.back();
}; // 뒤로 가기 버튼을 위한 화살표 함수.
router.pathname === "/pageName" ? "참 조건" : "아닐시 조건"
이외에도 컴포넌트들을 더욱 세분화 할수 있을것 같다. 이와 같이 컴포넌트화 작업은 큰 규모의 프로젝트 할때에 아주 중요한 포인트가 될것 같다. 그러기 위해 더 생산성 있는 코드를 만들기 위한 많은 고민과 이에 관한 서적들을 읽어보면 좋을것 같다.