免写 Recharts 模板:在 Next.js 应用中集成 shadcn 图表
在工单上看,图表好像是个小活儿。可一旦打开 Recharts 文档,你就会想起每个图表都需要多少配置:标签和颜色的配置对象、坐标轴、tooltip、图例、暗色模式下也不违和的配色,还有一个能正确自适应大小的容器。大多数人的做法是把上个项目里的代码整个复制过来,改改变量名。
shadcn/ui 的图表组件能省掉一部分工作,它在 Recharts 外面包了一层,自带主题配色和 tooltip。但每个图表你还是得手写。
于是我做了 ChartCN,一个免费开源的 shadcn 图表生成器。粘贴数据、选好图表类型,就能复制一个 TSX 文件,它只依赖 shadcn/ui 图表组件和 Recharts。项目采用 MIT 协议,不需要注册账号,也不用装额外的包。
本教程会带你在全新的 Next.js 应用里添加一个收入与支出对比的柱状图。我们会逐段讲解生成代码的作用,然后把图表接入在 Server Component 中加载的数据。这个工具帮你省去了打字的功夫,但第 5 步和第 6 步适用于任何 shadcn/ui 图表,不管它是生成的还是你自己写的。
前置要求:你需要熟悉 React 并安装了 Node.js。对 Next.js App Router 有基本了解会更有帮助,但每一步都有详细说明。
目录
你将构建什么
读完本文,你会有两个页面:
/路由展示一个分组条形图,数据直接写在组件里。这是最快让图表上屏的方式。/live路由展示同样的图表,但数据来自服务器端的异步函数。接入真实数据库或 API 时用这个版本。
该图表带有渐变条形、紧凑的坐标轴标签(如 40K 代替 40000)、图例,以及显示当月总额和各系列占比的提示框。
第一步:创建 Next.js 应用并初始化 shadcn/ui
新建应用并进入目录:
npx create-next-app@latest my-charts-app
cd my-charts-app
接受推荐的默认选项。你需要 TypeScript、Tailwind CSS 和 App Router。
接着,初始化 shadcn/ui:
npx shadcn@latest init
这条命令会完成三项你后续会依赖的工作:
创建
components.json,告知 shadcn/ui CLI 组件的存放位置。添加
lib/utils.ts,其中包含cn()类名合并助手函数。在
app/globals.css中写入主题变量,包括五种图表颜色:--chart-1到--chart-5,并区分浅色和深色模式。
这五个图表变量至关重要。每个生成的图表都会使用它们,因此要修改整个应用的图表颜色,只需编辑一行 CSS。
第二步:添加 shadcn/ui 图表组件
现在添加图表组件:
npx shadcn@latest add chart
这会安装 recharts 并创建 components/ui/chart.tsx。该文件导出了生成代码中会看到的构建模块:
ChartContainer封装了 Recharts 的ResponsiveContainer,使图表填满父容器。ChartConfig是用于映射每个数据键到标签和颜色的对象类型。ChartTooltip、ChartTooltipContent、ChartLegend和ChartLegendContent是 Recharts 提示框和图例的样式化版本。
继续之前,先检查一下当前安装的 Recharts 版本:
npm ls recharts
ChartCN 生成的代码适配的是 Recharts 3。如果显示的是 2.x 版本,请升级:
npm install recharts@latest
要点: shadcn/ui 图表组件只是 Recharts 的一层薄封装,它并非取代 Recharts,而是为你的 Recharts 图表应用主题样式。
第 3 步:在 ChartCN 中生成图表组件
打开 ChartCN 条形图页面。数据面板包含三个选项卡:粘贴数据、上传文件和编辑表格。在粘贴数据选项卡中填入以下 CSV 内容:
Month,Revenue,Expenses
Jan,42000,31000
Feb,58000,34000
Mar,51000,29000
Apr,67000,38000
May,72000,41000
Jun,69000,37000
ChartCN 会将第一列视为分类轴(条形图的 x 轴),其余列视为数值系列。JSON、TSV 及 Markdown 表格格式同样适用,系统会自动识别格式。
在预览区上方设置以下选项:
布局: 分组
提示框: 细分数据
代码: 内联数据
然后点击预览下方 chart.tsx 面板中的 复制组件。
第 4 步:将图表添加到页面
在 components/revenue-chart.tsx 新建文件,并粘贴复制的代码。
不要将其保存为 components/ui/chart.tsx。虽然 ChartCN 输出的文件名为 chart.tsx,但该路径已存放第 2 步中的shadcn 组件,覆盖它会破坏应用中的所有图表。
生成的组件始终导出为 Chart,因此在引入时应使用更清晰的命名。请用以下内容替换 app/page.tsx:
// app/page.tsx
import { Chart as RevenueChart } from "@/components/revenue-chart"
export default function Home() {
return (
<main className="mx-auto max-w-3xl p-8">
<h1 className="mb-6 text-2xl font-semibold">Revenue vs. expenses</h1>
<RevenueChart />
</main>
)
}
运行 npm run dev,打开 http://localhost:3000,就能看到图表了。
要点:生成的图表其实就是项目里的一个普通组件,不需要任何配置,也没有需要同步维护的依赖包。
第 5 步:读懂生成的代码
这个文件现在归你了,有必要搞清楚每个部分的作用。下面是关键代码(有删减)。
文件开头是一条指令和一些导入:
// components/revenue-chart.tsx
"use client"
import { useId } from "react"
import { Bar, BarChart, CartesianGrid, Rectangle, XAxis, YAxis } from "recharts"
import {
type ChartConfig,
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
} from "@/components/ui/chart"
Recharts 需要测量 DOM 并处理鼠标事件,所以图表必须是 Client Component。而 app/page.tsx 只负责渲染图表,可以继续作为 Server Component。
接下来是数据和配置:
const data = [
{ Month: "Jan", Revenue: 42000, Expenses: 31000 },
{ Month: "Feb", Revenue: 58000, Expenses: 34000 },
// ...
]
const chartConfig = {
Revenue: { label: "Revenue", color: "var(--chart-1)" },
Expenses: { label: "Expenses", color: "var(--chart-2)" },
} satisfies ChartConfig
chartConfig 的键必须与数据中的键一致。ChartContainer 会读取这份配置,并为每个键创建一个仅作用于当前图表的 CSS 变量:--color-Revenue 和 --color-Expenses。这些变量指向主题色,所以深色模式下图表会自动切换配色。
图表本身正是使用了这些变量:
<ChartContainer config={chartConfig} className="aspect-auto h-[350px] w-full">
<BarChart accessibilityLayer data={data} barCategoryGap="30%" barGap={4}>
{/* ...gradient <defs>, grid, axes, tooltip, legend... */}
<Bar
dataKey="Revenue"
fill="var(--color-Revenue)"
shape={(props) => <Rectangle {...props} fill={`url(#${uid}-fill-0)`} />}
radius={[6, 6, 0, 0]}
maxBarSize={36}
/>
</BarChart>
</ChartContainer>
有几个细节值得注意:
高度由
ChartContainer上的h-[350px]设定。如需调整,在此处修改。渐变效果由
shape属性绘制,而fill保持为纯色。因此,图例和 tooltip 中的圆点显示的是纯色而非渐变。uid来自useId(),确保在同一页面渲染多个图表时,渐变 ID 保持唯一。
该文件还包含一个约 70 行的 ChartBreakdownTooltip 组件,用于显示总计及每个系列的百分比。如果你更倾向于使用标准的 shadcn tooltip,请在复制前在 ChartCN 中选择 Tooltip: Simple。这样整个文件的行数将从约 120 行缩减到约 50 行。
总结:在 shadcn/ui 图表中,颜色通过主题变量经由 chartConfig 流向 --color-<key> 变量。理解这一路径后,你就可以手动编辑任何图表了。
Step 6: 将实时数据作为 Prop 传入
对于演示而言,硬编码数据没问题。对于实际数据,请回到 ChartCN,将 Code 模式切换为 Data as prop,然后再次复制。将此版本保存为 components/revenue-chart-live.tsx。
图表标记不变。文件顶部有所变化:data 数组被类型取代,组件将 data 接受为 prop:
// components/revenue-chart-live.tsx
export type ChartRow = { Month: string; Revenue: number | null; Expenses: number | null }
export interface ChartProps {
data: ChartRow[]
}
// ...chartConfig 未变...
export function Chart({ data }: ChartProps) {
// ...与前相同的 JSX
}
序列的类型被设为 `number | null`,因为实际数据中可能存在空缺。值为 `null` 时,图表会显示为缺失的柱状,而非零值柱状。
接下来,创建一个在服务器端加载数据的函数:
// lib/get-revenue.ts
import type { ChartRow } from "@/components/revenue-chart-live"
export async function getRevenue(): Promise<ChartRow[]> {
// 请将此处替换为实际的数据库查询或 API 调用。
// 例如:const res = await fetch("https://api.example.com/revenue")
return [
{ Month: "Jan", Revenue: 42000, Expenses: 31000 },
{ Month: "Feb", Revenue: 58000, Expenses: 34000 },
{ Month: "Mar", Revenue: 51000, Expenses: 29000 },
{ Month: "Apr", Revenue: 67000, Expenses: 38000 },
{ Month: "May", Revenue: 72000, Expenses: null },
]
}
然后在 Server Component 页面中调用它:
// app/live/page.tsx
import { Chart as RevenueChart } from "@/components/revenue-chart-live"
import { getRevenue } from "@/lib/get-revenue"
export default async function LivePage() {
const data = await getRevenue()
return (
<main className="mx-auto max-w-3xl p-8">
<h1 className="mb-6 text-2xl font-semibold">Revenue vs. expenses</h1>
<RevenueChart data={data} />
</main>
)
}
打开 `http://localhost:3000/live`。你会看到五月份只显示了 Revenue 柱,旁边没有 Expenses 柱,因为该值为 `null`。
该页面在服务器端获取数据,仅将数据行发送到客户端组件。数据库凭据和 API 密钥因此保留在服务器端。当你替换为真实的 `fetch` 或数据库调用时,请查阅 Next.js 数据获取文档 以控制数据刷新频率。
核心要点:在 Server Component 中加载数据,在 Client Component 中渲染。导出的 `ChartRow` 类型可让 TypeScript 校验数据是否符合图表的预期结构。
常见问题及解决方法
类型错误,如 "Property 'itemSorter' does not exist"。这表明项目中安装的是 Recharts 2。运行 `npm install recharts@latest` 即可升级至 Recharts 3。柱子是黑色的,图例的圆点不见了。你的 app/globals.css 里没有定义 --chart-1 到 --chart-5。重新运行 npx shadcn@latest init,或者从 shadcn/ui 主题文档中复制这些图表变量。
粘贴一个图表后所有图表都坏了。你很可能是把生成的文件覆盖保存到了 components/ui/chart.tsx。用 npx shadcn@latest add chart --overwrite 恢复它,并把你的图表换个名字保存。
同一页面的两个图表重名。每个生成的组件都导出为 Chart,导入时重命名即可,比如 import { Chart as SignupsChart } from "@/components/signups-chart"。
总结
shadcn/ui 的图表组件是对 Recharts 的主题化封装。它不替代 Recharts,而且 ChartCN 的输出需要 Recharts 3。
颜色来自 CSS 变量。
--chart-1通过chartConfig映射为--color-Revenue,所以暗色模式无需额外代码就能正常工作。生成的图表要用独立的文件名保存。
components/ui/chart.tsx归 shadcn/ui 所有。真实数据请用 Data 作为 prop。在 Server Component 中加载数据,再把带类型的行数据传给图表。
null表示“无数据”。空缺会渲染为空缺,而不是 0。
ChartCN 是一个 shadcn 图表生成器而非组件库,所以没有需要安装和维护更新的 ChartCN 包。你可以浏览它支持的全部 13 种 shadcn 图表,包括折线图、面积图、饼图、雷达图、KPI 卡片、瀑布图和热力图,它们都遵循本文同样的「粘贴、选择、复制」流程。
源码托管在 GitHub 上,采用 MIT 许可证。如果对你有帮助,点个 star 能让更多开发者发现它。