React

React Hooks 적용, Custom Hook과 React.lazy()와 Suspense를 이용하기

mellomello.made 2022. 7. 30. 01:24

React Hooks 

 

1. react.lazy(), suspense 를 사용해서 동적으로 리소스 받아오기

react.lazy()와 suspense 는 같이 사용해야 한다. (앱의 볼륨이 작으면 속도감이 안 올 수 있다, 대형 프로젝트에서는 빠른 속도 체감할 수 있다.)

동적 import를 사용해서 받아오고, 받아온 것들을 suspens 컴포넌트 안에서 실행시킨다. 

 

App.js

/* react 메소드 사용할 때 import React, { Suspense } from "react" 불러오기*/
import React, { Suspense } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import useFetch from "./util/useFetch";

/* react.lazy()와 suspense를 사용해 보세요. */

const Home = React.lazy(() => import("./Home"));
const Navbar = React.lazy(() => import("./component/Navbar"));
const CreateBlog = React.lazy(() => import("./blogComponent/CreateBlog"));
const BlogDetails = React.lazy(() => import("./blogComponent/BlogDetail"));
const NotFound = React.lazy(() => import("./component/NotFound"));
const Loading = React.lazy(() => import("./component/Loading"));
const Footer = React.lazy(() => import("./component/Footer"));


function App() {
  const {
    error,
    isPending,
    data: blogs,
  } = useFetch("http://localhost:3001/blogs");


return (
    <BrowserRouter>
      {error && <div>{error}</div>}
      {/*Suspense fallback에 로딩 컴포넌트 만들어서 넣어주기*/}
      <Suspense fallback={<Loading />}>
        <div className="app">
          <Navbar />
          <div className="content">
            <Routes>
              <Route
                exact
                path="/"
                element={<Home blogs={blogs} isPending={isPending} />}
              />
              <Route path="/create" element={<CreateBlog />} />
              <Route path="/blogs/:id" element={<BlogDetails blog={blogs} />} />
              <Route path="/blogs/:id" element={<NotFound />} />
            </Routes>
          </div>
          <Footer />
        </div>
      </Suspense>
    </BrowserRouter>
  );
}

export default App;

root component App이 동적 import를 받아오고 컴포넌트를 할 때 마다 추적해서 다운 받는다.

App 컴포넌트 밖에서(최상단) 불렀을 때 suspens가 fallback 안에서 실행 시켜준다. 

 


 

2. id를 이용하여 개별 블로그의 내용이 보일 수 있게하기.

react-router hook: useParams

=> 현재 url에서 동적 매개 변수 key/value 값 객체를 반환하는 hook이다.
BlogDetails.js

const { id } = useParams();
 
구조분해할당을 사용해서 동적으로 변하는 id의 id 가져온다.
 

 

3. useNavigate()를 이용하여 delete 버튼을 누르면 다시 home으로 리다이렉트 하기

BlogDetails.js

const handleClick = () => {
    /* delete 버튼을 누르면 다시 home으로 리다이렉트 되어야 합니다. */
    /* useNavigate()를 이용하여 로직을 작성해주세요. */

    fetch("http://localhost:3001/blogs/" + blog.id, {
      method: "DELETE",
    }).then(() => {
      navigate("/");
    });
  };

 


 

4. isLike와 blog.likes를 이용하여 하트를 누르면 home에서 새로고침을 했을 때 숫자가 올라가게 하기

BlogDetails.js

const handleLikeClick = () => {
    /* 하트를 누르면 home에서 새로고침을 했을 때 숫자가 올라가야 합니다. */
    /* isLike와 blog.likes를 이용하여 handleLikeClick의 로직을 작성해주세요. */

    setIsLike(!isLike);
    let result = blog.Likes;

    if (isLike === false) {
    //하트가 0일때는 마이너스가 나올 수 없기 때문에 -조건은 blog.likes가 0보다 클때로 지정한다.
      if (blog.likes > 0) {
        result = blog.likes - 1;
      }
      result = blog.likes;
    } else {
      result = blog.likes + 1;
    }

  //기존에 있던 값도 같이 보내줘야한다. 
    let putData = {
      id: blog.id,
      title: blog.title,
      body: blog.body,
      author: blog.author,
      //바꾸고자 하는 부분에 result 넣기
      likes: result,
    };
    
    fetch(`http://localhost:3001/blogs/` + blog.id, {
    //put 
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(putData),
    }).then(() => {
      navigate(`/blog/${id}`);
    });
    
    
  return (
    <div className="blog-details">
      {isPending && <div>Loading...</div>}
      {error && <div>{error}</div>}
      {blog && (
        <article>
          <h2>{blog.title}</h2>
          <p>Written by {blog.author}</p>
          <div>{blog.body}</div>
          <button onClick={handleLikeClick}>
            {/* isLike에 의해 조건부 렌더링으로 빨간 하트(❤️)와 하얀 하트(🤍)가 번갈아 보여야 합니다. */}
            {isLike === false ? "❤️" : "🤍"}
          </button>

          <button onClick={handleClick}>delete</button>
        </article>
      )}
    </div>
  );
};

export default BlogDetails;

 

전체코드

BlogDetails.js

