ํ๋ก์ ํธ๋ฅผ ํ๋ฉด์ ๊ฐ์ฅ ํด๊ฒฐํ๊ธฐ์ด๋ ค์์ ์ ๋จน์๋ ๋ถ๋ถ์ด ์๋ค.
๋ฐ๋ก App.js์ useEffect ๋น๋๊ธฐ์์ฒญ์ ์คํ์์ ๋ถ๋ถ์ด๋ค.
์์ํ๋ ์คํ์์์ ๋ฐ๋ผ ์คํ์ด๋์ง์์ ์ฌ์ฉ์ ์ธ๊ฐ๊ฐ ์ ๋ณด๊ฐ ํ์ํ ํ์ด์ง (ex ๋ง์ดํ์ด์ง)์์ ์๋ก๊ณ ์นจ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค์ง ๋ชปํ๋ ์ด์๊ฐ ์์๋ค.
ํ๋ก์ ํธ๊ฐ ๋๋ ๋ ๊น์ง๋ ํด๊ฒฐ์ด๋์ง ์์ ์์๋ฐฉํธ์ผ๋ก ํด๊ฒฐํด๋๊ณ , ์ดํ ๋ฆฌํฉํ ๋ง์ ํ๋ฉด์๋ ๊ทธ ์ด์์ ๋ํด ์ฌ๋ฌ๊ฐ์ง๋ฅผ ์ฐพ์๋ณด๊ณ ์ ์ฉํด๋ณด์์ง๋ง ์ฝ๊ฒ ํด๊ฒฐ์ด ๋์ง ์์๋ค.
ํ์ ์ด ๋๋๊ณ ์ฝ 1๋ฌ์ด ์ง๋๋ฌด๋ ต.. ๊ฐ์ด ๊ฐ๋ฐํ๋ ์ฐ๋ฆฌํ ์์ง๋ํ๊ณ ๋์ค์ฝ๋๋ก ๊ทผํฉ์ฃผ๊ณ ๋ฐ๋ค๊ฐ ๊ทธ ์ด์์ ๋ํด ์๊ธฐํ๊ฒ๋์๊ณ ๊ทธ๋ ๊ทธ ๋ฌธ์ ๋ฅผ digginggํ๋ค๊ฐ ๋๋์ด...!!! ์๋ฒฝ 3์๊ฐ ๋์ด์ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋์๋ค. ๐ญ
์ ๋ง ... ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๊ณ useEffect hook์ ์กฐ๊ฑด๋ ์ค๋ณด๊ณ , App.js useEffect๋ฅผ ์์ ๋นผ๋ณด๊ธฐ๋ํ๊ณ , axios instance๋ฅผ ์ ์ฉํด๋ณด๊ธฐ๋ํ๋๋ฐ.. ์๋ฌด๊ฒ๋ ์ด ์ด์ ์ฝ๋๋ฅผ ํด๊ฒฐํด์ค ๊ฑด ์์๋ ์ฌํ ๊ณผ๊ฑฐ์ฌ ์๋ ... ๐ฅฒ
๋!๋!์ด! ์ด ์ด์ ๋ญ์น๊ฐ ํด๊ฒฐ๋์ด์ ๋๋ ๊ธฐ์๊ณ ๊ฐ๋ฐ์ ์ ์ ์ด ํ์คํผ ๋ ๋ณดํ์ง ๊ธฐ๋ถ์ผ๋ก ์ ๋ค์๋ค.
๋น์ ํ๋ก์ ํธ ๋ง๊ฐ์ ๋ฐํ๋ฌธ์์ ์ ์ด๋์๋ ์ด์์ฌํญ
๋ ๋ค๋ฅธ ์ฐ
์๋ก๊ณ ์นจ์ redux-persist์์ ๋ถ๋ฌ์ค๋ ์ก์ธ์คํ ํฐ์ด ํค๋์ ์ค์ ๋๋ ๊ฒ๋ณด๋ค, ์ธ๊ฐ์ฝ๋๊ฐ ํ์ํ ๋ค๋ฅธ ํ์ด์ง์ ์์ฒญ์ด ๋จผ์ ์คํ๋์ด ์์ฒญ์คํ์ด๋ ๋ ํ ํฐ์ด ์์ด์ Unauthorized ๋ฌธ์ ๋ฐ์
App.js์์ ์๋ก๊ณ ์นจ์ refresh ์์ฒญ์ ๋ณด๋ด๊ณ ํ ํฐ๊ฐ๋ ์ ๋ฐ์์์ง๋๋ฐ ์คํ ์์์ ๋ฌธ์ ..
์ฐ์ ์ ์์๋ฐฉํธ์ผ๋ก ์ธ๊ฐ ์ ๋ณด๊ฐ ํ์ํ ํ์ด์ง์๋ ์ผ์ผ์ด ํค๋์ Authorization ๊ฐ์ ๋ฃ์ด์ค
๋ ์ข์ ๋ฐฉ๋ฒ์ด ์๋์ง ๊ณต๋ถ ํ์
๐ ๋ฌธ์ ์
์ํ๋ ์๋ ๋ฐฉ์
์๋ก๊ณ ์นจ์ App.js์์ ์๋ ๊ฐ์ง๊ณ ์๋ ํ ํฐ์ผ๋ก ์๋ฒ์ refresh ์์ฒญ์ ๋ณด๋ด์ new ์ก์ธ์คํ ํฐ์ ์ฌ๋ฐ๊ธ ๋ฐ๋ ๋น๋๊ธฐ ์์ฒญ์ด ๊ฐ์ฅ ๋จผ์ ์คํ์ด ๋๊ณ , ์๋ก ๋ฐ๊ธ๋ฐ์ ์ก์ธ์คํ ํฐ์ด ํค๋์ ์ค์ ๋๊ณ ๋์ ๊ทธ ํ ํฐ ์ ๋ณด๋ก ๋ง์ดํ์ด์ง์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ get์์ฒญ์ด ์คํ๋์ด์ผ ํ๋ค.
๋ฌธ์
๋ง์ดํ์ด์ง get์์ฒญ์ด ๊ฐ์ฅ ๋จผ์ ์คํ๋๊ณ , ๊ทธ ์ดํ App.js์ ์ฝ๋์ธ ํ ํฐ ์ ๋ณด๋ฅผ ์ค์ ํ๋ ๋ก์ง์ด ์ดํ์ ์คํ๋๋ค. ๋ฐ๋ผ์ ์ด๊ธฐ get ์์ฒญ์ ํค๋์ ํ ํฐ ์ ๋ณด๊ฐ ๋น ์ง ์ํ๋ก ์์ฒญ์ ๋ณด๋ด์ ์๋ต์ ์คํจํ๋ฏ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค์ง ๋ชปํ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
์์ธ
App.js, ๋ง์ดํ์ด์ง ๋ชจ๋ ์๋ฒ์ ํต์ ํ๋ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ํด useEffect ํ ์ผ๋ก ๊ด๋ฆฌํ๋๋ฐ, useEffect์ ์คํ์์๋ ์ต์๋จ ์ปดํฌ๋ํธ์ธ App.js๋ถํฐ ์คํ์ด ๋๋ ๊ฒ์ด์๋๊ณ ํ์์ ์๋ ์ปดํฌ๋ํธ์ useEffect๊ฐ ๋จผ์ ์คํ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋์ ํ์ ์ปดํฌ๋ํธ๊ฐ ๋ชจ๋ ๋ ๋๋ง ๋ ์ดํ์ App.js์ useEffect๊ฐ ์คํ๋๋ค. ์ด์ App.js์ useEffect ํจ์๊ฐ ๊ฐ์ฅ ๋ฆ๊ฒ ์คํ๋์ด ๋น๋๊ธฐ์์ฒญ์ด ์ํ๋ ์์๋๋ก ์๋ํ์ง ์์ ํ ํฐ์ด ๋จผ์ ์ค์ ๋์ง ์๋ ์์์์ ์ด์๊ฐ ๋ฐ์ํ๋ค.
ํด๊ฒฐ
Suspense Lazy ์ฌ์ฉํ์ฌ ํด๊ฒฐ
App.js ํ์ ์ปดํฌ๋ํธ๋ค์ Suspense๋ก ๊ฐ์ธ์ฃผ์ด ํ์ ์ปดํฌ๋ํธ์ ๋ ๋๋ง์ ๋ฏธ๋ฃจ๊ณ App.js๊ฐ ๋จผ์ ์คํ๋๊ฒ ํ๊ณ , App.js์ ๋น๋๊ธฐ ์์ฒญ์ด ์๋ฃ๋ ์์ ์ ํ์ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๊ฒ ํ๋ค.
Suspense Lazy ์ ์ฉ ์ด์
Suspense Lazy ์ ์ฉ ์ดํ
(์ง๊ธ ๋ณด๋ฉด App.js์ refresh ์์ฒญ์ ๊ณ์ ๋นจ๊ฐ์์ผ๋ก ์คํจํ๋๋ฐ, ๊ทธ ์ด์ ๋ ๋ก์ปฌํ๊ฒฝ์ด๋ผ์ ๊ทธ๋ ๋ค. ์๋ฒ๋จ์์ ๋ฆฌํ๋ ์ํ ํฐ์ ์ฟ ํค์ ๋ด์์ ๋ณด๋ด์ฃผ๋๋ฐ ๋ฐฐํฌ ์ดํ์ ๋ฐฐํฌ ์ฃผ์์ set cookie ์ ๋ณด๋ฅผ ๋ด๋ ๊ฒ์ผ๋ก ๋ณ๊ฒฝํ๊ธฐ๋๋ฌธ์, ๋ก์ปฌ ํ๊ฒฝ์์๋ ๋ก๊ทธ์ธ์ ๋ฆฌํ๋ ์ํ ํฐ์ด ์ฟ ํค์ ๋ด๊ธฐ์ง ์์์ ๋ฆฌํ๋ ์ ์์ฒญ์ ์๋ฌ๊ฐ ๋๋ ๊ฒ์ด๋ค.
์ฌ๊ธฐ์ ๋ฆฌํ๋ ์ ์์ฒญ์ ์ฑ๊ณตํ์ง ์๋๊ฒ์ด ํ์ฌ ํด๊ฒฐํ๋ ค๋ ๋ฌธ์ ์ ๊ณผ ์๊ด์ ์๊ธฐ๋๋ฌธ์ ๋ฌด์ํด๋ ์ข๋ค.)
์คํฌ๋ฆฐ์ท ํ๋ฉด์ ๋ก์ปฌํ๊ฒฝ์ด์ง๋ง, ๋ฐฐํฌํ๊ฒฝ๊ณผ ๋ง์ถ๊ธฐ์ํด useEffect๊ฐ 2๋ฒ ํธ์ถ๋๋๊ฑธ ๋ง๋ ์ฝ๋๋ก ๋ฐฐํฌํ๊ฒฝ์ฒ๋ผ useEffect hook์ด ํ๋ฒ๋ง ๋ ๋๋ง๋๋๋ก ์ค์ ํด๋์๋ค.
๊ทธ ์ด์ ๋ฅผ ์ ๊น ์ฒจ์ธํ์๋ฉด...
๋ฐฐํฌ ์ด์ ์ ๋ก์ปฌํ๊ฒฝ์์๋ง ๊ฐ๋ฐํ์๋๋ useEffect๊ฐ 2๋ฒ์คํ๋๋ฉด์ ๋ง์ดํ์ด์ง์ ๋ด๊ฐ ์ข์ํ ๋ ์ํผ ์ ๋ณด๋ฅผ ๋ฐ์์ค๋ get์์ฒญ์ด 1๋ฒ์งธ ์์ฒญ์ ์คํจ, 2๋ฒ์งธ ์์ฒญ์ ์ฑ๊ณตํ๋ค. ์ด์จ๋ get์์ฒญ์ด refresh์์ฒญ๋ณด๋ค ๋จผ์ ์คํ๋์์ง๋ง ํ๋ฉด์๋ ๋ฌธ์ ์์ด ์ ๋ฐ์ดํฐ๊ฐ ๋์์ ธ์ ์ด์๋ผ๊ณ ์ ํ ์๊ฐ์ง๋ ๋ชปํ์๋ค. ์ด๋ ๊ฒ ์๋์ฒ๋ผ ๋ฐ์ดํฐ๋ ์๋ฐ์์์ ๋์์ก์๋ค.
๋ฐฐํฌํ๊ฒฝ๊ณผ ๊ฐ๋ฐํ๊ฒฝ์ด ๋ค๋ฅธ ์ด์ ?
๊ฒฐ๋ก ์ ์ผ๋ก CRA๋ก ๊ตฌ์ถํ ๋ฆฌ์กํธ ์ฑ์ ๊ฒฝ์ฐ ๊ฐ๋ฐ๋ชจ๋์์๋ strict ๋ชจ๋๊ฐ ์ ์ฉ๋์ด ํจ์๊ฐ ๋๋ฒ ํธ์ถ๋๋ค.
๋ฆฌ์กํธ๋ฅผ npx create-react-app์ผ๋ก ๋ณด์ผ๋ฌํ๋ ์ดํธ๋ฅผ ๊น๊ณ ๊ฐ๋ฐํ๋ฉด ์๋์ผ๋ก strict๋ชจ๋๋ก ์ค์ ์ด ๋๋๋ฐ, strict ๋ชจ๋๋ ๊ฐ๋ฐํ๋ ๋์์๋ ํจ์๊ฐ ๋ ๋ฒ ํธ์ถ๋๋ค. ํญ์ ์ฝ์๋ก๊ทธ๋ฅผ ๋ณด๋ฉด ๋ ๋ฒ ํธ์ถ์ด ๋์ด์์๋๋ฐ ์ด strict ๋ชจ๋ ๋๋ฌธ์ด๋ค.
(๋ฐฐํฌ๋๊ณ ๋๋ฉด, ์ฆ ํ๋ก๋์ ๋ชจ๋์์ strict๋ชจ๋๋ ์ ์ ๋ก ์๋ํ์ง ์๋๋ค.)
strict ๋ชจ๋๋ ๋ง๊ทธ๋๋ก strictํ๊ฒ! ์๊ฒฉํ๊ฒ ์ปดํฌ๋ํธ๋ฅผ ๊ฒ์ฌํ๊ณ , ํจ์๋ฅผ ์๋์ ์ผ๋ก "์ด์ค์ผ๋ก ํธ์ถ"ํด์ ๋ฌธ์ ๊ฐ ๋ ๊ฒฝ์ฐ ๊ฒฝ๊ณ ๋ฉ์์ง๋ฅผ ๋์์ฃผ์ด ์ฐ๋ฆฌ ์ฑ์ ์ ์ฌ์ ์ธ ๋ฌธ์ ๋ฅผ ์๋ ค์ค๋ค.
์ฆ, ๋๋ฒ ์คํ์ด ๋์๋๋ฐ ๋ ๊ฐ์ ๊ฒฐ๊ณผ๊ฐ์ด ๋ค๋ฅด๋ค??????
๊ทธ๋ผ ๊ทธ ์ฝ๋๋ ๋ฌธ์ ๊ฐ ์๋ค๋ ๋ป....
๋ฆฌ์กํธ๊ฐ ์น์ ํ ๋ฌธ์ ๊ฐ ์๋ค๊ณ ์๋ ค์ค๊ฒ์ธ๋ฐ๋ ์ด๊ฑธ ๋ชจ๋ฅด๊ณ ๊ฐ๋ฐํ๋๋ผ ๊ณ ์ํ๋ค. ^^
ํ์ฌํผ ๊ฒฐ๋ก ์ ์ผ๋ก strict ๋ชจ๋๊ฐ ์ ์ฉ๋ ๋ก์ปฌ ํ๊ฒฝ์์๋ 2๊ฐ ์์ฒญ ์ค์ ํ๊ฐ๋ ์คํจ, ํ๊ฐ๋ ์ฑ๊ณตํด์ ๋ฐ์ดํฐ๊ฐ ๋์์ง๊ธดํ์ผ๋.. ๋ฐฐํฌํ๊ฒฝ์์ ๋๋ ค๋ณด๋, useEffect๊ฐ ํ ๋ฒ๋ง ์คํ๋์๊ณ ์์ get์์ฒญ์ด ์คํจํ๋ฉด์ ํ๋ฉด์ ๋ฐ์ดํฐ๊ฐ ์๋์์ง๋ ๋์ฐธ์ฌ๊ฐ ๋ฐ์ํ๋ค๋ ๊ฒ์ด๋ค.
์ด์ ์ฝ๋ ์ ์ฒด
//App.js
import React, { useState, useEffect } from "react";
import GlobalStyle from "./GlobalStyle";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import styled from "styled-components";
import Gnb from "./components/common/GNB/Gnb";
import SignUpForm from "./components/layout/RegisterForm/SignUpForm";
import LogIn from "./pages/LogIn/LogIn";
import NewRecipe from "./pages/NewRecipe/NewRecipe";
import OAuth2RedirectHandler from "./components/layout/RegisterForm/OAuth2RedirectHandler";
import EditRecipe from "./pages/EditRecipe/EditRecipe";
import Home from "./pages/Home/Home";
import RecipeDetail from "./pages/RecipeDetail/RecipeDetail";
import FridgeDigging from "./pages/FridgeDigging/FridgeDigging";
import MyFridge from "./pages/MyFridge/MyFridge";
import MyPage from "./pages/MyPage/MyPage";
import AdminPanel from "./components/layout/Admin/AdminPanel";
import axios from "axios";
import { setLoggedIn } from "./features/userSlice";
import Footer from "./components/layout/Footer/Footer";
import { useRef } from "react";
import CustomToast from "./components/common/CustomToast/CustomToast";
function App() {
const dispatch = useDispatch(); //for redux dispatch
// alert ๋์ ์ฌ์ฉ๋๋ toast ๊ด๋ จ ์ํ
const showToast = useSelector((state) => {
return state.toast.showToast;
});
//๋ก๊ทธ์ธ ์ํ ๊ฐ์ ธ์์ ๋ณ์์ ์ ์ฅ
const isLoggedIn = useSelector((state) => {
return state.user.isLoggedIn;
});
const userToken = useSelector((state) => {
return state.user.userToken;
});
//! Appjs์์ ์ก์ธ์คํ ํฐ ์ฌ๋ฐ๊ธ ์์ฒญํ๊ณ ํ ํฐ์ ๋ฐ๋ก ํค๋์ ์ค๋ ์๋ก๊ณ ์นจํ์๋ ๋ค๋ฅธํ์ด์ง (๋ง์ดํ์ด์ง, ๋์ฅ๊ณ ์กฐํ) ์์ฒญ์ด ๋จผ์ ์ฒ๋ฆฌ๋์ 400์๋ฌ..
const JWT_EXPIRY_TIME = 30 * 60 * 1000; //์ก์ธ์ค ํ ํฐ ๋ง๋ฃ์๊ฐ 30๋ถ์ ๋ฐ๋ฆฌ์ด๋ก ํํ
const onSilentRefresh = () => {
axios
.post("/api/auth/refresh")
.then((response) => {
const ACCESS_TOKEN = response.headers["access-token"]; //eyJ0eX.. ์๋ฒ์์ response header์ ์ฃ์ด๋ณด๋ด๋ ํ ํฐ๊ฐ
if (response.status === 200) {
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${ACCESS_TOKEN}`; //์์ฒญํค๋์ ์ก์ธ์ค ํ ํฐ ์ค์
console.log(
"ํ ํฐ ์ฌ๋ฐ๊ธ ์๋ฒ์ ์์ฒญ ํ App.js์์ ์ฌ๋ฐ๊ธ๋ ACCESS_TOKEN",
ACCESS_TOKEN
);
//refesh๋ก ์๋ก๋ฐ์์จ ์ก์ธ์ค ํ ํฐ ๋ฆฌ๋์ค์๋ ์ ์ฅํ๊ธฐ
dispatch(setLoggedIn({ userToken: ACCESS_TOKEN }));
//์ก์ธ์คํ ํฐ ๋ง๋ฃ๋๊ธฐ 1๋ถ ์ ๋ก๊ทธ์ธ ์ฐ์ฅ
setTimeout(onSilentRefresh, JWT_EXPIRY_TIME - 60000);
}
})
.catch((error) => console.log(error, "silent refresh ์๋ฌ"));
};
useEffect(() => {
if (isLoggedIn && userToken) {
axios.defaults.headers.common["Authorization"] = `Bearer ${userToken}`; //์์ฒญํค๋์ ์ก์ธ์ค ํ ํฐ ์ค์
console.log("์ด๋ฏธ์๋ ๋ฆฌ๋์ค userToken์ผ๋ก ํค๋์ ์ค์ ", userToken);
onSilentRefresh(); //์๋ก๊ณ ์นจํ๋ฉด ๋ฐ๋ก ์ก์ธ์คํ ํฐ ์ฌ๋ฐ๊ธํ๋ ํจ์์คํ
}
}, []);
return (
<BrowserRouter>
<GlobalStyle />
<ScrollToTop />
<Gnb />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<LogIn />} />
<Route path="/signup" element={<SignUpForm />} />
<Route path="/recipes/new" element={<NewRecipe />} />
<Route path="/auth/redirect" element={<OAuth2RedirectHandler />} />
<Route path="/recipes/edit" element={<EditRecipe />} />
<Route path="/recipes/:id" element={<RecipeDetail />} />
<Route path="/search" element={<FridgeDigging />} />
<Route path="/myfridge" element={<MyFridge />} />
<Route path="/mypage/:id" element={<MyPage />} />
<Route path="/admin" element={<AdminPanel />} />
</Routes>
{showToast && <CustomToast />}
<Footer />
</BrowserRouter>
);
}
export default App;
//MyPage.js
import MenuTab from "../../components/layout/MenuTab/MenuTab";
import { MyPageContainer } from "./MyPageStyle";
const MyPage = () => {
return (
<MyPageContainer>
<MenuTab></MenuTab>
</MyPageContainer>
);
};
export default MyPage;
//MenuTab.js
import { useState } from "react";
import { Link, useParams } from "react-router-dom";
import CommentsBox from "../MyCommentsBox/CommentsBox";
import MyProfile from "../MyProfile/MyProfile";
import MyRecipeBox from "../MyRecipeBox/MyRecipeBox";
import MyRecipeLikedBox from "../MyRecipeLikedBox/MyRecipeLikedBox";
import {
MenuTabContainer,
Menu,
Li,
MenuTitle,
MenuContent,
} from "./MenuTabStyle";
const MenuTab = () => {
//์ค์๊ฐ ์๊ฐ ๋ณํ
function timeSince(date) {
let seconds = Math.floor((new Date() - date) / 1000);
let interval = seconds / 31536000;
if (interval > 1) {
return Math.floor(interval) + "๋
์ ";
}
interval = seconds / 2592000;
if (interval > 1) {
return Math.floor(interval) + "๋ฌ ์ ";
}
interval = seconds / 86400;
if (interval > 1) {
return Math.floor(interval) + "์ผ ์ ";
}
interval = seconds / 3600;
if (interval > 1) {
return Math.floor(interval) + "์๊ฐ ์ ";
}
interval = seconds / 60;
if (interval > 1) {
return Math.floor(interval) + "๋ถ ์ ";
}
// return Math.floor(seconds) + "์ด ์ ";
return "๋ฐฉ๊ธ";
}
const menuArr = [
{
name: "๋ด ํ๋กํ",
title: "",
content: <MyProfile />,
pathname: "/profile",
id: 0,
},
{
name: "๋ด ๋ ์ํผ",
title: "๋ด๊ฐ ์์ฑํ ๋ ์ํผ",
content: <MyRecipeBox timeSince={timeSince} />,
pathname: "/written_recipes",
id: 1,
},
{
name: "๋ด ์ข์์",
title: "๋ด๊ฐ ์ข์ํ ๋ ์ํผ",
content: <MyRecipeLikedBox timeSince={timeSince} />,
pathname: "/loved_recipes",
id: 2,
},
{
name: "๋ฐ์ ๋๊ธ",
title: "๋ด๊ฐ ๋ฐ์ ๋๊ธ",
content: <CommentsBox timeSince={timeSince} />, //์๊ฐ ๋ณํ ํจ์ ๋ด๋ ค์ฃผ๊ธฐ
pathname: "/received_comments",
id: 3,
},
];
return (
<MenuTabContainer>
<Menu className="menu">
{menuArr.map((menu, idx) => (
<Li key={idx} isFocused={String(idx) === id}>
{/* ๋งํฌ๋ ์ปดํฌ๋ํธ ํญ ์์์๋ ์ปจํ
ํธ ์ฐ๊ฒฐ */}
<Link to={`/mypage/${menu.id}`}>{menu.name}</Link>
</Li>
))}
</Menu>
<MenuTitle>
{menuArr[id].title}
</MenuTitle>
<MenuContent className="MenuContent">
{menuArr[id].content}
</MenuContent>
</MenuTabContainer>
);
};
export default MenuTab;
//MyRecipeLikedBox.js
//๋ด๊ฐ ์ข์ํ ๋ ์ํผ ์ปดํฌ๋ํธ
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faTrashCan } from "@fortawesome/free-solid-svg-icons";
import {
RecipeFrameOuterContainer,
NoticeMsgBox,
} from "../MyRecipeBox/MyRecipeBoxStyle";
import RecipeFrame from "../RecipeFrame/RecipeFrame";
import UserName from "../../common/UserName/UserName";
import Pagination from "../../common/Pagination/Pagination";
import { useEffect, useRef, useState } from "react";
import axios from "axios";
import Loading from "../../common/Loading/Loading";
const MyRecipeLikedBox = ({ timeSince }) => {
const [myLikedRecipeList, setMyLikedRecipeList] = useState([]);
const [page, setPage] = useState(1); // ํ์ด์ง๋ค์ด์
์ผ๋ก ๋ฐ๋ ํ์ฌ ํ์ด์ง ์์น
const [total, setTotal] = useState(0); //์ ์ฒด ๊ฒ์๊ธ ์
const [totalPages, setTotalPages] = useState(0); //์ ์ฒด ํ์ด์ง ์
const [isUpdatedForLikedList, setIsUpdatedForLikedList] = useState(false); //์ข์์ํ ๋ ์ํผ ์ญ์ ์ ์
๋์ฌ๋ถ๋ก ํ๋ฉด์ ์ฌ๋ ๋๋ง ํ๋ ค๊ณ
const [isLoading, setIsLoading] = useState(true);
//๋ด๊ฐ ์ข์ํ ๋ ์ํผ ๋ชฉ๋ก ์กฐํํ๊ธฐ
const getMyLikedRecipeList = async () => {
try {
const { data } = await axios.get(
`/api/recipes/favorite?page=${page}`
//์์ ๋ฐฉํธ์ผ๋ก headers๋ฅผ ์ง์ ๋ฌ์์ค
// {
// headers: { Authorization: `Bearer ${userToken}` },
// }
);
console.log(data);
setTotal(data.pageInfo.totalElements);
setTotalPages(data.pageInfo.totalPages);
setMyLikedRecipeList([...data.data]);
setIsLoading(false); //๋ฐ์ดํฐ ๋ค ๋ฐ์์์ผ๋ ๋ก๋ฉ์ํ false๋ก ๋ง๋ค๊ธฐ
} catch (err) {
console.log(err);
}
};
//ํ์ด์ง ๋ฐ๋๋, ์ข์ํ๋ ๋ ์ํผ ๋ชฉ๋ก์์ ์ญ์ ํ ๋๋ง๋ค ์ฌ๋ ๋๋ง + ์ข์์ ๋๋ฅผ๋ ๋ฐ๋ก ์ข์์ ๋๋ฅธ ๋ชฉ๋ก ๋ ๋๋ง๋๊ฒ total ์์ ๋ฐ๋ผ ์ฌ๋ ๋๋ง
useEffect(() => {
getMyLikedRecipeList();
setIsUpdatedForLikedList(false);
}, [page, isUpdatedForLikedList]);
return (
<>
{isLoading ? (
<Loading />
) : (
<>
<RecipeFrameOuterContainer>
{/* ์๋ฒ์์ ๋ฐ์์จ ๋ด์ข์์ ๋ ์ํผ ๋ชฉ๋ก์ ์์๊ฐ ์์๋๋ง ๋ ์ํผ ํ๋ ์ ๋์ฐ๊ธฐ */}
{myLikedRecipeList.length ? (
myLikedRecipeList.map((data) => (
<RecipeFrame
key={data.id}
recipeIdForLikedList={data.id}
setIsUpdatedForLikedList={setIsUpdatedForLikedList}
imagePath={data.imagePath}
title={data.title}
date={timeSince(Date.parse(data.lastModifiedAt))}
icon2={
<FontAwesomeIcon
icon={faTrashCan}
mode={"my_liked_recipe"}
></FontAwesomeIcon>
}
>
<UserName
image={data.member.profileImagePath}
name={data.member.name}
></UserName>
</RecipeFrame>
))
) : (
<NoticeMsgBox>์์ง ๋ด๊ฐ ์ข์ํ ๋ ์ํผ๊ฐ ์์ด์</NoticeMsgBox>
)}
</RecipeFrameOuterContainer>
<Pagination
page={page}
setPage={setPage}
totalPages={totalPages}
></Pagination>
</>
)}
</>
);
};
export default MyRecipeLikedBox;
์ปดํฌ๋ํธ๊ฐ App.js -> MyPage.js -> MenuTab.js -> MyRecipeLikedBox.js ์ด๋ ๊ฒ ๊ตฌ์ฑ๋์ด์์ด์ ์ฌ๋ฌ ์ฝ๋๋ฅผ ๊ฐ์ง๊ณ ์์ง๋ง ์๋ฒ์ ํต์ ํ๋ ์ฝ๋๋ App ์ปดํฌ๋ํธ์ MyRecipeLikedBox ์ปดํฌ๋ํธ ๋๊ตฐ๋ฐ์ ์๋ค.
//App.js
const onSilentRefresh = () => {
axios
.post("/api/auth/refresh")
.then((response) => {
const ACCESS_TOKEN = response.headers["access-token"]; //eyJ0eX.. ์๋ฒ์์ response header์ ์ฃ์ด๋ณด๋ด๋ ํ ํฐ๊ฐ
if (response.status === 200) {
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${ACCESS_TOKEN}`; //์์ฒญํค๋์ ์ก์ธ์ค ํ ํฐ ์ค์
console.log(
"ํ ํฐ ์ฌ๋ฐ๊ธ ์๋ฒ์ ์์ฒญ ํ App.js์์ ์ฌ๋ฐ๊ธ๋ ACCESS_TOKEN",
ACCESS_TOKEN
);
//refesh๋ก ์๋ก๋ฐ์์จ ์ก์ธ์ค ํ ํฐ ๋ฆฌ๋์ค์๋ ์ ์ฅํ๊ธฐ
dispatch(setLoggedIn({ userToken: ACCESS_TOKEN }));
//์ก์ธ์คํ ํฐ ๋ง๋ฃ๋๊ธฐ 1๋ถ ์ ๋ก๊ทธ์ธ ์ฐ์ฅ
setTimeout(onSilentRefresh, JWT_EXPIRY_TIME - 60000);
}
})
.catch((error) => console.log(error, "silent refresh ์๋ฌ"));
};
useEffect(() => {
if (isLoggedIn && userToken) {
axios.defaults.headers.common["Authorization"] = `Bearer ${userToken}`; //์์ฒญํค๋์ ์ก์ธ์ค ํ ํฐ ์ค์
// console.log(axios.defaults.headers.common);
console.log("์ด๋ฏธ์๋ ๋ฆฌ๋์ค userToken์ผ๋ก ํค๋์ ์ค์ ", userToken);
onSilentRefresh(); //์๋ก๊ณ ์นจํ๋ฉด ๋ฐ๋ก ์ก์ธ์คํ ํฐ ์ฌ๋ฐ๊ธํ๋ ํจ์์คํ
}
}, []);
๋จผ์ App ์ปดํฌ๋ํธ์๋ useEffect๋ฅผ ํ์ฉํด์ ๋ก๊ทธ์ธ ์ํ์ด๊ณ ํ ํฐ์ ๋ณด๊ฐ ์์ผ๋ฉด ๊ธฐ์กด์ ๊ฐ์ง๊ณ ์๋ ํ ํฐ์ ํค๋์ ์ค์ ํ ํ ๊ทธ ํ ํฐ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ onSilentRefresh() ํจ์๋ฅผ ์คํ์ํจ๋ค.
onSilentRefresh() ํจ์์์ ์ก์ธ์คํ ํฐ์ ์ฌ๋ฐ๊ธ๋ฐ๋ ์์ฒญ์ ์๋ฒ์ ๋ณด๋ด๋๋ฐ, ์๋ฒ ์๋ต์ ์ฑ๊ณตํ๋ฉด ์ฌ๋ฐ๊ธ๋ฐ์ ํ ํฐ์ ๋ค์ ์์ฒญํค๋์ ์ฌ์ด์ค๋ค. ์ด์ ๊ทธ๋ผ (ํ ํฐ ์ค์ ํ๋ ์ฝ๋๊ฐ ์ ์ผ ๋จผ์ ์คํ๋๊ธฐ๋ง ํ๋ค๋ฉด) ํ ํฐ์ด ํ์ํ ๋ค๋ฅธ ์์ฒญ์ ๋ณด๋ผ๋ ํค๋์ ํ ํฐ์ ๋ณด ๋ด์์ค ํ์๊ฐ ์์ด์ง๋ค.
(์๋ก๊ณ ์นจ์ํด๋ ๋ก๊ทธ์ธ ์ ๋ณด์ ํ ํฐ์ ๋ณด๋ ๊ณ์ ๋จ์์๋๋ฐ ๊ทธ ์ด์ ๋ redux persist๋ก ๋ก๊ทธ์ธ์ ๋ณด๋ฅผ ๊ด๋ฆฌํ๊ธฐ ๋๋ฌธ์ ์๋ก๊ณ ์นจํด๋ ์ธ์ ์คํ ๋ฆฌ์ง์ ๋จ์์์ด ๋ก๊ทธ์ธ ์ ๋ณด๊ฐ ๋ ๋ผ๊ฐ์ง ์๋๋ค.)
//MyRecipeLikedBox.js
//๋ด๊ฐ ์ข์ํ ๋ ์ํผ ๋ชฉ๋ก ์กฐํํ๊ธฐ
const getMyLikedRecipeList = async () => {
try {
const { data } = await axios.get(
`/api/recipes/favorite?page=${page}`
// {
// headers: { Authorization: `Bearer ${userToken}` },
// }
);
console.log(data);
setTotal(data.pageInfo.totalElements);
setTotalPages(data.pageInfo.totalPages);
setMyLikedRecipeList([...data.data]);
setIsLoading(false); //๋ฐ์ดํฐ ๋ค ๋ฐ์์์ผ๋ ๋ก๋ฉ์ํ false๋ก ๋ง๋ค๊ธฐ
} catch (err) {
console.log(err);
}
};
//ํ์ด์ง ๋ฐ๋๋, ์ข์ํ๋ ๋ ์ํผ ๋ชฉ๋ก์์ ์ญ์ ํ ๋๋ง๋ค ์ฌ๋ ๋๋ง + ์ข์์ ๋๋ฅผ๋ ๋ฐ๋ก ์ข์์ ๋๋ฅธ ๋ชฉ๋ก ๋ ๋๋ง๋๊ฒ total ์์ ๋ฐ๋ผ ์ฌ๋ ๋๋ง
useEffect(() => {
getMyLikedRecipeList();
setIsUpdatedForLikedList(false);
}, [page, isUpdatedForLikedList]);
๋ด ์ข์์ ์ปดํฌ๋ํธ์์๋ useEffectํ ์์ getMyLikedRecipeList() ํจ์๋ฅผ ํธ์ถํด์ ์ฌ๊ธฐ์ favorite ๋ชฉ๋ก get ์์ฒญ์ ๋ณด๋ธ๋ค. ์ ๊ธฐ headers๊ฐ ์ฃผ์์ฒ๋ฆฌ ๋์ด์๋ ๋ถ๋ถ์ ๋ฌธ์ ํด๊ฒฐ์ด ๋๊ธฐ ์ ์ ์์ ๋ฐฉํธ์ผ๋ก ์๋ฒ์ ์์ฒญ ๋ณด๋ผ๋ ํค๋์ ํ ํฐ ์ ๋ณด๋ฅผ ์์ ๋ฃ์ด์ค ๊ฒ์ด๋ค.
๋ด์ข์์ ์ปดํฌ๋ํธ๊ฐ ํ์ ์ปดํฌ๋ํธ์ด๊ธฐ๋๋ฌธ์, ์ฌ๊ธฐ์ useEffect๊ฐ ๋จผ์ ์คํ์ด๋๊ณ , ๊ทธ๋ฆฌ๊ณ ๋์ ์ต์์์ธ App ์ปดํฌ๋ํธ์ useEffect๊ฐ ์คํ์ด ๋๋ค.
๊ทธ๋์ ์ด์ ์ฝ๋์์๋ ๋ง์ดํ์ด์ง์์ ์๋ก๊ณ ์นจํ๋ฉด ๋ด๊ฐ ์ข์ํ๋ ๋ ์ํผ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ favorite?page=1 ์์ฒญ์ธ get ์์ฒญ์ด ๊ฐ์ฅ ๋จผ์ ์คํ๋๋ค.
ํ์ง๋ง ํค๋์ ํ ํฐ์ ์ฌ์ด์ฃผ๋ App.js์ ์ฝ๋๊ฐ ๊ทธ ์ดํ์ ์คํ๋๋ฏ๋ก get์์ฒญ์ธ favorite ์์ฒญ์ ๋ค์ฌ๋ค๋ณด๋ฉด ์์ฒญํค๋์ธ Request Headers์ ์ด๋์๋ Authorization์ ํ ํฐ์ด ๋ด๊ฒจ์์ง ์๋ค. ํ ํฐ ์ ๋ณด๊ฐ ํ์ํ ๋ง์ดํ์ด์ง์ ํ ํฐ์ด ์๋์ํ๋ก ์์ฒญ์ ๋ณด๋ด๋ ๋น์ฐํ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ๋ ๊ฒ์ด๋ค.
์ดํ Suspense Lazy ์ ์ฉ ์ ์ฒด ์ฝ๋
๋ค๋ฅธ ์ฝ๋๋ ๊ฐ์ผ๋ฏ๋ก App.js ๋ง ์ ์
//App.js
//lazy, Suspense ์ถ๊ฐ
import React, { useState, useEffect, lazy, Suspense } from "react";
import GlobalStyle from "./GlobalStyle";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import styled from "styled-components";
import Gnb from "./components/common/GNB/Gnb";
import SignUpForm from "./components/layout/RegisterForm/SignUpForm";
import LogIn from "./pages/LogIn/LogIn";
import NewRecipe from "./pages/NewRecipe/NewRecipe";
import OAuth2RedirectHandler from "./components/layout/RegisterForm/OAuth2RedirectHandler";
import EditRecipe from "./pages/EditRecipe/EditRecipe";
==========
//๋ก๋ฉ ์ปดํฌ๋ํธ ์ถ๊ฐ
import Loading from "./components/common/Loading/Loading";
//React Lazy๋ก ๋ณํ
const Home = lazy(() => import("./pages/Home/Home"));
const RecipeDetail = lazy(() => import("./pages/RecipeDetail/RecipeDetail"));
const FridgeDigging = lazy(() => import("./pages/FridgeDigging/FridgeDigging"));
const MyFridge = lazy(() => import("./pages/MyFridge/MyFridge"));
const MyPage = lazy(() => import("./pages/MyPage/MyPage"));
const AdminPanel = lazy(() => import("./components/layout/Admin/AdminPanel"));
==========
import axios from "axios";
import { setLoggedIn } from "./features/userSlice";
import Footer from "./components/layout/Footer/Footer";
import { useRef } from "react";
import CustomToast from "./components/common/CustomToast/CustomToast";
function App() {
const dispatch = useDispatch(); //for redux dispatch
// alert ๋์ ์ฌ์ฉ๋๋ toast ๊ด๋ จ ์ํ
const showToast = useSelector((state) => {
return state.toast.showToast;
});
//๋ก๊ทธ์ธ ์ํ ๊ฐ์ ธ์์ ๋ณ์์ ์ ์ฅ
const isLoggedIn = useSelector((state) => {
return state.user.isLoggedIn;
});
const userToken = useSelector((state) => {
return state.user.userToken;
});
//! Appjs์์ ์ก์ธ์คํ ํฐ ์ฌ๋ฐ๊ธ ์์ฒญํ๊ณ ํ ํฐ์ ๋ฐ๋ก ํค๋์ ์ค๋ ์๋ก๊ณ ์นจํ์๋ ๋ค๋ฅธํ์ด์ง (๋ง์ดํ์ด์ง, ๋์ฅ๊ณ ์กฐํ) ์์ฒญ์ด ๋จผ์ ์ฒ๋ฆฌ๋์ 400์๋ฌ..
const JWT_EXPIRY_TIME = 30 * 60 * 1000; //์ก์ธ์ค ํ ํฐ ๋ง๋ฃ์๊ฐ 30๋ถ์ ๋ฐ๋ฆฌ์ด๋ก ํํ
const onSilentRefresh = () => {
axios
.post("/api/auth/refresh")
.then((response) => {
const ACCESS_TOKEN = response.headers["access-token"]; //eyJ0eX.. ์๋ฒ์์ response header์ ์ฃ์ด๋ณด๋ด๋ ํ ํฐ๊ฐ
if (response.status === 200) {
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${ACCESS_TOKEN}`; //์์ฒญํค๋์ ์ก์ธ์ค ํ ํฐ ์ค์
console.log(
"ํ ํฐ ์ฌ๋ฐ๊ธ ์๋ฒ์ ์์ฒญ ํ App.js์์ ์ฌ๋ฐ๊ธ๋ ACCESS_TOKEN",
ACCESS_TOKEN
);
//refesh๋ก ์๋ก๋ฐ์์จ ์ก์ธ์ค ํ ํฐ ๋ฆฌ๋์ค์๋ ์ ์ฅํ๊ธฐ
dispatch(setLoggedIn({ userToken: ACCESS_TOKEN }));
//์ก์ธ์คํ ํฐ ๋ง๋ฃ๋๊ธฐ 1๋ถ ์ ๋ก๊ทธ์ธ ์ฐ์ฅ
setTimeout(onSilentRefresh, JWT_EXPIRY_TIME - 60000);
}
})
.catch((error) => console.log(error, "silent refresh ์๋ฌ"));
};
useEffect(() => {
if (isLoggedIn && userToken) {
axios.defaults.headers.common["Authorization"] = `Bearer ${userToken}`; //์์ฒญํค๋์ ์ก์ธ์ค ํ ํฐ ์ค์
console.log("์ด๋ฏธ์๋ ๋ฆฌ๋์ค userToken์ผ๋ก ํค๋์ ์ค์ ", userToken);
onSilentRefresh(); //์๋ก๊ณ ์นจํ๋ฉด ๋ฐ๋ก ์ก์ธ์คํ ํฐ ์ฌ๋ฐ๊ธํ๋ ํจ์์คํ
}
}, []);
return (
<BrowserRouter>
<GlobalStyle />
<ScrollToTop />
<Gnb />
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<LogIn />} />
<Route path="/signup" element={<SignUpForm />} />
<Route path="/recipes/new" element={<NewRecipe />} />
<Route path="/auth/redirect" element={<OAuth2RedirectHandler />} />
<Route path="/recipes/edit" element={<EditRecipe />} />
<Route path="/recipes/:id" element={<RecipeDetail />} />
<Route path="/search" element={<FridgeDigging />} />
<Route path="/myfridge" element={<MyFridge />} />
<Route path="/mypage/:id" element={<MyPage />} />
<Route path="/admin" element={<AdminPanel />} />
</Routes>
</Suspense>
{showToast && <CustomToast />}
<Footer />
</BrowserRouter>
);
}
export default App;
๋จผ์ React.lazy๋ก dynamic import ๋์ ๋ถ๋ฌ์ค๊ธฐ๋ฅผ ํ๋ค. ์ด์ ์๋ ์ต์์์ ๋ชจ๋ import ๋ฅผ ๋ถ๋ฌ์ ์ฌ์ฉํ๋ ํ์ผ/๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์น ๋ค ๊ฐ์ ธ์ค๋ ์ ์ ๋ถ๋ฌ์ค๊ธฐ๋ฅผ ํ๋ค. ์ด๋ ๋ชจ๋ ์ปดํฌ๋ํธ๋ฅผ ํ๋ฒ์ ๋ถ๋ฌ์ค๊ธฐ๋๋ฌธ์ ์ฒซ ํ๋ฉด์ด ๋ ๋๋ง๋ ๋๊น์ง ์๊ฐ์ด ์ค๋๊ฑธ๋ฆฐ๋ค๋ ๋จ์ ๋ ์์๋๋ฐ, ํ์ํ ์ปดํฌ๋ํธ๋ฅผ lazy๋ก ๋์ ๋ถ๋ฌ์ค๊ธฐ๋ฅผ ํ ๊ฒฝ์ฐ ๋น์ฅ ์ฌ์ฉํ์ง ์๋ ์ปดํฌ๋ํธ๋ ๋์ค์ ๋ถ๋ฌ์ค๊ฒ ๋๋ค.
๋๋ ์ฌ๊ธฐ์ ์ฌ์ฉ์ ์ธ๊ฐ๊ฐ ํ์ํ ํ์ด์ง๋ง React lazy๋ฅผ ํตํด ๋์ import๋ฅผ ํด์๋๋ฐ ์๋์ด ์๋์ด์ ๋ค๋ฅธ ์ปดํฌ๋ํธ๋ lazy๋ก ๋ถ๋ฌ์ค์ง๋ ์์๋ค.
๊ทธ๋ฆฌ๊ณ Route ์ปดํฌ๋ํธ๋ค์ Suspense๋ก ๊ฐ์ธ๊ณ , Suspense ํ์์์ ๋ผ์ฐํธ๋ก ๋ณด์ฌ์ค ์ปดํฌ๋ํธ๋ React.lazy๋ก ๋ถ๋ฌ์จ๋ค.
Suspense๋ lazy์ปดํฌ๋ํธ๊ฐ ๋ก๋๋๊ธธ ๊ธฐ๋ค๋ฆฌ๋๋์ fallback ์ธ์๋ก ๋ฐ๋ ์ปจํ ์ธ ๋ฅผ ๋ณด์ฌ์ค๋ค. ์ฌ๊ธฐ์ ๋ก๋ฉ์ปดํฌ๋ํธ๋ฅผ fallback ์ธ์๋ก์ฃผ์๋ค.
์ด๋ ๊ฒ Suspense๋ lazy๊ฐ ์ ์ฉ๋ ์ธ๊ฐ๊ฐ ํ์ํ ํ์ด์ง์์ ๋ฐ์ดํฐ๋ฅผ ๋ค ๋ฐ์์ฌ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๊ณ ๊ทธ๋์ ๋ก๋ฉ์ปดํฌ๋ํธ๋ฅผ ๋ณด์ฌ์ค๋ค.
App.js์์ ํ ํฐ์ค์ ํ๋ ๋น๋๊ธฐ ๋ก์ง์ด ๋จผ์ ์๋๋๊ณ ์ดํ Suspense๊ฐ ๊ฑธ๋ ค์๋ ํ์ ์ปดํฌ๋ํธ์ธ ๋ง์ดํ์ด์ง์ ์์ฒญ์ ๋ณด๋ด๋ฉด์ ์ํ๋ ์์๋๋ก ์๋์ด ๋๋๊ฒ์ด๋ค.
์ด๋ ๊ฒ ์ ์ฉํ๋ฉด ๋ฐฐํฌํ๊ฒฝ์์๋ ๊น๋ํ๊ฒ App.js์ refresh ์์ฒญ์ด ๋จผ์ ๊ฐ๋ฏ๋ก ๋จผ์ ํ ํฐ์ด ์ค์ ๋๊ณ , ๊ทธ ๋ค์ ๊ทธ ํ ํฐ ์ ๋ณด์ํจ๊ป get ์์ฒญ์ ์คํํ๋ฏ๋ก ๋ฌธ์ ์์ด ์ ์๋ํ๋ค. ๐
์ ๋ฆฌ
๐ React.lazy๋ฅผ ํตํด ๋์ import๋ฅผ ํด์์ ์ฝ๋๋ฅผ ๋ถํ ํ๋ค. Code splitting!
- ํ์ํ์ง ์์ ์ฝ๋๋ ์ฒ์์ ๋ถ๋ฌ์ค์ง ์๋๋ค.
- ์ฒ์ ๋ฆฌ์กํธ๊ฐ ๋ฒ๋ค ํ์ผ์ ๋ค ๋ค์ด ๋ฐ๊ณ ์ดํ์ ์ ์ฒด ์ฑ์ ํ๋ฉด์ ๋ณด์ฌ์ฃผ๋๋ฐ, ์ฝ๋ ๋ถํ ์ ํ๋ฉด ๋ฒ๋ค์ด ๊ฑฐ๋ํด์ง๋ ๊ฑธ ๋ง์ ์ด๊ธฐ ์ฑ ๋ก๋ฉ์๊ฐ์ ๋จ์ถ์์ผ์ฃผ๋ ์ฅ์ ๋ ์๋ค.
๐ lazy ์ปดํฌ๋ํธ๋ค์ ๋จ๋ ์ผ๋ก ์ฌ์ฉ ๋ชปํ๊ณ , Suspense ํ์์ ์ปดํฌ๋ํธ์์ ๋ ๋๋ง๋์ด์ผ ํ๋ค.
๐ Suspense๋ก ๊ฐ์ธ์ฃผ๋ ์์ ์ปดํฌ๋ํธ๋ค์ ๋น๋๊ธฐ ์์ ์ ์ฒ๋ฆฌํ๋ ์ปดํฌ๋ํธ์ด๋ค. (MyPage์ปดํฌ๋ํธ)
๐ Susepnse์ ๊ฑธ๋ ค์๋ ์ปดํฌ๋ํธ๊ฐ ๋น๋๊ธฐ ์์ ์ ์งํํ๋ ๋์์๋ fallback์ผ๋ก ํ ๋น๋ฐ๋ ํน์ ์ปดํฌ๋ํธ(๋ก๋ฉ)๊ฐ ๋ ๋๋ง๋๋ค.
๐ ๋น๋๊ธฐ ์์ ์ ํ๋ ์์ ์ปดํฌ๋ํธ๋ (๋ด ์์์ ๊ฒฝ์ฐ <MyPage>๊ฐ์ ์ปดํฌ๋ํธ) ๋น๋๊ธฐ ์์ ์ด ๋๋๋ฉด, ๊ทธ๋ ์๋ ์ปดํฌ๋ํธ์ธ MyPage๋ฅผ ๋ฆฌ๋ ๋๋งํด์ ๋ณด์ฌ์ค๋ค.
๋ฌธ์ ๋ฅผ ์ด๋ป๊ฒ๋ ํด๊ฒฐํ๊ณ ๋ ์์๋ณด๋... ๋ณดํต์ React lazy์ Suspense๋ ๋น๋๊ธฐ ๋ ๋๋ง์ ๋ฌธ์ ๋ณด๋ค๋, ๋ฒ๋ค ์ฌ์ด์ฆ์ ํฌ๊ธฐ๋ฅผ ์ค์ด๊ณ ์ฑ์ ์ฑ๋ฅ ๊ฐ์ ์ ์ํด ์ฌ์ฉ๋๋ค. ๋น๋๊ธฐ ๋ ๋๋ง์ ๊ด๋ จ๋ ๋ถ๋ถ ์ฆ, ๋น๋๊ธฐ ํต์ ์ด ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ ธ๋ค๊ฐ ๋น๋๊ธฐ ํต์ ์ด ๋๋๋ฉด ๊ทธ๋ ๋ฐ์ดํฐ๋ฅผ ํ๋ฉด์ ๋์์ฃผ๋ ๋ฐฉ๋ฒ์ data-fetching ๊ด๋ จ๋ Suspense๊ฐ ๋ ๊ด๋ จ์ด ์๋ ๊ฒ ๊ฐ๋ค.
๋์ ๊ฒฝ์ฐ App.js ์ ๋ง์ดํ์ด์ง์์์ ๋น๋๊ธฐ ํธ์ถ์์ ์ด์๊ฐ React lazy์ Suspense๋ก๋ ํด๊ฒฐ์ด ๋์์ง๋ง.. ์ฌ์ค ๋ ์ ํํ ๋ฌธ์ ๊ฐ ๋ฐ์๋ ๊ทธ ๋ถ๋ถ์ ํด๊ฒฐํ๊ณ ์ถ์ผ๋ฉด Suspense for data fetching ๋ถ๋ถ์ ๋ ๊ณต๋ถํด์ ์ ์ฉํด๋ณด๋ ๊ฒ๋ ์ข์ ๊ฒ ๊ฐ๋ค.
'React' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
typescript + react | form submit e.preventDefault() ์๋์ํจ (0) | 2023.03.04 |
---|---|
npm install -g ์๋ฌ EACCES (1) | 2023.01.05 |
ํ๋ก ํธ์๋ ๊ธฐ์ ๋ฉด์ ๊ธฐ์ด ์์์ง๋ฌธ ๋ต๋ณ ์ ๋ฆฌ (0) | 2022.11.27 |
๋ฆฌ์กํธ react-hook-form input ๋น์ด์์ ๋ button disabled ์ ์ฉ (0) | 2022.10.31 |
VSCode ํน์ ๊ธ์ ์ ๋ณ๊ฒฝ (0) | 2022.10.12 |