๐Ÿ’ฃ Next.js_02 Dynamic Routes, next/link

29255 ๋‹จ์–ด next.jsnext.js

Next.js

์ฝ”๋”ฉ์•™๋งˆ Next.js ๊ฐ•์˜๋ฅผ ๋ณด๊ณ  ์ •๋ฆฌํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

๐Ÿ”ช axios

axios

  • API ํ˜ธ์ถœ์„ ํ•˜๊ธฐ ์œ„ํ•ด axios ์„ค์น˜
$ npm i axios
import Axios from "axios";
import Head from "next/head";
import { useEffect } from "react";

export default function Home() {
  const API_URL =
    "http://makeup-api.herokuapp.com/api/v1/products.json?brand=maybelline";

  // ์ตœ์ดˆ์— ์ง„์ž…ํ–ˆ์„ ๋•Œ ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ์„ ์œ„ํ•œ ํ•จ์ˆ˜
  function getData() {
    Axios.get(API_URL).then((res) => {
      // API ํ˜ธ์ถœ์ด ์ž˜ ๋˜๋Š”์ง€ ํ™•์ธ
      console.log(res);
    });
  }

  // ์ตœ์ดˆ์— ์ง„์ž…ํ–ˆ์„ ๋•Œ ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœํ•˜๋ฉด ๋˜๋ฏ€๋กœ useEffect ์‚ฌ์šฉ
  useEffect(() => {
    getData();
  }, []);

  return (
    <div>
      <Head>
        <title>Home | ๋ณด๋ฆฌ๊ตฌ๋ฆฌ</title>
      </Head>
    </div>
  );
}
  • API ํ˜ธ์ถœ ํ™•์ธ

๐Ÿ”ช useEffect

useEffect๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋งˆ์šดํŠธ/์–ธ๋งˆ์šดํŠธ/์—…๋ฐ์ดํŠธ์‹œ ํ•  ์ž‘์—… ์„ค์ •ํ•˜๊ธฐ

  • useEffect๋ฅผ ์ด์šฉํ•˜์—ฌ

    • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ๋์„ ๋•Œ(์ฒ˜์Œ ๋‚˜ํƒ€๋‚ฌ์„ ๋•Œ)
    • ์–ธ๋งˆ์šดํŠธ ๋์„ ๋•Œ(์‚ฌ๋ผ์งˆ ๋•Œ)
    • ์—…๋ฐ์ดํŠธ ๋˜์—ˆ์„ ๋•Œ(ํŠน์ • props๊ฐ€ ๋ฐ”๋€” ๋•Œ)

    ํŠน์ • ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

import React, { useEffect } from 'react';

