You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js + TypeScript 仪表板数据获取优化重构咨询

问题:Next.js Dashboard页面多API并行请求加载缓慢,求重构与优化方案

我是Next.js和TypeScript的新手,当前我的dashboard.tsx页面通过Promise.all并行请求多个API接口,导致页面加载耗时过长,希望对代码进行重构以提升数据获取速度。同时希望得到编码模式的优化建议,后端使用Laravel框架。我曾尝试过React的useMemo,但不确定是否适用于当前需求。

相关代码

dashboard.tsx

import Layout from "@/layouts/layout";
import React, { useContext, useEffect, useState } from "react";
import { LanguageContext } from "@/hooks/Lang/LanguageContext";
import Card from "@/components/Cards/dashboardCard";
import Link from "next/link";
import {
  editData,
  getMultipleData,
  getSingleData,
} from "@/lib/ApiRequestHelper";
import DepositWithdrawTable from "@/components/Table/DepositWithdrawTable";
import PerformanceTable from "@/components/Table/PerformanceTable";
import { checkUserRoles } from "@/lib/checkUserRole";
import Spinner from "@/components/Spinner/Spinner";
import GeneralOutcomeTable from "@/components/Table/GeneralOutcomeTable";

type ReportData = {
  data: {
    amount: number;
    confirmStatus: number;
    id: number;
    reporter: {
      id: number;
      name: string;
    };
    type: string;
    [key: string]: any; // 兼容额外属性
  };
};


const Dashboard = () => {
  const { t } = useContext(LanguageContext);
  const [income, setIncome] = useState<number>(0);
  const [outcome, setOutcome] = useState<number>(0);
  const [regularData, setRegularData] = useState<ReportData[]>([]);
  const [balanceData, setBalanceData] = useState<any[]>([]);
  const [userCount, setUserCount] = useState(0);
  const [reportsCount, setReportsCount] = useState(0);
  const [loading, setLoading] = useState<boolean>(true);
  const [userRole, setUserRole] = useState<string>("");
  const [data, setData] = useState<ReportData[]>([]); // 初始化为空数组

  const fetchData = async () => {
    const [fetchFinancial, fetchUsers, fetchReports, fetchUncheckReports, fetchRegularData, role] = await Promise.all([
      getMultipleData("/calculations"),
      getMultipleData("/users"),
      getMultipleData("/reports"),
      getMultipleData("/uncheck-reports"),
      getMultipleData('/general-outcome'),
      checkUserRoles()
    ]);
    try {

      setUserCount(fetchUsers.data.length);
      setReportsCount(fetchReports.meta.totalItems);
      setIncome(fetchFinancial.data.income);
      setOutcome(fetchFinancial.data.outcome);
      setBalanceData(fetchFinancial.data);
      setData(fetchUncheckReports.data);
      setRegularData(fetchRegularData.data);
      setUserRole(role);
      setLoading(false);

    } catch (error) {
      setLoading(false);
    }
  };

  const handleAccept = async (id: number) => {
    await editData(`/reports/${id}/accept`, "");
    fetchData();
  };

  const handleReject = async (id: number) => {
    await getSingleData(`/reports/${id}/reject`);
    fetchData();
  };

  useEffect(() => {
    fetchData();
  }, []);

  return (
    <Layout>
      <div>
        {loading ? (
          <Spinner />
        ) : (
          <div className="">
            <nav aria-label="breadcrumb" className="w-full p-4 dark:bg-gray-800 dark:text-gray-100 pl-8">
              <ol className="flex h-8 space-x-2 dark:text-gray-100">
                <li className="flex items-center">
                  <Link rel="noopener noreferrer" href="/app/home" title="返回首页" className="flex items-center hover:underline"><i className="fa-solid fa-house" /></Link>
                </li>
                <li className="flex items-center space-x-1">
                  <span className="dark:text-gray-400">/</span>
                  <Link rel="noopener noreferrer" href="/app/dashboard" className="flex items-center px-1 capitalize hover:underline">仪表盘</Link>
                </li>
              </ol>
            </nav>
            <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-3 p-8">
              <div className="w-full sm:w-full md:w-1/4 p-2">
                <Link href="/app/reports">
                  <Card title={t("income")} data={income} />
                </Link>
              </div>
              <div className="w-full sm:w-full md:w-1/4 p-2">
                <Link href="/app/reports">
                  <Card title={t("outcome")} data={outcome} />
                </Link>
              </div>
              <div className="w-full sm:w-full md:w-1/4 p-2">
                <Link href="/app/reports">
                  <Card title={t("reports")} data={reportsCount} />
                </Link>
              </div>
              <div className="w-full sm:w-full md:w-1/4 p-2">
                <Link href="/app/users">
                  <Card title={t("users")} data={userCount} />
                </Link>
              </div>
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-5 p-8">
              <div className="w-full">
                <DepositWithdrawTable
                  onAccept={handleAccept}
                  onReject={handleReject}
                  data={data}
                  userRole={userRole}
                />
              </div>
              <div className="w-full">
                <PerformanceTable
                  balanceData={balanceData}
                />
              </div>
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-5 p-8">
              <div className="w-full">
                <GeneralOutcomeTable data={regularData} userRole={userRole} />
              </div>
            </div>
          </div>
        )}
      </div>
    </Layout>
  );
};

export default Dashboard;

getMultipleData函数

import { callApi } from '@/hooks/callApi'

const defaultRoute = '/api/app';