import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import useFetch from "../util/useFetch";

const BlogDetails = ({ blogs }) => {
  const { id } = useParams();
  const {
    data: blog,
    error,
    isPending,
  } = useFetch("http://localhost:3001/blogs" + id);
  const [isLike, setIsLike] = useState(true);
  const navigate = useNavigate();

  /* 현재는 개별 블로그 내용으로 진입해도 내용이 보이지 않습니다. */
  /* id를 이용하여 개별 블로그의 내용이 보일 수 있게 해봅시다.
  useParams => 현재 url에서 동적 매개 변수 key/value 값 객체를반환
  */

  const handleClick = () => {
    /* delete 버튼을 누르면 다시 home으로 리다이렉트 되어야 합니다. */
    /* useNavigate()를 이용하여 로직을 작성해주세요. */

    fetch("http://localhost:3001/blogs/" + blog.id, {
      method: "DELETE",
    }).then(() => {
      navigate("/");
    });
  };

  const handleLikeClick = () => {
    /* 하트를 누르면 home에서 새로고침을 했을 때 숫자가 올라가야 합니다. */
    /* isLike와 blog.likes를 이용하여 handleLikeClick의 로직을 작성해주세요. */

    setIsLike(!isLike);
    let result = blog.Likes;

    if (isLike === false) {
      if (blog.likes > 0) {
        result = blog.likes - 1;
      }
      result = blog.likes;
    } else {
      result = blog.likes + 1;
    }

    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(`/blog/${id}`);
    });
  };

  return (
    <div className="blog-details">
      {isPending && <div>Loading...</div>}
      {error && <div>{error}</div>}
      {blog && (
        <article>
          <h2>{blog.title}</h2>
          <p>Written by {blog.author}</p>
          <div>{blog.body}</div>
          <button onClick={handleLikeClick}>
            {/* isLike에 의해 조건부 렌더링으로 빨간 하트(❤️)와 하얀 하트(🤍)가 번갈아 보여야 합니다. */}
            {isLike === false ? "❤️" : "🤍"}
          </button>

          <button onClick={handleClick}>delete</button>
        </article>
      )}
    </div>
  );
};

export default BlogDetails;

 

5. 등록 버튼을 누르면 게시물이 등록하고, home으로 리다이렉트하기.

CreateBlog.js

import { useState } from "react";
import { useNavigate } from "react-router-dom";

const CreateBlog = () => {
  const [title, setTitle] = useState("");
  const [body, setBody] = useState("");
  const [author, setAuthor] = useState("김코딩");
  const navigate = useNavigate();

const handleSubmit = (e) => {
    e.preventDefault();
    
    /*라이브러리 기능으로 id는 자동으로 들어가기 때문에 안 넣어도 됨, likes: 0 으로 정해줘야함*/
    const blog = { title, body, author, likes: 0 };

    fetch("http://localhost:3000/blogs/", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(blog),
    }).then(() => {
      navigate("/");
    });
  };
  
  return (
    <div className="create">
      <h2>Add a New Blog</h2>
      <form onSubmit={handleSubmit}>
        <label>제목</label>
        <input
          type="text"
          required
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="제목을 입력해주세요."
        />
        <label>내용</label>
        <textarea
          required
          value={body}
          onChange={(e) => setBody(e.target.value)}
          placeholder="내용을 입력해주세요."
        ></textarea>
        <label>작성자</label>
        <select value={author} onChange={(e) => setAuthor(e.target.value)}>
          <option value="kimcoding">김코딩</option>
          <option value="parkhacker">박해커</option>
        </select>
        <button>등록</button>
      </form>
    </div>
  );
};

export default CreateBlog;

 

1. 어떤 데이터를 보낼 것인지 request body를 통해서 정해야한다.

 
2. JSON 파일안에 넣어줘야 하기 때문에 파일을 컨버팅 해줘야할 필요가 있다.
=>  JSON.stringify(blog) 메서드를 사용해 바꿔준다. 
 
3. .then()에서 리다이렉트하여 홈으로 갈 때,  useNavigate() 훅을 사용한다.

4. const navigate = useNavigate(); 넣어주고

5. .then()안에  navigate("/") 넣어준다.


6. useState를 이용하여 data, isPending, error를 정의하기.

 
useFetch.js

import { useState, useEffect } from "react";

const useFetch = (url) => {
  /* useState를 이용하여 data, isPending, error를 정의하세요. */
  const [data, setData] = useState(null);
  const [isPending, setIsPending] = useState(true);
  const [error, setError] = useState(null);

  /* useFetch 안의 중심 로직을 작성해주세요. */
  useEffect(() => {
    const abortCont = new AbortController();

    setTimeout(() => {
      fetch(url, { signal: abortCont.signal })
        .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) => {
          if (err.name === "AbortError") {
            console.log("fetch aborted");
          } else {
            setIsPending(false);
            setError(err.message);
          }
        });
    }, 1000);
//http://localhost:3001/blogs" + id); url이 계속 바뀌는 것을 따라가야할 필요성이있다.
//url에 따라 실행시켜 리렌더링이 된다. 

    return () => abortCont.abort();
  }, [url]);

  return { data, isPending, error };
};

export default useFetch;