function User({ user }) {
  useEffect(() => {
    console.log('์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚จ');
    return () => {
      console.log('์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์—์„œ ์‚ฌ๋ผ์ง');
    };
  }, []);
  • useEffect ์ฒซ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ : ํ•จ์ˆ˜
  • useEffect ๋‘ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ : ์˜์กด๊ฐ’์ด ๋“ค์–ด์žˆ๋Š” ๋ฐฐ์—ด (deps)

๋งˆ์šดํŠธ ์‹œ ํ•˜๋Š” ์ž‘์—…๋“ค

  • props๋กœ ๋ฐ›์€ ๊ฐ’์„ ์ปดํฌ๋„ŒํŠธ์˜ ๋กœ์ปฌ ์ƒํƒœ๋กœ ์„ค์ •
  • ์™ธ๋ถ€ API ์š”์ฒญ (REST API ๋“ฑ)
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ (D3, Video.js ๋“ฑ)
  • setInterval์„ ํ†ตํ•œ ๋ฐ˜๋ณต์ž‘์—… ๋˜๋Š” setTimeout์„ ํ†ตํ•œ ์ž‘์—… ์˜ˆ์•ฝ

์–ธ๋งˆ์šดํŠธ ์‹œ ํ•˜๋Š” ์ž‘์—…

  • setInterval, setTimeout์„ ์‚ฌ์šฉํ•˜์—ฌ ๋“ฑ๋กํ•œ ์ž‘์—…๋“ค clearํ•˜๊ธฐ (clearInterval, clearTimeout)
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ธ์Šคํ„ด์Šค ์ œ๊ฑฐ

deps

  • ๋นˆ ๋ฐฐ์—ด : ์ฒ˜์Œ์— ํ•œ ๋ฒˆ๋งŒ ํ•จ์ˆ˜ ํ˜ธ์ถœ

  • ํŠน์ • ๊ฐ’ ๋„ฃ๊ธฐ

    • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฒ˜์Œ ๋งˆ์šดํŠธ ๋  ๋•Œ ํ˜ธ์ถœ์ด ๋˜๊ณ , ์ง€์ •ํ•œ ๊ฐ’์ด ๋ฐ”๋€” ๋•Œ์—๋„ ํ˜ธ์ถœ
    • deps ์•ˆ์— ํŠน์ • ๊ฐ’์ด ์žˆ๋‹ค๋ฉด ์–ธ๋งˆ์šดํŠธ ์‹œ์—๋„ ํ˜ธ์ถœ, ๊ฐ’์ด ๋ฐ”๋€Œ๊ธฐ ์ง์ „์—๋„ ํ˜ธ์ถœ
    • useEffect ์•ˆ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ƒํƒœ๋‚˜, props๊ฐ€ ์žˆ๋‹ค๋ฉด useEffect์˜ deps์— ๋„ฃ์–ด์•ผํ•œ๋‹ค.(๊ทœ์น™)
  • ํŒŒ๋ผ๋ฏธํ„ฐ ์ƒ๋žต

    • deps ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ƒ๋žตํ•˜๋ฉด, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ

๐Ÿ”ช useState

useState ๋ฅผ ํ†ตํ•ด ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ”๋€Œ๋Š” ๊ฐ’ ๊ด€๋ฆฌํ•˜๊ธฐ

๋ฆฌ์•กํŠธ 16.8 ์ด์ „ ๋ฒ„์ „์—์„œ๋Š” ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์—†์—ˆ์œผ๋‚˜, ๋ฆฌ์•กํŠธ 16.8์—์„œ Hooks ๊ธฐ๋Šฅ์ด ๋„์ž…๋˜๋ฉด์„œ ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ์—์„œ๋„ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

์ด๋ฒคํŠธ ์„ค์ •

  • Counter์—์„œ ๋ฒ„ํŠผ์ด ํด๋ฆญ๋˜๋Š” ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ํŠน์ • ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๋„๋ก ์„ค์ •
import React from 'react';

function Counter() {
  const onIncrease = () => {
    console.log('+1');
  }
  const onDecrease = () => {
    console.log('-1');
  }
  
  return (
    <div>
      <h1>0</h1>
        <button onClick={onIncrease}>+1</button>
        <button onClick={onDecrease}>-1</button>
      </div>
  );
}

export default Counter;

useState

  • ๋™์ ์ธ ๊ฐ’ = ์ƒํƒœ(state)
  • useStateํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ปดํฌ๋„ŒํŠธ์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
import React, { useState } from 'react';
// ๋ฆฌ์•กํŠธ ํŒจํ‚ค์ง€์—์„œ useState ํ•จ์ˆ˜๋ฅผ ๋ถˆ๋Ÿฌ์˜ด

function Counter() {
  const [number, setNumber] = useState(0);
  // number : ํ˜„์žฌ ์ƒํƒœ(์ฒซ ๋ฒˆ์งธ ์›์†Œ)
  // setNumber : Setter ํ•จ์ˆ˜(๋‘ ๋ฒˆ์งธ ์›์†Œ)
  // Setter ํ•จ์ˆ˜ : ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋ฐ›์€ ๊ฐ’์„ ์ตœ์‹  ์ƒํƒœ๋กœ ์„ค์ •
  
  const onIncrease = () => {
    setNumber(number + 1);
  }
  const onDecrease = () => {
    setNumber(number - 1);
  }
  
  return (
    <div>
      <h1>{number}</h1>
        <button onClick={onIncrease}>+1</button>
        <button onClick={onDecrease}>-1</button>
      </div>
  );
}

export default Counter;

๐Ÿ”ช Dynamic Routes

  • ํŒŒ์ผ๋ช…์— ๋Œ€๊ด„ํ˜ธ[]๋กœ ๋ฌถ์€ js ํŒŒ์ผ์„ ์ƒ์„ฑ
    • /post/1, /post/abc๋“ฑ๊ณผ ๊ฐ™์€ url์— ์ ‘์†ํ–ˆ์„ ๋•Œ ๋ชจ๋‘ pages/post/[pid].js ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ๋‹ค.
    • [pid]๋ฅผ ํ†ตํ•ด์„œ ๊ฒฝ๋กœ๊ฐ€ 1์ธ์ง€ abc์ธ์ง€ ์–ป์–ด๋‚ธ ๋‹ค์Œ ์‚ฌ์šฉ
import { useRouter } from 'next/router'

const Post = () => {
  const router = useRouter()
  const { pid } = router.query

  return <p>Post: {pid}</p>
}

export default Post

๐Ÿ”ช next/link

  • <a>์™€ ์‚ฌ์šฉ๋ฒ• ๊ฐ™๋‹ค
import Link from 'next/link'

function Home() {
  return (
    <ul>
      <li>
        <Link href="/">
          <a>Home</a>
        </Link>
      </li>
      <li>
        <Link href="/about">
          <a>About Us</a>
        </Link>
      </li>
    </ul>
  )
}

export default Home
  • <a>๋ฅผ ๊ฐ์‹ธ๋Š” ์ปค์Šคํ…€ ์ปดํฌ๋„ŒํŠธ์ธ ๊ฒฝ์šฐ, <Link>์— passHref ์†์„ฑ์ด ํ•„์š”ํ•˜๋‹ค.
    => styled-components ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, passHref ์†์„ฑ์ด ์—†๋‹ค๋ฉด <a>๋Š” href์†์„ฑ์„ ๊ฐ€์งˆ ์ˆ˜ ์—†๊ฒŒ ๋œ๋‹ค.
    • Futurama ๋งŒ๋“ค ๋•Œ passHref์†์„ฑ์— ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์•„ <a>๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์Œ
      => ์ปค์Šคํ…€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , <a>์—๋Š” ํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์Šคํƒ€์ผ์„ ์ง€์ •ํ•˜์—ฌ ํ•ด๊ฒฐํ•จ
import Link from 'next/link'
import styled from 'styled-components'

function NavLink({ href, name }) {
  // passHref์†์„ฑ์„ Link์— ์ถ”๊ฐ€
  return (
    <Link href={href} passHref>
      <RedLink>{name}</RedLink>
    </Link>
  )
}

export default NavLink

// <a>๋ฅผ ๊ฐ์‹ธ๋Š” ์ปค์Šคํ…€ ์ปดํฌ๋„Œ์Šค๊ฐ€ ์ƒ์„ฑ
const RedLink = styled.a`
  color: red;
`

๐Ÿ”ช next/router

  • <Link> ๋Œ€์‹  next/router๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์ด์ง€๋ฅผ ๋งํฌ
  • next/router๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฝ๋กœ ์ด๋ฆ„์— ์—‘์„ธ์Šคํ•˜๊ณ  ๊ตฌ์„ฑ์š”์†Œ๋ฅผ ๋ Œ๋”๋ง
import { useRouter } from "next/router";
import { Menu } from "semantic-ui-react";

export default function Gnb() {
  const router = useRouter();
  let activeItem = "home";

  if (router.pathname === "/") {
    activeItem = "home";
  } else if (router.pathname === "/about") {
    activeItem = "about";
  }

  function golink(e, data) {
    if (data.name === "home") {
      router.push("/");
    } else if (data.name === "about") {
      router.push("/about");
    }
  }

  return (
    <div>
      <Menu inverted>
        <Menu.Item
          name="home"
          active={activeItem === "home"}
          onClick={golink}
        />
        <Menu.Item
          name="about"
          active={activeItem === "about"}
          onClick={golink}
        />
      </Menu>
    </div>
  );
}

next/link ๋˜๋Š” next/router๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ?

  • <a>, location.href
    => ํŽ˜์ด์ง€๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจ ๋˜๋ฉด์„œ ์ด๋™
    => ํŽ˜์ด์ง€๊ฐ€ ์ƒˆ๋กœ ๊ทธ๋ ค์ง„๋‹ค. = ์š”์ฒญ์ด ๋Š˜์–ด๋‚จ
    => SPA(Single Page Application)์˜ ์žฅ์ ์ด ์‚ฌ๋ผ์ง

  • next/link, next/router
    => ํŽ˜์ด์ง€๋Š” ๊ทธ๋Œ€๋กœ ์žˆ์œผ๋ฉด์„œ ์•ˆ์˜ ๋‚ด์šฉ๋“ค๋งŒ ๋ฐ”๋€๋‹ค.

์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