From 6b52a8dedbdef1ee59fc50c35b23b1e73db44d00 Mon Sep 17 00:00:00 2001 From: hamster1963 <1410514192@qq.com> Date: Thu, 26 Dec 2024 13:20:49 +0800 Subject: [PATCH] perf: refactor server data fetch --- .../detail/ServerDetailChartClient.tsx | 17 ++--- .../detail/ServerDetailClient.tsx | 24 ++----- app/(main)/ClientComponents/main/Global.tsx | 6 +- .../main/ServerListClient.tsx | 10 +-- .../main/ServerOverviewClient.tsx | 11 ++-- app/(main)/layout.tsx | 5 +- app/lib/server-data-context.tsx | 62 +++++++++++++++++++ 7 files changed, 86 insertions(+), 49 deletions(-) create mode 100644 app/lib/server-data-context.tsx diff --git a/app/(main)/ClientComponents/detail/ServerDetailChartClient.tsx b/app/(main)/ClientComponents/detail/ServerDetailChartClient.tsx index f3aa06b..dc2f7e0 100644 --- a/app/(main)/ClientComponents/detail/ServerDetailChartClient.tsx +++ b/app/(main)/ClientComponents/detail/ServerDetailChartClient.tsx @@ -1,15 +1,15 @@ "use client" -import { NezhaAPISafe, ServerApi } from "@/app/types/nezha-api" +import { useServerData } from "@/app/lib/server-data-context" +import { NezhaAPISafe } from "@/app/types/nezha-api" import { ServerDetailChartLoading } from "@/components/loading/ServerDetailLoading" import AnimatedCircularProgressBar from "@/components/ui/animated-circular-progress-bar" import { Card, CardContent } from "@/components/ui/card" import { ChartConfig, ChartContainer } from "@/components/ui/chart" -import { formatBytes, formatNezhaInfo, formatRelativeTime, nezhaFetcher } from "@/lib/utils" +import { formatBytes, formatNezhaInfo, formatRelativeTime } from "@/lib/utils" import { useTranslations } from "next-intl" import { useEffect, useState } from "react" import { Area, AreaChart, CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts" -import useSWRImmutable from "swr/immutable" type cpuChartData = { timeStamp: string @@ -52,16 +52,9 @@ export default function ServerDetailChartClient({ }) { const t = useTranslations("ServerDetailChartClient") - const { data: allFallbackData } = useSWRImmutable("/api/server", nezhaFetcher) - const fallbackData = allFallbackData?.result?.find((item) => item.id === server_id) + const { data: serverList, error } = useServerData() - const { data, error } = useSWRImmutable( - `/api/detail?server_id=${server_id}`, - nezhaFetcher, - { - fallbackData, - }, - ) + const data = serverList?.result?.find((item) => item.id === server_id) if (error) { return ( diff --git a/app/(main)/ClientComponents/detail/ServerDetailClient.tsx b/app/(main)/ClientComponents/detail/ServerDetailClient.tsx index 2dd87fe..31deca1 100644 --- a/app/(main)/ClientComponents/detail/ServerDetailClient.tsx +++ b/app/(main)/ClientComponents/detail/ServerDetailClient.tsx @@ -1,18 +1,15 @@ "use client" -import { NezhaAPISafe, ServerApi } from "@/app/types/nezha-api" +import { useServerData } from "@/app/lib/server-data-context" import { BackIcon } from "@/components/Icon" import ServerFlag from "@/components/ServerFlag" import { ServerDetailLoading } from "@/components/loading/ServerDetailLoading" import { Badge } from "@/components/ui/badge" import { Card, CardContent } from "@/components/ui/card" -import getEnv from "@/lib/env-entry" -import { cn, formatBytes, nezhaFetcher } from "@/lib/utils" +import { cn, formatBytes } from "@/lib/utils" import { useTranslations } from "next-intl" import { notFound, useRouter } from "next/navigation" import { useEffect, useState } from "react" -import useSWR from "swr" -import useSWRImmutable from "swr/immutable" export default function ServerDetailClient({ server_id }: { server_id: number }) { const t = useTranslations("ServerDetailClient") @@ -39,24 +36,13 @@ export default function ServerDetailClient({ server_id }: { server_id: number }) } } - const { data: allFallbackData, isLoading } = useSWRImmutable( - "/api/server", - nezhaFetcher, - ) - const fallbackData = allFallbackData?.result?.find((item) => item.id === server_id) + const { data: serverList, error, isLoading } = useServerData() + const data = serverList?.result?.find((item) => item.id === server_id) - if (!fallbackData && !isLoading) { + if (!data && !isLoading) { notFound() } - const { data, error } = useSWR(`/api/detail?server_id=${server_id}`, nezhaFetcher, { - refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 5000, - dedupingInterval: 1000, - fallbackData, - revalidateOnMount: false, - revalidateIfStale: false, - }) - if (error) { return ( <> diff --git a/app/(main)/ClientComponents/main/Global.tsx b/app/(main)/ClientComponents/main/Global.tsx index 8e5fb31..52c2294 100644 --- a/app/(main)/ClientComponents/main/Global.tsx +++ b/app/(main)/ClientComponents/main/Global.tsx @@ -1,8 +1,6 @@ "use client" -import { ServerApi } from "@/app/types/nezha-api" -import { nezhaFetcher } from "@/lib/utils" -import useSWRImmutable from "swr/immutable" +import { useServerData } from "@/app/lib/server-data-context" import GlobalLoading from "../../../../components/loading/GlobalLoading" import { geoJsonString } from "../../../../lib/geo-json-string" @@ -11,7 +9,7 @@ import GlobalInfo from "./GlobalInfo" import { InteractiveMap } from "./InteractiveMap" export default function ServerGlobal() { - const { data: nezhaServerList, error } = useSWRImmutable("/api/server", nezhaFetcher) + const { data: nezhaServerList, error } = useServerData() if (error) return ( diff --git a/app/(main)/ClientComponents/main/ServerListClient.tsx b/app/(main)/ClientComponents/main/ServerListClient.tsx index 46e0efb..d94f374 100644 --- a/app/(main)/ClientComponents/main/ServerListClient.tsx +++ b/app/(main)/ClientComponents/main/ServerListClient.tsx @@ -1,18 +1,17 @@ "use client" -import { ServerApi } from "@/app/types/nezha-api" +import { useServerData } from "@/app/lib/server-data-context" import ServerCard from "@/components/ServerCard" import ServerCardInline from "@/components/ServerCardInline" import Switch from "@/components/Switch" import getEnv from "@/lib/env-entry" import { useFilter } from "@/lib/network-filter-context" import { useStatus } from "@/lib/status-context" -import { cn, nezhaFetcher } from "@/lib/utils" +import { cn } from "@/lib/utils" import { MapIcon, ViewColumnsIcon } from "@heroicons/react/20/solid" import { useTranslations } from "next-intl" import dynamic from "next/dynamic" import { useEffect, useRef, useState } from "react" -import useSWR from "swr" import GlobalLoading from "../../../../components/loading/GlobalLoading" @@ -70,10 +69,7 @@ export default function ServerListClient() { } }, []) - const { data, error } = useSWR("/api/server", nezhaFetcher, { - refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000, - dedupingInterval: 1000, - }) + const { data, error } = useServerData() if (error) return ( diff --git a/app/(main)/ClientComponents/main/ServerOverviewClient.tsx b/app/(main)/ClientComponents/main/ServerOverviewClient.tsx index c1d777a..4d9fadc 100644 --- a/app/(main)/ClientComponents/main/ServerOverviewClient.tsx +++ b/app/(main)/ClientComponents/main/ServerOverviewClient.tsx @@ -1,31 +1,30 @@ "use client" -import { ServerApi } from "@/app/types/nezha-api" +import { useServerData } from "@/app/lib/server-data-context" import { Loader } from "@/components/loading/Loader" import { Card, CardContent } from "@/components/ui/card" import getEnv from "@/lib/env-entry" import { useFilter } from "@/lib/network-filter-context" import { useStatus } from "@/lib/status-context" -import { cn, formatBytes, nezhaFetcher } from "@/lib/utils" +import { cn, formatBytes } from "@/lib/utils" import blogMan from "@/public/blog-man.webp" import { ArrowDownCircleIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid" import { useTranslations } from "next-intl" import Image from "next/image" -import useSWRImmutable from "swr/immutable" export default function ServerOverviewClient() { + const { data, error, isLoading } = useServerData() const { status, setStatus } = useStatus() const { filter, setFilter } = useFilter() const t = useTranslations("ServerOverviewClient") - - const { data, error, isLoading } = useSWRImmutable("/api/server", nezhaFetcher) const disableCartoon = getEnv("NEXT_PUBLIC_DisableCartoon") === "true" if (error) { + const errorInfo = error as any return (

- Error status:{error.status} {error.info?.cause ?? error.message} + Error status:{errorInfo?.status} {errorInfo.info?.cause ?? errorInfo?.message}

{t("error_message")}

diff --git a/app/(main)/layout.tsx b/app/(main)/layout.tsx index 9d07edb..0a54532 100644 --- a/app/(main)/layout.tsx +++ b/app/(main)/layout.tsx @@ -1,5 +1,6 @@ import Footer from "@/app/(main)/footer" import Header from "@/app/(main)/header" +import { ServerDataProvider } from "@/app/lib/server-data-context" import { auth } from "@/auth" import { SignIn } from "@/components/SignIn" import getEnv from "@/lib/env-entry" @@ -13,7 +14,9 @@ export default function MainLayout({ children }: DashboardProps) {
- {children} + + {children} +
diff --git a/app/lib/server-data-context.tsx b/app/lib/server-data-context.tsx new file mode 100644 index 0000000..7074b61 --- /dev/null +++ b/app/lib/server-data-context.tsx @@ -0,0 +1,62 @@ +"use client" + +import { ServerApi } from "@/app/types/nezha-api" +import getEnv from "@/lib/env-entry" +import { nezhaFetcher } from "@/lib/utils" +import { ReactNode, createContext, useContext, useEffect, useState } from "react" +import useSWR from "swr" + +interface ServerDataWithTimestamp { + timestamp: number + data: ServerApi +} + +interface ServerDataContextType { + data: ServerApi | undefined + error: Error | undefined + isLoading: boolean + history: ServerDataWithTimestamp[] +} + +const ServerDataContext = createContext(undefined) + +const MAX_HISTORY_LENGTH = 30 + +export function ServerDataProvider({ children }: { children: ReactNode }) { + const [history, setHistory] = useState([]) + + const { data, error, isLoading } = useSWR("/api/server", nezhaFetcher, { + refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000, + dedupingInterval: 1000, + }) + + useEffect(() => { + if (data) { + setHistory((prev) => { + const newHistory = [ + { + timestamp: Date.now(), + data: data, + }, + ...prev, + ].slice(0, MAX_HISTORY_LENGTH) + + return newHistory + }) + } + }, [data]) + + return ( + + {children} + + ) +} + +export function useServerData() { + const context = useContext(ServerDataContext) + if (context === undefined) { + throw new Error("useServerData must be used within a ServerDataProvider") + } + return context +}