Framework/Next.js

[Next.js & React] ํŒŒ์ผ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ… ์ž‘์—…ํ•˜๊ธฐ

yuri lee 2022. 11. 13. 00:08
๋ฐ˜์‘ํ˜•
์ด ๊ธ€์€ udemy์˜ Next.js & React - ์™„๋ฒฝ ์ •๋ณต ๊ฐ€์ด๋“œ (incl. Two Paths) ๊ฐ•์ขŒ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ •๋ฆฌํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

 

69. ๋ชจ๋“ˆ ๊ฐœ์š”

 

์ด๋ฒˆ ์„น์…˜์—์„œ๋Š” ์ด๋ฒคํŠธ ์•ฑ์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งŒ๋“ค์–ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ ๋ชฉ๋ก์„ ํ™•์ธํ•˜๊ณ , ๊ทธ ์ค‘์—์„œ ํ•œ ์ด๋ฒคํŠธ๋ฅผ ์„ ํƒํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

 

  1. Creat a Complete NextJS Project From Scratch
  2. Add Static & Dynamic Routes
  3. Add Regular Components & Connect Everything! 

 

70. ํ”„๋กœ์ ํŠธ ๊ณ„ํš

  • / : Startign PAge (show featured Events)
  • /events: Events Page (show all Events)
  • /events/<some-id : Event Detail Page (show selected Event)
  • /evnets/,,,slug : Filtered Events Page (show filtered Events)

 

71. ๋ฉ”์ธ ํŽ˜์ด์ง€ ์„ค์ •ํ•˜๊ธฐ

๋ฉ”์ธ ํŽ˜์ด์ง€ ์„ค์ •ํ•˜๊ธฐ 

 

72. ๋”๋ฏธ ๋ฐ์ดํ„ฐ & ์ •์  ํŒŒ์ผ ์ถ”๊ฐ€ํ•˜๊ธฐ

์ด๋ฏธ์ง€์˜ ๊ฒฝ์šฐ ๋ฐ˜๋“œ์‹œ public ํด๋”์— ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์œ„ ํด๋”๋ช…์€ ๊ผญ images ์ผ ํ•„์š”๋Š” ์—†๊ณ , public ํด๋”์— ์ €์žฅํ•ด๋„ ๋ฌด๋ฐฉํ•˜์ง€๋งŒ ๋ฐ˜๋“œ์‹œ public ํด๋”์— ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. public ํด๋”๋Š” Next.js ํ”„๋กœ์ ํŠธ์—์„œ ํŠน์ˆ˜ํ•œ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ด ํด๋”์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” ์ด๋ฏธ์ง€๋‚˜ ๊ธ€๊ผด ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋“ค์€ Next.js์—์„œ ์ •์ (Static)์ธ ๋ฐ์ดํ„ฐ๋กœ ์ž‘์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— CSS๋‚˜ HTML ์ฝ”๋“œ์—์„œ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. public ํด๋”์— ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•ด๋‘๋ฉด Next.js์—์„œ๋Š” ๊ทธ ํด๋”์— ์ €์žฅ๋œ ๋ชจ๋“  ์ฝ˜ํ…์ธ ๋ฅผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ผ๋ถ€๋กœ ๊ฐ„์ฃผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ •์ ์ธ ์ฝ˜ํ…์ธ ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

 

73.  React ์ปดํฌ๋„ŒํŠธ ์ถ”๊ฐ€ํ•˜๊ธฐ

components>event> event-item.js, event-list.js ํŒŒ์ผ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.  ๋ณดํ†ต ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ JSX ์ฝ”๋“œ๋ฅผ ๊ตฌ์ƒํ•  ๋•Œ๋Š” ๊ฑด๋ฌผ์„ ์ง€์„ ๋•Œ ์“ฐ์ด๋Š” ๋ฒฝ๋Œ์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๋กœ ๋‚˜๋ˆ„์–ด์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•ฉ์ณ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์ถ•ํ•˜๋Š” ์Šต๊ด€์„ ๋“ค์ด๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. 

 

74. ๋” ๋งŽ์€ React ์ปดํฌ๋„ŒํŠธ ์ถ”๊ฐ€ํ•˜๊ธฐ & ์ปดํฌ๋„ŒํŠธ ์—ฐ๊ฒฐํ•˜๊ธฐ

react ์ปดํฌ๋„ŒํŠธ ์ถ”๊ฐ€ 

 

