[TIL] Day66 #React Hooks #Naver Movie API

Beanxxยท2022๋…„ 7์›” 29์ผ
0

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
66/120
post-thumbnail

2022.07.29(Fri)

[TIL] Day66
[SEB FE] Day67

โ˜‘๏ธย [pair] fe-sprint-react-hooks

๐Ÿ“Žย json-server ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ

: ์ง์ ‘ DB & ์„œ๋ฒ„ ๊ตฌ์ถ•ํ•  ํ•„์š”์—†์ด JSON ํŒŒ์ผ์„ ์ด์šฉํ•˜์—ฌ REST API ์„œ๋ฒ„๋ฅผ ๊ตฌ์ถ•ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
โœ‹ย ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๊ณต๋ถ€ํ•˜๋‹ค๊ฐ€ ์„œ๋ฒ„๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด ์‚ฌ์šฉํ•˜๊ธฐ!! (์‹ค์ œ ํ”„๋กœ์ ํŠธ์‹œ์—” ์‚ฌ์šฉ ์ง€์–‘)

# json-server ์ „์—ญ ์„ค์น˜
$ npm i -g json-server

# json-server๋ฅผ localhost:3001์œผ๋กœ ์—ด๊ธฐ
$ cd data
$ json-server --watch data.json --port 3001
  • App.js
    • react.lazy() & suspense ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ Refactoring โœจ

      const CreateBlog = lazy(() => import("./blogComponent/CreateBlog"));
      ...
      
      return (
      	<BrowserRouter>
      		<Suspense fallback={<Loading />} />
      		<Routes>
      			<Route path="/create" element={<CreateBlog />} />
      			...
      		</Routes>
      	</BrowserRouter>
      )
  • BlogDetail.js
    • useParams์„ ์ด์šฉํ•˜์—ฌ ๊ฐœ๋ณ„ id๋ฅผ ๋ฐ›์•„์™€ ๊ฐœ๋ณ„ ๋ธ”๋กœ๊ทธ์˜ ๋‚ด์šฉ์ด ๋ณด์ผ ์ˆ˜ ์žˆ๋„๋ก โœจ

      import { useNavigate, useParams } from "react-router-dom";
      
      const { id } = useParams(); // params์˜ id๋ฅผ ๊ตฌ์กฐ๋ถ„ํ•ดํ• ๋‹น์œผ๋กœ ๊ฐ€์ ธ์˜ด!
      
      // 'useFetch' Custom Hook ์‚ฌ์šฉ
      const {
          data: blog, // data๋ฅผ blog ๋ณ€์ˆ˜๋กœ ๋ฐ”๊ฟ”์„œ ์‚ฌ์šฉ
          isPending,
          error,
        } = useFetch(`http://localhost:3001/blogs/${id}`);
      
      const navigate = useNavigate(); // url๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์†Œ๋“œ
      
      // ๊ธ€ ์‚ญ์ œ event handler
      const handleDeleteClick = () => {
          fetch(`http://localhost:3001/blogs/${blog.id}`, {
            method: "DELETE",
          })
            .then(() => {
              navigate("/"); // home์œผ๋กœ redirect
            })
            .catch((error) => console.error("Error", error));
        };
      
      // ํ•˜ํŠธ ํด๋ฆญ event handler
      const handleLikeClick = () => {
          setIsLike(!isLike); // ํ•˜ํŠธ ๋ˆ„๋ฆ„์˜ true/false ์ƒํƒœ ๋ณ€๊ฒฝ
          let result = blog.likes;
      
      		/* ํ•˜ํŠธ๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ˆซ์ž +1 */
          if (isLike === true) {
            result = blog.likes + 1;
          } else {
            if (blog.likes > 0) {
              result = blog.likes - 1;
            }
            result = blog.likes;
          }
      
      		// ์ˆ˜์ •ํ•  ๋ฐ์ดํ„ฐ ์†์„ฑ
          let putData = {
            id: blog.id,
            title: blog.title,
            body: blog.body,
            author: blog.author,
            likes: result,
          };
      
          fetch(`http://localhost:3001/blogs/${blog.id}`, {
            method: "PUT", // ์ˆ˜์ •
            headers: { "Content-type": "application/json" },
            body: JSON.stringify(putData),
          })
            .then(() => {
              navigate(`/blogs/${blog.id}`);
            })
            .catch((error) => console.error("Error", error));
        };
  • CreateBlog.js
    • ๋“ฑ๋ก ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๊ฒŒ์‹œ๋ฌผ์ด ๋“ฑ๋ก์ด ๋˜๋ฉฐ home์œผ๋กœ Redirect๋˜๋„๋ก โœจ

    • fetch & useNavigate๋ฅผ ์ด์šฉํ•˜์—ฌ handleSubmit event ์™„์„ฑํ•˜๊ธฐ โœจ

      const [title, setTitle] = useState("");
      const [body, setBody] = useState("");
      const [author, setAuthor] = useState("๊น€์ฝ”๋”ฉ");
      
      const handleSubmit = (e) => {
          e.preventDefault();
      
          const blog = { title, body, author, likes: 0 };
      
          fetch("http://localhost:3001/blogs/", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify(blog),
          })
            .then(() => {
              navigate("/");
            });
        };
  • UseFetch.js
    • GET ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š” useEffect hook๋ฅผ custom hook์œผ๋กœ ๋งŒ๋“ค์–ด ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก โœจ

      import { useState, useEffect } from "react";
      
      const useFetch = (url) => {
        const [data, setData] = useState(null);
        const [isPending, setIsPending] = useState(true);
        const [error, setError] = useState(null);
      
        useEffect(() => {
          setTimeout(() => {
            fetch(url)
              .then((res) => {
                if (!res.ok) {
                  throw Error("could not fetch the data for that resource");
                }
                return res.json();
              })
              .then((data) => {
                setIsPending(false);
                setData(data);
                setError(null);
              })
              .catch((err) => {
                setIsPending(false);
                setError(err.message);
              });
          }, 1000);
        }, [url]); // url์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ๋ฆฌ๋ Œ๋”๋ง
      
        return { data, isPending, error }; // ๊ฐ์ฒด๋กœ ์ƒํƒœ ๊ฐ’ ๋ฐ˜ํ™˜
      };
      
      export default useFetch;

