From dfbc6ce1be128514c91376164b9b82509381f0e8 Mon Sep 17 00:00:00 2001 From: rarlala Date: Mon, 30 Sep 2024 15:21:44 +0900 Subject: [PATCH] feat: add skeleton ui and tanstack-query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tanstack-query 사용 - Skeleton UI 사용 - next dynamic 활용하여 동적 로딩 적용 --- .pnp.cjs | 34 ++++++++++++++++ package.json | 1 + src/app/page.tsx | 46 +++++++++++++++------- src/components/common/skeleton.tsx | 18 +++++++++ src/components/main/banner.tsx | 9 +++++ src/components/main/bannerSection.tsx | 20 +++++++++- src/components/main/boxBanner.tsx | 16 +++++++- src/components/main/boxBannerItem.tsx | 17 ++++++++ src/components/main/boxSection.tsx | 20 +++++++++- src/components/main/mobileBannerSlider.tsx | 11 +++++- src/fetch/main.ts | 5 +++ src/lib/next-auth/index.tsx | 18 +++++---- src/types/main.ts | 5 +++ yarn.lock | 19 +++++++++ 14 files changed, 212 insertions(+), 27 deletions(-) create mode 100644 src/components/common/skeleton.tsx create mode 100644 src/fetch/main.ts diff --git a/.pnp.cjs b/.pnp.cjs index 62c3e44..c04fdf4 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -28,6 +28,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "./",\ "packageDependencies": [\ ["@hookform/resolvers", "virtual:fab99ab653819c31700118e3551051ec2498dddb808f51896150d7d12ac371c264b5fa46a662ab7fea4ee02c7e8cf37d0e46b047c0fd340fb8234dee7ca27815#npm:3.9.0"],\ + ["@tanstack/react-query", "virtual:fab99ab653819c31700118e3551051ec2498dddb808f51896150d7d12ac371c264b5fa46a662ab7fea4ee02c7e8cf37d0e46b047c0fd340fb8234dee7ca27815#npm:5.56.2"],\ ["@types/node", "npm:20.16.5"],\ ["@types/prop-types", "npm:15.7.13"],\ ["@types/react", "npm:18.3.5"],\ @@ -625,6 +626,38 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@tanstack/query-core", [\ + ["npm:5.56.2", {\ + "packageLocation": "../../../../.yarn/berry/cache/@tanstack-query-core-npm-5.56.2-1bcea5cf6e-10c0.zip/node_modules/@tanstack/query-core/",\ + "packageDependencies": [\ + ["@tanstack/query-core", "npm:5.56.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@tanstack/react-query", [\ + ["npm:5.56.2", {\ + "packageLocation": "../../../../.yarn/berry/cache/@tanstack-react-query-npm-5.56.2-fc27f4cc61-10c0.zip/node_modules/@tanstack/react-query/",\ + "packageDependencies": [\ + ["@tanstack/react-query", "npm:5.56.2"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:fab99ab653819c31700118e3551051ec2498dddb808f51896150d7d12ac371c264b5fa46a662ab7fea4ee02c7e8cf37d0e46b047c0fd340fb8234dee7ca27815#npm:5.56.2", {\ + "packageLocation": "./.yarn/__virtual__/@tanstack-react-query-virtual-8c3374119f/5/.yarn/berry/cache/@tanstack-react-query-npm-5.56.2-fc27f4cc61-10c0.zip/node_modules/@tanstack/react-query/",\ + "packageDependencies": [\ + ["@tanstack/react-query", "virtual:fab99ab653819c31700118e3551051ec2498dddb808f51896150d7d12ac371c264b5fa46a662ab7fea4ee02c7e8cf37d0e46b047c0fd340fb8234dee7ca27815#npm:5.56.2"],\ + ["@tanstack/query-core", "npm:5.56.2"],\ + ["@types/react", "npm:18.3.5"],\ + ["react", "npm:18.3.1"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@types/cookie", [\ ["npm:0.6.0", {\ "packageLocation": "../../../../.yarn/berry/cache/@types-cookie-npm-0.6.0-1f4c3f48f0-10c0.zip/node_modules/@types/cookie/",\ @@ -1845,6 +1878,7 @@ const RAW_RUNTIME_STATE = "packageDependencies": [\ ["eqcm", "workspace:."],\ ["@hookform/resolvers", "virtual:fab99ab653819c31700118e3551051ec2498dddb808f51896150d7d12ac371c264b5fa46a662ab7fea4ee02c7e8cf37d0e46b047c0fd340fb8234dee7ca27815#npm:3.9.0"],\ + ["@tanstack/react-query", "virtual:fab99ab653819c31700118e3551051ec2498dddb808f51896150d7d12ac371c264b5fa46a662ab7fea4ee02c7e8cf37d0e46b047c0fd340fb8234dee7ca27815#npm:5.56.2"],\ ["@types/node", "npm:20.16.5"],\ ["@types/prop-types", "npm:15.7.13"],\ ["@types/react", "npm:18.3.5"],\ diff --git a/package.json b/package.json index f5f1d9f..41abf4e 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@hookform/resolvers": "^3.9.0", + "@tanstack/react-query": "^5.56.2", "classnames": "^2.5.1", "immer": "^10.1.1", "next": "14.2.9", diff --git a/src/app/page.tsx b/src/app/page.tsx index 09973e3..504ab91 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,28 +1,44 @@ 'use client'; -import { useEffect, useState } from 'react'; -import BannerSection from '@/components/main/bannerSection'; -import BoxSection from '@/components/main/boxSection'; +import dynamic from 'next/dynamic'; +import { useQuery } from '@tanstack/react-query'; +import { BannerSectionSkeleton } from '@/components/main/bannerSection'; +import { BoxSectionSkeleton } from '@/components/main/boxSection'; import ViewMoreButton from '@/components/main/viewMoreButton'; -import { BannerType, BoxBannerType } from '@/types/main'; +import { fetchMainData } from '@/fetch/main'; +import { MainDataType } from '@/types/main'; + +const BannerSection = dynamic(() => import('@/components/main/bannerSection'), { + loading: () => , + ssr: false, +}); + +const BoxSection = dynamic(() => import('@/components/main/boxSection'), { + loading: () => , + ssr: false, +}); export default function Home() { - const [data, setData] = useState<{ - banners: BannerType[]; - boxes: BoxBannerType[]; - } | null>(null); + const { data } = useQuery({ + queryKey: ['main'], + queryFn: fetchMainData, + staleTime: 60000, + }); - useEffect(() => { - fetch('/main') - .then((response) => response.json()) - .then((data) => setData(data)); - }, []); + if (!data) { + return ( +
+ + +
+ ); + } return (
- - + +
diff --git a/src/components/common/skeleton.tsx b/src/components/common/skeleton.tsx new file mode 100644 index 0000000..aa191d8 --- /dev/null +++ b/src/components/common/skeleton.tsx @@ -0,0 +1,18 @@ +import cn from 'classnames'; + +type Props = { + width?: string | number; + height?: string | number; + style?: string; +}; + +const Skeleton = ({ width, height, style }: Props) => { + return ( +
+ ); +}; + +export default Skeleton; diff --git a/src/components/main/banner.tsx b/src/components/main/banner.tsx index a8b7448..eb6d709 100644 --- a/src/components/main/banner.tsx +++ b/src/components/main/banner.tsx @@ -1,6 +1,7 @@ import Image from 'next/image'; import Link from 'next/link'; import cn from 'classnames'; +import Skeleton from '../common/skeleton'; import { BannerType } from '@/types/main'; type Props = { @@ -9,6 +10,10 @@ type Props = { }; const Banner = ({ data, style }: Props) => { + if (!data) { + return null; + } + return ( { ); }; +export const BannerSkeleton = () => { + return ; +}; + export default Banner; diff --git a/src/components/main/bannerSection.tsx b/src/components/main/bannerSection.tsx index ac9cf83..fc19ef4 100644 --- a/src/components/main/bannerSection.tsx +++ b/src/components/main/bannerSection.tsx @@ -1,5 +1,8 @@ -import MobileBannerSlider from './mobileBannerSlider'; +import MobileBannerSlider, { + MobileBannerSliderSkeleton, +} from './mobileBannerSlider'; import Banner from './banner'; +import Skeleton from '../common/skeleton'; import { BannerType } from '@/types/main'; type Props = { @@ -19,4 +22,19 @@ const BannerSection = ({ data }: Props) => { ); }; +export const BannerSectionSkeleton = () => { + return ( +
+
+ {[...Array(3)].map((_, index) => ( + + ))} +
+
+ +
+
+ ); +}; + export default BannerSection; diff --git a/src/components/main/boxBanner.tsx b/src/components/main/boxBanner.tsx index 03f9f30..ea2a83a 100644 --- a/src/components/main/boxBanner.tsx +++ b/src/components/main/boxBanner.tsx @@ -1,7 +1,8 @@ import Image from 'next/image'; import Link from 'next/link'; -import BoxBannerItem from './boxBannerItem'; +import BoxBannerItem, { BoxBannerItemSkeleton } from './boxBannerItem'; import { BoxBannerType, ProductType } from '@/types/main'; +import Skeleton from '../common/skeleton'; type Props = { data: BoxBannerType; @@ -31,4 +32,17 @@ const BoxBanner = ({ data }: Props) => { ); }; +export const BoxBannerSkeleton = () => { + return ( +
+
+ + + +
+ +
+ ); +}; + export default BoxBanner; diff --git a/src/components/main/boxBannerItem.tsx b/src/components/main/boxBannerItem.tsx index 97a82cd..a783ae0 100644 --- a/src/components/main/boxBannerItem.tsx +++ b/src/components/main/boxBannerItem.tsx @@ -2,6 +2,7 @@ import Image from 'next/image'; import Link from 'next/link'; import { ProductType } from '@/types/main'; import { formatLikesToK, formatWithCommas } from '@/utils/format'; +import Skeleton from '../common/skeleton'; import { Icons } from '../icons'; type Props = { @@ -46,4 +47,20 @@ const BoxBannerItem = ({ product }: Props) => { ); }; +export const BoxBannerItemSkeleton = () => { + return ( +
+
+ + +
+ + + +
+
+
+ ); +}; + export default BoxBannerItem; diff --git a/src/components/main/boxSection.tsx b/src/components/main/boxSection.tsx index 269c799..dbef009 100644 --- a/src/components/main/boxSection.tsx +++ b/src/components/main/boxSection.tsx @@ -1,8 +1,8 @@ 'use client'; import Masonry, { ResponsiveMasonry } from 'react-responsive-masonry'; -import Banner from './banner'; -import BoxBanner from './boxBanner'; +import Banner, { BannerSkeleton } from './banner'; +import BoxBanner, { BoxBannerSkeleton } from './boxBanner'; import BoxSlider from './boxSlider'; import { BannerType, @@ -19,6 +19,7 @@ const BoxSection = ({ data }: Props) => { if (data === undefined) { return null; } + return (
@@ -45,4 +46,19 @@ const BoxSection = ({ data }: Props) => { ); }; +export const BoxSectionSkeleton = () => { + return ( +
+ + + + + + + + +
+ ); +}; + export default BoxSection; diff --git a/src/components/main/mobileBannerSlider.tsx b/src/components/main/mobileBannerSlider.tsx index 5b08c8f..b1226af 100644 --- a/src/components/main/mobileBannerSlider.tsx +++ b/src/components/main/mobileBannerSlider.tsx @@ -7,8 +7,9 @@ import 'swiper/css'; import 'swiper/css/navigation'; import 'swiper/css/pagination'; import { Autoplay, Pagination } from 'swiper/modules'; -import { BannerType } from '@/types/main'; import Banner from './banner'; +import Skeleton from '../common/skeleton'; +import { BannerType } from '@/types/main'; import { Icons } from '../icons'; type Props = { @@ -75,4 +76,12 @@ const MobileBannerSlider = ({ data }: Props) => { ); }; +export const MobileBannerSliderSkeleton = () => { + return ( +
+ +
+ ); +}; + export default MobileBannerSlider; diff --git a/src/fetch/main.ts b/src/fetch/main.ts new file mode 100644 index 0000000..0f9ca7c --- /dev/null +++ b/src/fetch/main.ts @@ -0,0 +1,5 @@ +import { MainDataType } from '@/types/main'; + +export const fetchMainData = async (): Promise => { + return fetch('/main').then((response) => response.json()); +}; diff --git a/src/lib/next-auth/index.tsx b/src/lib/next-auth/index.tsx index c35c249..9913ba5 100644 --- a/src/lib/next-auth/index.tsx +++ b/src/lib/next-auth/index.tsx @@ -1,7 +1,7 @@ 'use client'; import { SessionProvider } from 'next-auth/react'; -import React from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import Header from '@/components/header'; import Footer from '@/components/footer'; @@ -11,14 +11,18 @@ interface Props { children: React.ReactNode; } +const queryClient = new QueryClient(); + const AuthProvider = ({ children }: Props) => { return ( - -
- {children} -