75. Next.js ํ”„๋กœ์ ํŠธ ๋‚ด์—์„œ ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ๋ง ํ•˜๊ธฐ

Next.js ํ”„๋กœ์ ํŠธ๋Š” module.css๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๋นŒ๋“œ ํ”„๋กœ์„ธ์Šค๊ฐ€ CSS ์ฝ”๋“œ๋ฅผ ์ถ”์ถœํ•ด์„œ ์„ ํƒ์ž๋ฅผ ๋ฐ”๊พธ๊ณ  ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ HTML ์ฝ”๋“œ์— ๋ฒ”์œ„๋ฅผ ์ง€์ •ํ•œ ๋‹ค์Œ ์‹คํ–‰ ์ค‘์ธ ํŽ˜์ด์ง€์— ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค.  module.css ํŒŒ์ผ์„ import ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š”, classes๋‚˜ styles ์ค‘ ํ•˜๋‚˜๋ฅผ ์ž…๋ ฅํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 <li className={classes.item}></li>
.item {
  color: red;
}โ€‹

 

event-item_๊ณ ์œ  ํ•ด์‹œ๊ฐ€ ๋ถ™์€ ํด๋ž˜์Šค๊ฐ€ ์ƒ๊ฒผ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

 

UI ํ™”๋ฉด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. 

76. ๋ฒ„ํŠผ & ์•„์ด์ฝ˜ ์ถ”๊ฐ€ํ•˜๊ธฐ

์ง์ ‘ ๋ˆˆ์—๋Š” ๋ณด์ด์ง€ ์•Š์ง€๋งŒ, Link ํƒœ๊ทธ๊ฐ€ ์ž์ฒด์ ์œผ๋กœ ์•ต์ปค ํƒœ๊ทธ๋ฅผ ๋ Œ๋”๋ง ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์ง€์ • ์Šคํƒ€์ผ์„ ์ ์šฉํ•˜๊ณ ์ž ํ•œ๋‹ค๋ฉด, ํ•ด๋‹น ์•ต์ปค ํƒœ๊ทธ๋Š” ์ง์ ‘ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์ฝ”๋“œ์—์„œ๋Š” Link ํƒœ๊ทธ๊ฐ€ ๋‚ด๋ถ€์— ์žˆ๋Š” ์•ต์ปค ํƒœ๊ทธ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์ž์ฒด์ ์œผ๋กœ ์•ต์ปค ํƒœ๊ทธ๋ฅผ ๋ Œ๋”๋ง ํ•˜๋Š” ๋Œ€์‹  ์ถ”๊ฐ€๋œ ์•ต์ปค ํƒœ๊ทธ๋ฅผ ๋ Œ๋”๋ง ํ•ฉ๋‹ˆ๋‹ค.

 

๋ณธ ์•ต์ปค ํƒœ๊ทธ์—๋Š” href ์†์„ฑ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” Link ํƒœ๊ทธ๊ฐ€ ์—ฌ๊ธฐ ์ž…๋ ฅ๋œ ๋‚ด์šฉ์— ๋”ฐ๋ผ ์ž์ฒด์ ์œผ๋กœ ์ถ”๊ฐ€ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. 

export default function Button(props) {
  return (
    <Link href={props.link}>
      <a className={classes.btn}>{props.children}</a>
    </Link>
  );
}

 

77. "Event Detail" ํŽ˜์ด์ง€ ์ถ”๊ฐ€ํ•˜๊ธฐ (๋™์  ๋ผ์šฐํŠธ)

useRouter hook์„ next/router์—์„œ ์ž„ํฌํŠธํ•ด ์˜จ ๋‹ค์Œ, router ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•˜๋„๋ก useRouter๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. router ๊ฐ์ฒด์—์„œ query ํ”„๋กœํผํ‹ฐ์— ์—‘์„ธ์Šค ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด ํ”„๋กœํผํ‹ฐ์—๋Š” ํ•ด๋‹น ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋Š” ๋ชจ๋“  ๋™์  ๊ฒฝ๋กœ segment๊ฐ€ key๋กœ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. 

export default function EventDetailPage() {
  const router = useRouter();
  const eventId = router.query.eventId;
  const event = getEventById(eventId);

  if (!event) {
    return <p>No event found! </p>;
  }
  return (
    <Fragment>
      <EventSummary title={event.title} />
      <EventLogistics
        date={event.date}
        address={event.location}
        image={event.image}
        imageAlt={event.title}
      />
      <EventContent>
        <p>{event.description}</p>
      </EventContent>
    </Fragment>
  );
}

 

