UI는 지금 추천하는 도서 (3개의 추천 책), 등록된 모든 도서 (3개의 추천 책)이 렌더링 되는 것을 만드려고 합니다.
index.tsx로 와서 section을 2개로 나눈 후 처음 섹션은 지금 추천하는 도서 두 번째 섹션은 등록된 모든 도서로 구분해줍니다.
import { ReactNode } from "react";
import SearchableLayout from "./components/searchable-layout";
export default function Home() {
return (
<div>
<section>
<h3>추천하는 도서</h3>
</section>
<section>
<h3>등록된 모든 도서</h3>
</section>
</div>
);
}
Home.getLayout = (page: ReactNode) => {
return <SearchableLayout>{page}</SearchableLayout>;
};
후 먼저 기본 container 설정을 해줍니다.
.container {
display: flex;
flex-direction: column;
gap: 20px;
}
.container h3 {
margin-bottom: 0px;
}
book-item.tsx를 만들어 컴포넌트를 만들어 줍니다.
export default function BookItem() {
return <div>도서 아이템</div>;
}
임시 데이터 Mock을 만들어 src 폴더안에 mock을 만들어 준후 books.json을 만들고 데이터를 넣어주시면 됩니다.
그 다음 index.tsx로 돌아와서 books.json을 import 한 후 map 함수를 통해 BookItem 컴폰넌트로 보내주면 됩니다.
import { ReactNode } from "react";
import SearchableLayout from "./components/searchable-layout";
import style from "./index.module.css";
import books from "@/mock/books.json";
import BookItem from "./components/book-item";
export default function Home() {
return (
<div className={style.container}>
<section>
<h3>추천하는 도서</h3>
{books.map((book) => (
<BookItem key={book.id} {...book} />
))}
</section>
<section>
<h3>등록된 모든 도서</h3>
</section>
</div>
);
}
Home.getLayout = (page: ReactNode) => {
return <SearchableLayout>{page}</SearchableLayout>;
};
그 다음 book-item에서 map으로 받아준 값을 받고 프롭스들의 타입들을 정의한 후 꾸며줍니다.
import { BookData } from "@/types";
import Link from "next/link";
import style from "./book-item.module.css";
export default function BookItem({ id, title, subTitle, description, author, publisher, coverImgUrl }: BookData) {
return (
<Link href={`/book/${id}`} className={style.container}>
<img src={coverImgUrl} />
<div>
<div className={style.title}>{title}</div>
<div className={style.subTitle}>{subTitle}</div>
<br />
<div className={style.author}>
{author} | {publisher}
</div>
</div>
</Link>
);
}
.container {
display: flex;
gap: 15px;
padding: 20px 10px;
border-bottom: 1px solid rgb(220, 220, 220);
text-decoration: none;
color: black;
}
.container img {
width: 80px;
}
.title {
font-weight: bold;
}
.subTitle {
word-break: keep-all;
}
.author {
color: gray;
}
이제는 search 연결된 값이 title 값과 일치하는 결과가 나오도록 하겠습니다.
search/index.tsx로 들어가
똑같이 json 파일을 받아와주는 Page 컴포넌트를 만들면 됩니다.
import { ReactNode } from "react";
import SearchableLayout from "../components/searchable-layout";
import books from "@/mock/books.json";
import BookItem from "../components/book-item";
export default function Page() {
return (
<div>
{books.map((book) => (
<BookItem key={book.id} {...book} />
))}
</div>
);
}
Page.getLayout = (page: ReactNode) => {
return <SearchableLayout>{page}</SearchableLayout>;
};
이번에는 책을 클릭하는 순간 상세페이지로 넘어가는 기능을 만들어 보려고 합니다.
import style from "./[id].module.css";
const mockData = {
id: 1,
title: "한 입 크기로 잘라 먹는 리액트",
subTitle: "자바스크립트 기초부터 애플리케이션 배포까지",
description:
"자바스크립트 기초부터 애플리케이션 배포까지\n처음 시작하기 딱 좋은 리액트 입문서\n\n이 책은 웹 개발에서 가장 많이 사용하는 프레임워크인 리액트 사용 방법을 소개합니다. 인프런, 유데미에서 5000여 명이 수강한 베스트 강좌를 책으로 엮었습니다. 프런트엔드 개발을 희망하는 사람들을 위해 리액트의 기본을 익히고 다양한 앱을 구현하는 데 부족함이 없도록 만들었습니다. \n\n자바스크립트 기초 지식이 부족해 리액트 공부를 망설이는 분, 프런트엔드 개발을 희망하는 취준생으로 리액트가 처음인 분, 퍼블리셔나 백엔드에서 프런트엔드로 직군 전환을 꾀하거나 업무상 리액트가 필요한 분, 뷰, 스벨트 등 다른 프레임워크를 쓰고 있는데, 실용적인 리액트를 배우고 싶은 분, 신입 개발자이지만 자바스크립트나 리액트 기초가 부족한 분에게 유용할 것입니다.",
author: "이정환",
publisher: "프로그래밍인사이트",
coverImgUrl: "https://shopping-phinf.pstatic.net/main_3888828/38888282618.20230913071643.jpg",
};
export default function Page() {
const { id, title, subTitle, description, author, publisher, coverImgUrl } = mockData;
return (
<div className={style.container}>
<div className={style.cover_img_container} style={{ backgroundImage: `url('${coverImgUrl}')` }}>
<img src={coverImgUrl} />
</div>
<div className={style.title}>{title}</div>
<div className={style.subTitle}>{subTitle}</div>
<div className={style.author}>
{author} | {publisher}
</div>
<div className={style.description}>{description}</div>
</div>
);
}
.container {
display: flex;
flex-direction: column;
gap: 10px;
}
.cover_img_container {
display: flex;
justify-content: center;
padding: 20px;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
position: relative;
}
.cover_img_container::before {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
content: "";
}
.cover_img_container > img {
z-index: 1;
max-height: 350px;
height: 100%;
}
.title {
font-size: large;
font-weight: bold;
}
.subTitle {
color: gray;
}
.author {
color: gray;
}
.description {
background-color: rgb(245, 245, 245);
padding: 15px;
line-height: 1.3;
white-space: pre-line;
border-radius: 5px;
}