如何在Next.js框架中从SpringBoot后端API获取数据渲染记录表格?
Next.js + SpringBoot 实现数据表格的最优方案
一、后端SpringBoot接口准备
优先提供支持分页、排序的REST接口,这是大数量级数据场景下的性能基础。SpringBoot可通过Pageable自动解析分页参数,返回标准化的分页数据结构。
示例Controller代码:
import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/records") public class RecordController { private final RecordService recordService; public RecordController(RecordService recordService) { this.recordService = recordService; } // 分页获取记录,自动解析page/size/sort参数 @GetMapping public Page<Record> getRecords(Pageable pageable) { return recordService.findAll(pageable); } }
同时配置CORS允许前端访问:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; @Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.addAllowedOrigin("http://localhost:3000"); // 你的Next.js前端地址 config.addAllowedMethod("*"); config.addAllowedHeader("*"); source.registerCorsConfiguration("/api/**", config); return new CorsFilter(source); } }
二、前端Next.js数据获取:用专业工具提升效率
Next.js中推荐用SWR或React Query处理数据请求,它们自带缓存、重新验证、错误处理能力,比原生fetch更高效。这里以SWR为例:
- 安装SWR:
npm install swr
- 封装请求逻辑,处理分页排序参数:
import useSWR from 'swr'; import { useState } from 'react'; const fetcher = async (url) => { const res = await fetch(url); if (!res.ok) throw new Error('请求失败'); return res.json(); }; export default function RecordTable() { // 分页与排序状态 const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); const [sortBy, setSortBy] = useState('id'); const [sortDir, setSortDir] = useState('asc'); // 构造请求URL const url = `http://localhost:8080/api/records?page=${pageIndex}&size=${pageSize}&sort=${sortBy},${sortDir}`; const { data, error, isLoading } = useSWR(url, fetcher); // 状态反馈 if (isLoading) return <div>加载中...</div>; if (error) return <div>获取数据失败:{error.message}</div>; // 表格渲染逻辑(后续结合表格库实现) return <div className="table-wrapper"></div>; }
如果有SEO需求,用getServerSideProps做服务器端渲染:
export async function getServerSideProps(context) { const { page = 0, size = 10 } = context.query; const res = await fetch(`http://localhost:8080/api/records?page=${page}&size=${size}`); const data = await res.json(); return { props: { data } }; } export default function RecordTable({ data }) { // 直接使用服务端获取的数据渲染 return <div className="table-wrapper"></div>; }
三、表格渲染:用高性能可定制的库
推荐TanStack Table(原React Table),它轻量无UI依赖,支持分页、排序、过滤、虚拟滚动等核心功能,性能远超原生table。
- 安装TanStack Table:
npm install @tanstack/react-table
- 结合SWR实现完整表格:
import useSWR from 'swr'; import { useState, useEffect } from 'react'; import { useTable, usePagination, useSortBy } from '@tanstack/react-table'; const fetcher = async (url) => { const res = await fetch(url); if (!res.ok) throw new Error('请求失败'); return res.json(); }; // 定义表格列 const columns = [ { accessorKey: 'id', header: 'ID', enableSorting: true }, { accessorKey: 'name', header: '名称', enableSorting: true }, { accessorKey: 'createTime', header: '创建时间', enableSorting: true } ]; export default function RecordTable() { const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); const [sortInfo, setSortInfo] = useState({ sortBy: 'id', sortDir: 'asc' }); const url = `http://localhost:8080/api/records?page=${pageIndex}&size=${pageSize}&sort=${sortInfo.sortBy},${sortInfo.sortDir}`; const { data, error, isLoading } = useSWR(url, fetcher); if (isLoading) return <div>加载中...</div>; if (error) return <div>获取数据失败:{error.message}</div>; // 配置表格实例 const table = useTable({ columns, data: data.content, pageCount: Math.ceil(data.totalElements / pageSize), initialState: { pagination: { pageIndex, pageSize } } }, useSortBy, usePagination); // 同步表格状态到前端 useEffect(() => { const pagination = table.getState().pagination; setPageIndex(pagination.pageIndex); setPageSize(pagination.pageSize); const sort = table.getState().sorting[0]; if (sort) { setSortInfo({ sortBy: sort.id, sortDir: sort.desc ? 'desc' : 'asc' }); } }, [table.getState()]); return ( <div className="table-container"> <table border="1"> <thead> {table.getHeaderGroups().map(headerGroup => ( <tr key={headerGroup.id}> {headerGroup.headers.map(column => ( <th key={column.id} onClick={column.getSortHandler()}> {column.render('header')} {column.getIsSorted() ? (column.getIsSorted() === 'desc' ? ' ↓' : ' ↑') : ''} </th> ))} </tr> ))} </thead> <tbody> {table.getRowModel().rows.map(row => ( <tr key={row.id}> {row.getVisibleCells().map(cell => ( <td key={cell.id}>{cell.render('cell')}</td> ))} </tr> ))} </tbody> </table> {/* 分页控件 */} <div className="pagination"> <button onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>上一页</button> <span>第 {pageIndex + 1} 页 / 共 {table.getPageCount()} 页</span> <button onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>下一页</button> <select value={pageSize} onChange={(e) => table.setPageSize(Number(e.target.value))}> {[10, 20, 50].map(size => <option key={size} value={size}>{size} 条/页</option>)} </select> </div> </div> ); }
四、关键优化点
- 分页优先:永远不要一次性请求全量数据,通过后端分页+前端控件,只加载当前页数据
- 缓存复用:SWR/React Query会缓存请求结果,用户切换页面返回时直接复用缓存
- 虚拟滚动:单页数据超200条时,用
@tanstack/react-virtual插件实现虚拟滚动,只渲染可视区域行 - 搜索防抖:实现搜索功能时,对输入做500ms防抖处理,避免频繁调用后端
- 状态反馈:始终处理加载、错误状态,给用户明确提示,避免页面空白
内容的提问来源于stack exchange,提问作者Avinash Pandey
相关产品推荐
相关产品推荐