handleSubmit event handler์˜ body ๋‚ด์˜ ๋ฐ์ดํ„ฐ ์†์„ฑ์— id๋ฅผ ๋„ฃ์–ด์„œ ๊ณ„์† title, body, author ๊ฐ’์ด ์ƒ์„ฑ์ด ์•ˆ ๋œ๊ฑฐ์˜€๋‹ค.. id๋Š” ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์•ˆ ๋„ฃ์–ด๋„ ๋˜๊ณ , id๋ฅผ ๋นผ์ฃผ๋‹ˆ๊นŒ ๊ฐ’์ด ์ž˜ ๋‚˜์™”๋‹ค! ์ด๊ฑธ๋กœ 1์‹œ๊ฐ„ ๋„˜๊ฒŒ ์‚ฝ์งˆํ•จ,,,๐Ÿซ  ๊ทธ๋ฆฌ๊ณ  headers๋„ ๋„ฃ์–ด์ฃผ๋Š”๊ฑฐ ์žŠ์ง€ ๋ง๊ธฐ!!



โ˜‘๏ธย ์ข…ํ•ฉ Quiz - React Hooks

๐Ÿคทโ€โ™€๏ธย Q: React๊ฐ€ Class ์ปดํฌ๋„ŒํŠธ โ†’ Function ์ปดํฌ๋„ŒํŠธ๋กœ ์ ์ง„์ ์œผ๋กœ ๋„˜์–ด๊ฐ€๊ฒŒ ๋œ ์ด์œ ๋Š” ์ƒํƒœ๊ฐ’์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋“ค์ด ๋ฏธ์ง„ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค?

