챕터 10: 첫 번째 컴포넌트 만들기
서론
이전 챕터에서 컴포넌트의 개념을 배웠다면, 이제는 실제로 우리만의 컴포넌트를 처음부터 끝까지 만들어볼 시간입니다. 처음 컴포넌트를 만들 때는 어디서부터 시작해야 할지 막막할 수 있습니다. 하지만 걱정하지 마십시오. 이번 챕터에서는 가장 기본적인 버튼 컴포넌트부터 시작하여, 점차 복잡한 카드 컴포넌트까지 단계별로 만들어보겠습니다.
실습을 통해 컴포넌트를 만드는 과정을 체득하고, 자신만의 컴포넌트 라이브러리를 구축하는 첫걸음을 내딛어보겠습니다. 이 챕터를 마치면 여러분은 어떤 디자인이든 컴포넌트로 만들 수 있는 자신감을 갖게 될 것입니다.
본론
버튼 컴포넌트 만들기 - 단계별 접근
가장 기본적이면서도 중요한 버튼 컴포넌트부터 만들어봅시다. 먼저 components 폴더가 없다면 생성하고, 그 안에 Button.jsx 파일을 만듭니다.
Step 1: 가장 기본적인 버튼
// components/Button.jsx
export default function Button() {
return (
<button>
클릭하세요
</button>
)
}
Step 2: 스타일 추가하기
// components/Button.jsx
export default function Button() {
return (
<button className="bg-blue-500 text-white px-4 py-2 rounded">
클릭하세요
</button>
)
}
Step 3: 호버 효과와 트랜지션 추가
// components/Button.jsx
export default function Button() {
return (
<button className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors duration-200">
클릭하세요
</button>
)
}
Step 4: 클릭 이벤트 추가
// components/Button.jsx
export default function Button() {
const handleClick = () => {
alert('버튼이 클릭되었습니다!')
}
return (
<button
onClick={handleClick}
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors duration-200 active:scale-95"
>
클릭하세요
</button>
)
}
이제 페이지에서 버튼을 사용해봅시다:
// app/page.js
import Button from '../components/Button'
export default function Home() {
return (
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">나의 첫 버튼 컴포넌트</h1>
<Button />
</div>
)
}
카드 컴포넌트 만들기
이제 좀 더 복잡한 카드 컴포넌트를 만들어봅시다. 카드는 이미지, 제목, 설명, 버튼 등 여러 요소를 포함하는 컴포넌트입니다.
// components/Card.jsx
export default function Card() {
return (
<div className="max-w-sm rounded-lg overflow-hidden shadow-lg bg-white hover:shadow-xl transition-shadow duration-300">
{/* 이미지 영역 */}
<div className="h-48 bg-gradient-to-r from-purple-400 to-pink-400"></div>
{/* 콘텐츠 영역 */}
<div className="p-6">
{/* 카테고리 태그 */}
<span className="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mb-2">
#개발
</span>
{/* 제목 */}
<h2 className="font-bold text-xl mb-2">
카드 컴포넌트 제목
</h2>
{/* 설명 */}
<p className="text-gray-700 text-base mb-4">
이것은 카드 컴포넌트의 설명 부분입니다.
여러 줄의 텍스트를 포함할 수 있으며,
내용을 간략하게 요약하여 보여줍니다.
</p>
{/* 하단 정보 */}
<div className="flex items-center justify-between">
<div className="text-sm text-gray-600">
2024년 1월 15일
</div>
<button className="bg-purple-500 hover:bg-purple-600 text-white px-3 py-1 rounded text-sm transition-colors">
자세히 보기
</button>
</div>
</div>
</div>
)
}
프로필 카드 컴포넌트
사용자 프로필을 표시하는 특별한 카드 컴포넌트를 만들어봅시다:
// components/ProfileCard.jsx
export default function ProfileCard() {
return (
<div className="max-w-xs mx-auto bg-white rounded-xl shadow-md overflow-hidden hover:shadow-xl transition-shadow duration-300">
{/* 배경 이미지 */}
<div className="h-32 bg-gradient-to-r from-blue-500 to-purple-600"></div>
{/* 프로필 정보 */}
<div className="relative px-6 pb-6">
{/* 프로필 이미지 */}
<div className="absolute -top-12 left-1/2 transform -translate-x-1/2">
<div className="w-24 h-24 bg-white rounded-full border-4 border-white shadow-lg">
<div className="w-full h-full rounded-full bg-gradient-to-r from-green-400 to-blue-500 flex items-center justify-center text-white text-2xl font-bold">
JD
</div>
</div>
</div>
{/* 이름과 직책 */}
<div className="pt-14 text-center">
<h3 className="text-xl font-bold text-gray-800">홍길동</h3>
<p className="text-gray-600 text-sm">프론트엔드 개발자</p>
</div>
{/* 통계 */}
<div className="mt-4 flex justify-around border-t pt-4">
<div className="text-center">
<div className="text-2xl font-bold text-gray-800">42</div>
<div className="text-xs text-gray-600">프로젝트</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-gray-800">1.2k</div>
<div className="text-xs text-gray-600">팔로워</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-gray-800">128</div>
<div className="text-xs text-gray-600">팔로잉</div>
</div>
</div>
{/* 액션 버튼 */}
<div className="mt-4 flex gap-2">
<button className="flex-1 bg-blue-500 hover:bg-blue-600 text-white py-2 rounded-lg transition-colors">
팔로우
</button>
<button className="flex-1 border border-gray-300 hover:bg-gray-50 text-gray-700 py-2 rounded-lg transition-colors">
메시지
</button>
</div>
</div>
</div>
)
}
네비게이션 바 컴포넌트
웹사이트의 필수 요소인 네비게이션 바를 컴포넌트로 만들어봅시다:
// components/Navbar.jsx
export default function Navbar() {
return (
<nav className="bg-white shadow-lg">
<div className="max-w-7xl mx-auto px-4">
<div className="flex justify-between items-center h-16">
{/* 로고 */}
<div className="flex items-center">
<span className="text-2xl font-bold text-blue-600">
MyLogo
</span>
</div>
{/* 메뉴 - 데스크톱 */}
<div className="hidden md:flex items-center space-x-8">
<a href="#" className="text-gray-700 hover:text-blue-600 transition-colors">
홈
</a>
<a href="#" className="text-gray-700 hover:text-blue-600 transition-colors">
소개
</a>
<a href="#" className="text-gray-700 hover:text-blue-600 transition-colors">
서비스
</a>
<a href="#" className="text-gray-700 hover:text-blue-600 transition-colors">
포트폴리오
</a>
<a href="#" className="text-gray-700 hover:text-blue-600 transition-colors">
연락처
</a>
</div>
{/* CTA 버튼 */}
<div className="hidden md:flex items-center">
<button className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition-colors">
시작하기
</button>
</div>
{/* 모바일 메뉴 버튼 */}
<div className="md:hidden flex items-center">
<button className="text-gray-700 hover:text-blue-600 focus:outline-none">
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
</div>
</div>
</nav>
)
}
모달 컴포넌트
사용자 상호작용을 위한 모달(팝업) 컴포넌트를 만들어봅시다:
// components/Modal.jsx
export default function Modal() {
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-8 max-w-md w-full mx-4 transform transition-all">
{/* 모달 헤더 */}
<div className="flex justify-between items-center mb-4">
<h2 className="text-2xl font-bold text-gray-800">
모달 제목
</h2>
<button className="text-gray-500 hover:text-gray-700">
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* 모달 본문 */}
<div className="mb-6">
<p className="text-gray-600">
이것은 모달 컴포넌트입니다. 중요한 정보를 표시하거나
사용자의 확인이 필요할 때 사용합니다.
</p>
</div>
{/* 모달 푸터 */}
<div className="flex justify-end gap-3">
<button className="px-4 py-2 text-gray-600 hover:text-gray-800 transition-colors">
취소
</button>
<button className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors">
확인
</button>
</div>
</div>
</div>
)
}
폼 입력 컴포넌트
사용자 입력을 받는 폼 컴포넌트를 만들어봅시다:
// components/ContactForm.jsx
export default function ContactForm() {
return (
<form className="max-w-lg mx-auto bg-white p-8 rounded-lg shadow-lg">
<h2 className="text-2xl font-bold mb-6 text-gray-800">
문의하기
</h2>
{/* 이름 입력 */}
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2">
이름
</label>
<input
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500 transition-colors"
placeholder="홍길동"
/>
</div>
{/* 이메일 입력 */}
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2">
이메일
</label>
<input
type="email"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500 transition-colors"
placeholder="example@email.com"
/>
</div>
{/* 메시지 입력 */}
<div className="mb-6">
<label className="block text-gray-700 text-sm font-bold mb-2">
메시지
</label>
<textarea
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500 transition-colors h-32 resize-none"
placeholder="문의 내용을 입력해주세요..."
/>
</div>
{/* 제출 버튼 */}
<button
type="submit"
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg transition-colors"
>
전송하기
</button>
</form>
)
}
실습: 컴포넌트 조합하여 완전한 페이지 만들기
이제 우리가 만든 모든 컴포넌트를 조합하여 완전한 페이지를 구성해봅시다:
// app/showcase/page.js
import Navbar from '../../components/Navbar'
import Button from '../../components/Button'
import Card from '../../components/Card'
import ProfileCard from '../../components/ProfileCard'
import ContactForm from '../../components/ContactForm'
export default function ShowcasePage() {
return (
<div className="min-h-screen bg-gray-50">
<Navbar />
{/* 히어로 섹션 */}
<section className="py-20 bg-gradient-to-r from-blue-500 to-purple-600">
<div className="container mx-auto text-center text-white">
<h1 className="text-5xl font-bold mb-4">
컴포넌트 쇼케이스
</h1>
<p className="text-xl mb-8">
우리가 만든 모든 컴포넌트를 한 곳에서 확인하세요
</p>
<Button />
</div>
</section>
{/* 카드 섹션 */}
<section className="py-16 container mx-auto">
<h2 className="text-3xl font-bold text-center mb-12">
카드 컴포넌트
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 px-4">
<Card />
<Card />
<Card />
</div>
</section>
{/* 프로필 섹션 */}
<section className="py-16 bg-gray-100">
<div className="container mx-auto">
<h2 className="text-3xl font-bold text-center mb-12">
팀 멤버
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 px-4">
<ProfileCard />
<ProfileCard />
<ProfileCard />
</div>
</div>
</section>
{/* 연락처 섹션 */}
<section className="py-16">
<div className="container mx-auto px-4">
<ContactForm />
</div>
</section>
</div>
)
}
컴포넌트 개발 팁
1. 작게 시작하기: 복잡한 컴포넌트도 작은 부분부터 시작합니다.
2. 단계별 개선: 기본 구조 → 스타일 → 상호작용 순으로 개발합니다.
3. 재사용성 고려: 다른 곳에서도 사용할 수 있도록 범용적으로 만듭니다.
4. 명확한 책임: 하나의 컴포넌트는 하나의 명확한 역할만 담당해야 합니다.
5. 일관된 스타일: 프로젝트 전체에서 일관된 디자인 패턴을 유지합니다.
결론
축하합니다! 여러분은 이제 다양한 종류의 컴포넌트를 직접 만들 수 있게 되었습니다. 버튼, 카드, 프로필 카드, 네비게이션 바, 모달, 폼 등 웹사이트의 핵심 구성 요소들을 모두 컴포넌트로 만들어보았습니다.
컴포넌트를 만드는 것은 처음에는 어려워 보일 수 있지만, 하나씩 만들다 보면 패턴이 보이기 시작합니다. 대부분의 컴포넌트는 비슷한 구조를 가지고 있으며, 스타일과 내용만 다를 뿐입니다.
이제 여러분은 어떤 디자인이든 컴포넌트로 만들 수 있는 기본기를 갖추었습니다. 다음 챕터에서는 Props를 사용하여 이 컴포넌트들을 더욱 유연하고 재사용 가능하게 만드는 방법을 배워보겠습니다. Props를 활용하면 같은 컴포넌트로도 다양한 모습을 표현할 수 있습니다!