export async function getMultipleData(url: string) {
  return await callApi('get', defaultRoute + url)
}

解决方案与优化建议

一、API层面优化(Laravel后端)

最有效的提速方式是聚合API请求,避免前端发起6个独立请求:

  • 在Laravel后端新增一个dashboard接口,一次性返回所有仪表盘需要的数据:用户总数、报表总数、收支数据、待审核报表、常规支出数据、用户角色等。
  • 示例后端路由与控制器逻辑:
// routes/api.php
Route::get('/app/dashboard', [DashboardController::class, 'index']);

// app/Http/Controllers/DashboardController.php
public function index(Request $request) {
    $user = $request->user();
    $userCount = User::count();
    $reportsCount = Report::count();
    $financialData = Calculation::first(['income', 'outcome']);
    $uncheckReports = Report::where('confirmStatus', 0)->get();
    $regularOutcome = GeneralOutcome::all();
    $userRole = $user->role;

    return response()->json([
        'user_count' => $userCount,
        'reports_count' => $reportsCount,
        'financial' => $financialData,
        'uncheck_reports' => $uncheckReports,
        'regular_outcome' => $regularOutcome,
        'user_role' => $userRole
    ]);
}

这样前端只需发起1次请求,减少网络握手开销,大幅提升加载速度。

二、前端请求与状态优化

1. 重构请求逻辑

将多个getMultipleData调用替换为单个请求:

// 新增getDashboardData函数
export async function getDashboardData() {
  return await callApi('get', defaultRoute + '/dashboard');
}

// Dashboard组件中的fetchData修改为:
const fetchData = async () => {
  try {
    const response = await getDashboardData();
    const { user_count, reports_count, financial, uncheck_reports, regular_outcome, user_role } = response.data;
    
    setUserCount(user_count);
    setReportsCount(reports_count);
    setIncome(financial.income);
    setOutcome(financial.outcome);
    setBalanceData(financial);
    setData(uncheck_reports);
    setRegularData(regular_outcome);
    setUserRole(user_role);
    setLoading(false);
  } catch (error) {
    setLoading(false);
    // 可添加错误提示逻辑
  }
};

2. 避免全量刷新

当前handleAccept/handleReject操作后会重新请求所有数据,可优化为局部更新状态:

const handleAccept = async (id: number) => {
  await editData(`/reports/${id}/accept`, "");
  // 直接从data中移除已审核的项,避免全量请求
  setData(prev => prev.filter(item => item.data.id !== id));
  // 如果需要更新报表总数,也可以直接减1
  setReportsCount(prev => prev - 1);
};

const handleReject = async (id: number) => {
  await getSingleData(`/reports/${id}/reject`);
  setData(prev => prev.filter(item => item.data.id !== id));
  setReportsCount(prev => prev - 1);
};

3. useMemo的正确使用场景

useMemo适合缓存计算开销大的结果,比如对表格数据的排序、过滤,而非缓存API请求结果。如果你的表格需要频繁处理数据,可以用useMemo缓存处理后的数据:

import { useMemo } from 'react';

// 示例:缓存处理后的待审核报表数据
const processedData = useMemo(() => {
  return data.map(item => ({
    ...item.data,
    formattedAmount: `$${item.data.amount.toFixed(2)}`
  }));
}, [data]);

// 传给组件时用processedData
<DepositWithdrawTable
  onAccept={handleAccept}
  onReject={handleReject}
  data={processedData}
  userRole={userRole}
/>

三、编码模式优化建议

  1. 状态聚合:将分散的状态(income、outcome、userCount等)合并为一个对象状态,减少重复的useState:
const [dashboardState, setDashboardState] = useState({
  income: 0,
  outcome: 0,
  userCount: 0,
  reportsCount: 0,
  balanceData: [],
  data: [],
  regularData: [],
  userRole: ""
});

// 更新时:
setDashboardState(prev => ({
  ...prev,
  user_count: response.data.user_count,
  // 其他字段...
}));
  1. 自定义Hook封装数据请求:将仪表盘数据请求逻辑抽离为自定义Hook,提高复用性和可读性:
// hooks/useDashboardData.ts
import { useState, useEffect } from 'react';
import { getDashboardData } from '@/lib/ApiRequestHelper';

export const useDashboardData = () => {
  const [loading, setLoading] = useState(true);
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetch = async () => {
      try {
        const response = await getDashboardData();
        setData(response.data);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };
    fetch();
  }, []);

  return { loading, data, error };
};

// 在Dashboard组件中使用:
const { loading, data, error } = useDashboardData();
// 解构data中的字段使用
  1. 类型安全优化:替换any类型为明确的TypeScript接口,避免类型错误:
// 定义仪表盘数据接口
interface DashboardData {
  user_count: number;
  reports_count: number;
  financial: {
    income: number;
    outcome: number;
    [key: string]: any;
  };
  uncheck_reports: ReportData[];
  regular_outcome: ReportData[];
  user_role: string;
}

// 状态定义时使用该接口
const [dashboardState, setDashboardState] = useState<DashboardData>({
  // 初始值...
});
  1. 错误处理增强:添加错误提示UI,让用户知道请求失败,而不是只隐藏加载状态:
{loading ? (
  <Spinner />
) : error ? (
  <div className="p-8 text-red-500">数据加载失败,请稍后重试</div>
) : (
  // 正常渲染内容
)}

内容的提问来源于stack exchange,提问作者LwinMoeAung

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 05:54:57