๐Ÿ™…โ€โ™€๏ธย A: NO
ใ„ดwhy? ์ƒํƒœ๊ฐ’์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ๋“ค์ด ๋ฏธ์ง„ํ•œ ๊ฒƒ์€ ์ดˆ๊ธฐ์˜ ํ•จ์ˆ˜ ์ปดํฌ๋„ŒํŠธ! ํด๋ž˜์Šค ์ปดํฌ๋„ŒํŠธ๋Š” componentDidMount(), componentDidUpdate() โ€ฆ ๋ Œ๋”๋ง ์‹œ์ ์— ๋”ฐ๋ผ ์–ด๋–ป๊ฒŒ ๋™์ž‘์‹œํ‚ฌ์ง€ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์†Œ๋“œ ์กด์žฌ!


๐Ÿคทโ€โ™‚๏ธย Q: useMemo๋ฅผ const result = useMemo(() => calculate(value), [value]); ์™€ ๊ฐ™์€ ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•  ๋•Œ, ๋ฐฐ์—ด ์•ˆ์˜ value๋Š” ์˜ต์…˜์ด๋‹ค?

๐Ÿ™…โ€โ™‚๏ธย A: NO
ใ„ดwhy? useMemo์˜ 1๏ธโƒฃ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜-์–ด๋–ป๊ฒŒ ์—ฐ์‚ฐํ• ์ง€ ์ •์˜ํ•˜๋Š” ํ•จ์ˆ˜, 2๏ธโƒฃ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜-deps ๋ฐฐ์—ด
โ‡’ ๋ฐฐ์—ด ์•ˆ์— ๋„ฃ์€ ๋‚ด์šฉ์ด ๋ณ€๊ฒฝ๋˜๋ฉด 1๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๊ฐ’์„ ์—ฐ์‚ฐ
๐Ÿ‘‰ย 2๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ๋ฐฐ์—ด์€ ๊ฐ’ ๋ณ€๊ฒฝ์˜ ์œ ๋ฌด๋ฅผ ๊ฐ์ง€ํ•˜๋Š” ๋ถ€๋ถ„์œผ๋กœ ๋ฐ˜๋“œ์‹œ! ์ธ์‹ํ•ด์•ผ ํ•  ๊ฐ’์„ ๋„ฃ์–ด์ค˜์•ผ ํ•จ.



โ˜‘๏ธย Naver Movie API ์‚ฌ์šฉํ•˜๊ธฐ

