NextJs - UI 구현하기

김명원·2025년 2월 27일

learnNextjs

목록 보기
10/24

UI 구현하기

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와 연결하기

이제는 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;
}
profile
개발자가 되고 싶은 정치학도생의 기술 블로그

0개의 댓글