78. ์ผ๋ฐ˜์ ์ธ ๋ ˆ์ด์•„์›ƒ ๋ž˜ํผ ์ปดํฌ๋„ŒํŠธ ์ถ”๊ฐ€ํ•˜๊ธฐ

ํ˜„์žฌ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ”๊ฐ€ ์—†์œผ๋ฏ€๋กœ ๋ชจ๋“  ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ์— ๋งจ ์œ„์— <header>๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ์ž‘์—…์„ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. 

ํ•˜์ง€๋งŒ ์ด ๊ฑ์šฐ์—๋Š” ๋ชจ๋“  ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ์— ๋™์ผํ•œ ์ฝ”๋“œ๋ฅผ ๋ฐ˜๋ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (์ค‘๋ณต!)

 

์ด ๊ฒฝ์šฐ์— _app.js ํŒŒ์ผ์ด ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์ด ๋ฃจํŠธ ์ปดํฌ๋„ŒํŠธ๋กœ์„œ ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง ๋˜๋Š” ๊ณณ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. Nextj.js๋Š” _app ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด์šฉํ•ด์„œ ํŽ˜์ด์ง€ ์ปจํ…์ธ ๋ฅผ ์ „๋‹ฌํ•˜๊ณ , ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•  ๋•Œ ์ฝ˜ํ…์ธ ๋ฅผ ํ‘œ์‹œํ•ด์ค๋‹ˆ๋‹ค. 

 

_app.js

import "../styles/globals.css";
import Layout from "../components/layout/layout";

function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  );
}

export default MyApp;

 

layout.js

import { Fragment } from "react";
import MainHeader from "./main-header";

function Layout(props) {
  return (
    <Fragment>
      <MainHeader />
      <main>{props.children}</main>
    </Fragment>
  );
}

export default Layout;

 

79. All Events ํŽ˜์ด์ง€ ์ž‘์—…   

๋ชจ๋“  ์ด๋ฒคํŠธ๊ฐ€ ๋ณด์ด๋Š” ํŽ˜์ด์ง€๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฏธ EventList ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ํŽธํ•˜๊ฒŒ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

import { getAllEvents } from "../../dummy-data";
import EventList from "../../components/events/event-list";

export default function AllEventsPage() {
  const events = getAllEvents();
  return (
    <div>
      <h1>All Events</h1>
      <EventList items={events} />
    </div>
  );
}

 

80. ์ด๋ฒคํŠธ ํ•„ํ„ฐ๋ง์„ ์œ„ํ•œ ํ•„ํ„ฐ ์–‘์‹ ์ถ”๊ฐ€ํ•˜๊ธฐ

ํ•„ํ„ฐ๋ง์„ ์œ„ํ•ด์„œ events-search.js์™€ module.css ํŒŒ์ผ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. 

import Button from "../ui/button";
import classes from "./events-search.module.css";

function EventSearch(props) {
  return (
    <form className={classes.form}>
      <div className={classes.controls}>
        <div className={classes.control}>
          <label htmlFor="year">Year</label>
          <select id="year">
            <option value="2021">2021</option>
            <option value="2022">2022</option>
          </select>
        </div>
        <div className={classes.control}>
          <label htmlFor="month">Month</label>
          <select id="month">
            <option value="1">January</option>
            <option value="2">February</option>
            <option value="3">March</option>
            <option value="4">April</option>
            <option value="5">May</option>
            <option value="6">June</option>
            <option value="7">July</option>
            <option value="8">August</option>
            <option value="9">Septemer</option>
            <option value="10">October</option>
            <option value="11">November</option>
            <option value="12">December</option>
          </select>
        </div>
      </div>
      <Button>Find Events</Button>
    </form>
  );
}

export default EventSearch;

 

81.  "Filtered Events" ํŽ˜์ด์ง€๋ฅผ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ๋‚ด๋น„๊ฒŒ์ดํŒ… ํ•˜๊ธฐ 

ํ˜„์žฌ๋Š” form ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ ์‹คํ–‰๋˜๋Š” ์ž‘์—…์ด ์•„๋ฌด๊ฒƒ๋„ ์—†์Šต๋‹ˆ๋‹ค. submitHandler ๋ผ๋Š” ํŠธ๋ฆฌ๊ฑฐ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. form ์š”์†Œ์— obSubmit ํ”„๋กœํผํ‹ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , submitHandler์„ ๊ฐ€๋ฆฌํ‚ค๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. submitHandler๋Š” form ์ œ์ถœ ์‹œ React๋ฅผ ํ†ตํ•ด์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. 