7.27(์ˆ˜) ์ €๋…์— React ํ”„๋กœ์ ํŠธ ์•ˆ์—์„œ ๋„ค์ด๋ฒ„ ์˜ํ™” API๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๋Š” ์—ฐ์Šต์„ ํ–ˆ๋‹ค. ๋ฏธ๋ฃจ๋‹ค๊ฐ€ ์ด์ œ์•ผ ๊ธฐ๋กํ•˜๋Š” ๋‚˜์˜ API ์‚ฌ์šฉ๊ธฐ ูฉ( แ› )ูˆ ์ฒ˜์Œ 1์‹œ๊ฐ„์ •๋„๋Š” API๋ฅผ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ• ์ง€ ๋ชฐ๋ผ์„œ ์šฐ์™•์ขŒ์™• ์ฝ”๋“œ๋ฅผ ์งœ์ง€๋„ ๋ชปํ•˜๊ฒ ์–ด์„œ ๊ตฌ๊ธ€๋งํ•ด์„œ ์‚ฌ์šฉ๋ฒ• ์ข€ ์ฐพ์•„๋ณด๋‹ค๊ฐ€ ์ด๊ฒƒ์ €๊ฒƒ ์ ์šฉํ•ด๋ณด๊ณ  2์‹œ๊ฐ„ ๋„˜๊ฒŒ ๋„์ ์ด๋‹ค๊ฐ€ ๋ฐ์ดํ„ฐ ๋ฐ›์•„์˜ค๋Š” ๊ฒƒ๊นŒ์ง€ ์„ฑ๊ณต!โœจ
์ถ”ํ›„์— ์‹œ๊ฐ„ ์žˆ์„ ๋•Œ ํ™”๋ฉด ๊ฒ€์ƒ‰์‹œ ์˜ํ™” ํฌ์Šคํ„ฐ๋ž‘ ์˜ํ™” ์ •๋ณด๊ฐ€ ์ถœ๋ ฅ๋˜๋„๋ก ์ถ”๊ฐ€ ๊ตฌํ˜„ํ•ด๋ณด๊ธฐ ๐Ÿ™Œ

  1. NAVER Developers์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋“ฑ๋ก(API ์ด์šฉ์‹ ์ฒญ)ํ•˜๊ธฐ

    • ๋“ฑ๋ก์„ ๋งˆ์น˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ •๋ณด์—์„œ Client ID์™€ Client Secret ํ™•์ธํ•˜๊ธฐ!
  2. axios๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ API๋ฅผ GET ์š”์ฒญํ•˜๊ธฐ

    import React, { useEffect, useState } from "react";
    import axios from "axios";
    
    function Home() {
      const [data, setData] = useState([]);
      const clientID = "my unique clientID";
      const clientSecret = "my unique clientSecret";
    
      const apiData = async () => {
        await axios
          .get("/v1/search/movie.json", {
            params: {
              query: "์งฑ๊ตฌ", // ๊ฒ€์ƒ‰ํ•˜๊ณ ์ž ํ•˜๋Š” ํ‚ค์›Œ๋“œ
              display: 10, // ๋ช‡ ๊ฐœ์˜ data๋ฅผ ์ถœ๋ ฅํ• ๊ฑด์ง€
            },
            headers: {
              "X-Naver-Client-Id": clientID,
              "X-Naver-Client-Secret": clientSecret,
            },
          })
          .then((res) => {
            console.log(res.data.items);
            setData(res.data.items);
          })
          .catch((error) => {
            console.log(error);
          });
      };
    
      useEffect(() => {
        apiData();
      }, []);
    
      return (
        <>
          <h1>์˜ํ™” ๊ฒ€์ƒ‰</h1>
        </>
      );
    }
    
    export default Home;

    โœ‹ย ์—ฌ๊ธฐ๊นŒ์ง€ ๊ณผ์ •์„ ๋งˆ์น˜๊ณ  ๋‚˜๋‹ˆ๊นŒ CORS ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. console์˜ error๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด url์ด http://localhost:3000/v1/search/movie.json ์š”๊ธฐ๋กœ GET ์š”์ฒญ์ด ๊ฐ„ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋”ฐ๋กœ proxy ์„ค์ •์„ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

  3. Proxy ์„ค์ •ํ•ด์ฃผ๊ธฐ
    a. package.json์—์„œ proxy ์„ค์ •

    // package.json
     "proxy": "https://openapi.naver.com"

    ์ฒ˜์Œ์— ์ด ๋ฐฉ๋ฒ•์„ ์ ์šฉํ•ด๋ดค๋Š”๋ฐ ์—ฌ์ „ํžˆ ์—๋Ÿฌ๊ฐ€ ํ•ด๊ฒฐ๋˜์ง€ ์•Š์•˜์Œ..ใ…œ
    ๊ทธ๋ž˜์„œ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ• ์‹œ๋„..!

    b. http-proxy-middleware ๋ชจ๋“ˆ ์„ค์น˜ํ•˜๊ณ  setupProxy.js ํŒŒ์ผ ์ƒ์„ฑ

    # ๋จผ์ € ๋ชจ๋“ˆ์„ ์„ค์น˜ํ•œ๋‹ค
     $ npm install http-proxy-middleware
    // setupProxy.js
     const { createProxyMiddleware } = require("http-proxy-middleware");
     
     module.exports = function (app) {
       app.use(
         createProxyMiddleware("/api", {
           target: "https://openapi.naver.com",
           changeOrigin: true,
         })
       );
     };

    ์ด๋ ‡๊ฒŒ ํ•˜๋‹ˆ๊นŒ CORS ์—๋Ÿฌ๊ฐ€ ์‚ฌ๋ผ์ง€๊ณ  ๋ฐ์ดํ„ฐ๊ฐ€ ์ถœ๋ ฅ๋จ!!!

    ๐Ÿ‘ปย ๋‚˜์ค‘์— ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•ด์„œ ์›น ์‚ฌ์ดํŠธ์— ์ถœ๋ ฅํ•˜๊ณ  ๊ฒ€์ƒ‰๋˜๋„๋ก ์ถ”๊ฐ€ ๊ตฌํ˜„ํ•˜๊ธฐ

profile
FE developer

0๊ฐœ์˜ ๋Œ“๊ธ€