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;