import { useRef } from "react";
import Button from "../ui/button";
import classes from "./events-search.module.css";

function EventSearch(props) {
  const yearInputRef = useRef();
  const monthInputRef = useRef();

  function submitHandler(event) {
    event.preventDefault();

    const selectedYear = yearInputRef.current.value;
    const selectedMonth = monthInputRef.current.value;

    props.onSearch(selectedYear, selectedMonth);
  }

  return (
    <form className={classes.form} onSubmit={submitHandler}>
      <div className={classes.controls}>
        <div className={classes.control}>
          <label htmlFor="year">Year</label>
          <select id="year" ref={yearInputRef}>
            <option value="2021">2021</option>
            <option value="2022">2022</option>
          </select>
        </div>
        <div className={classes.control}>
          <label htmlFor="month">Month</label>
          <select id="month" ref={monthInputRef}>
            <option value="1">January</option>
            <option value="2">February</option>
            <option value="3">March</option>
            <option value="4">April</option>
            <option value="5">May</option>
            <option value="6">June</option>
            <option value="7">July</option>
            <option value="8">August</option>
            <option value="9">Septemer</option>
            <option value="10">October</option>
            <option value="11">November</option>
            <option value="12">December</option>
          </select>
        </div>
      </div>

      <Button>Find Events</Button>
    </form>
  );
}

export default EventSearch;

 

82. Catch-All ํŽ˜์ด์ง€ ์ƒ์—์„œ ๋ฐ์ดํ„ฐ ์ถ”์ถœํ•˜๊ธฐ 

uesRouter ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ, router.query.slug ์— ์—‘์„ธ์Šค ํ•˜์—ฌ ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

export default function FilteredEventsPage() {
  const router = useRouter();
  const filterData = router.query.slug;

  if (!filterData) {
    return <p className="center">Loading...</p>;
  }

  const filteredYear = filterData[0];
  const filteredMonth = filterData[1];

  const numYear = +filteredYear;
  const numMonth = +filteredMonth;

  if (
    isNaN(numYear) ||
    isNaN(numMonth) ||
    numYear > 2030 ||
    numYear < 2021 ||
    numMonth < 1 ||
    numMonth > 12
  ) {
    return <p>Invalid filter. Please adjust your values! </p>;
  }

  const filteredEvents = getFilteredEvents({
    year: numYear,
    month: numMonth,
  });

  if (!filteredEvents || filteredEvents.length === 0) {
    return <p>No events found for the chosen filter! </p>;
  }

  return (
    <div>
      <h1>Filtered Events</h1>
    </div>
  );
}

 

83. ์ตœ์ข… ๋‹จ๊ณ„ 

์œ„์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์Šคํƒ€์ผ๋ง์  ์š”์†Œ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. 

 

  • Resluts Title Component
  • Error Alert Component

 

 

84. ๋ชจ๋“ˆ ์š”์•ฝ

Next.js ์—์„œ ๋ผ์šฐํŒ…์ด ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€, ์—ฌ๋Ÿฌ ๋ผ์šฐํŠธ๋ฅผ ํ†ตํ•ด ์„œ๋กœ ๋‹ค๋ฅธ ํŽ˜์ด์ง€ ํŒŒ์ผ์ด ์–ด๋–ป๊ฒŒ ์ฝํžˆ๋Š”์ง€, ์Šฌ๋Ÿฌ๊ทธ ๋ผ์šฐํŠธ์™€ ๋‹จ์ผ ๋™์  ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ผ์šฐํŠธ์˜ ์ž‘๋™ ๋ฐฉ์‹์— ๋Œ€ํ•ด ๋ฐฐ์› ์Šต๋‹ˆ๋‹ค. 

 

๋ผ์šฐํ„ฐ ์•ก์„ธ์Šค๋ฅผ ์œ„ํ•ด useRouter hook ์‚ฌ์šฉ ๋ฐฉ๋ฒ• ๋˜ํ•œ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ๋ผ์šฐํ„ฐ๋ฅผ ํ†ตํ•ด ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜ URL์— ๋ถ€ํ˜ธํ™”๋œ ๋ฐ์ดํ„ฐ์— ์•ก์„ธ์Šค ํ•  ์ˆ˜๋„ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. 

 

๋ฐ˜์‘ํ˜•