← dompdf.js Studio

React 项目集成 dompdf.js:PDF 生成完整指南

React 生态里生成 PDF 一直是个麻烦事:jsPDF 需要手动计算坐标,代码量大且样式还原差,改一次版式就要重写;html2canvas 输出的是位图,文字不可选中、大图还模糊,导出文件也不利于归档检索;Puppeteer 又要额外维护浏览器服务,成本居高不下。dompdf.js 直接渲染 DOM 生成矢量 PDF,文字可选中、中文不乱码、表格和分页都支持,是 React 项目里性价比很高的方案。本文从安装、函数组件集成、自定义 Hook 封装,到样式与分页注意事项,完整演示如何在 React 项目中接入 dompdf.js,帮助你在最短时间内上线稳定可靠的导出功能,并沉淀为团队可复用的基础设施,避免每个页面重复造轮子。

React 中 PDF 导出的需求与选型

报表平台、数据分析工具、电商后台都需要导出 PDF;React 组件化的页面结构天然适合“把某个组件区域渲染成 PDF”的场景,导出内容与页面状态保持一致,数据更新后导出的内容也同步更新,不需要额外同步逻辑,实现起来非常自然。

对比常见方案:jsPDF 坐标绘图开发成本高、维护困难,样式一改就要重写;html2canvas 输出位图,文字不可选且大图会糊,放大打印效果差;dompdf.js 输出矢量 PDF,文字清晰可搜索,效果与成本更均衡,是多数业务场景的优先选择,选型时可以少走弯路,评估成本也低,跑通一个示例就能对比出差距。

dompdf.js 不依赖后端和浏览器插件,npm 安装即可使用,与 React 的虚拟 DOM 配合良好;导出的是渲染完成后的真实 DOM,内容和页面状态天然一致,没有数据同步的烦恼,也没有额外的运维负担,小团队也能轻松维护,无需额外的基础设施投入,对比后端渲染方案成本优势明显。

安装与基本用法

执行 npm install dompdf.js 安装依赖,然后在组件里 import { DomPDF } from 'dompdf.js' 即可开始使用,无需任何额外配置,也不影响现有构建流程,接入成本几乎可以忽略,十分钟内就能跑通第一个示例,快速验证可行性,先写一个最简单的导出按钮验证效果,再逐步加上表格、分页和页眉页脚,迭代路径清晰,风险可控。

核心用法三步:创建 DomPDF 实例、调用 addPage 传入 DOM 节点或 HTML 字符串、调用 save 下载文件;多页文档通过多次 addPage 批量添加,一次 save 输出完整文件,代码非常直观,新人也能快速看懂,接手维护无压力,多页文档就多次 addPage,一次 save 输出完整文件,结构简单,出问题也好定位,团队上手成本很低。

类组件和函数组件都能使用,推荐函数组件配合 useRef 管理导出区域,代码更简洁,也更容易把导出逻辑封装成自定义 Hook 在多个页面间复用,团队协作时规范也更统一,长期维护更省心,代码库也会更整洁,导出入口统一、行为一致,新页面接入时直接复用,不需要重新理解实现。

代码示例:函数组件 + ref 导出

useRef 绑定导出区域,点击按钮时把 ref.current 传给 addPage;React 完成渲染后 DOM 就是最终版式,导出结果与页面所见完全一致,无需额外的同步逻辑,代码直观、容易排查问题,出问题也一眼能定位,调试成本很低,实现非常干净,不需要在组件里维护额外的导出状态,代码心智负担更小。

如果导出内容来自接口数据,要确保数据加载完成后再触发导出,必要时用状态控制按钮禁用,避免在数据未就绪时导出空白文档;加载中给按钮加 loading 态,体验更完整,用户不会误以为功能坏了,反馈也更及时,数据量大的报表导出前先提示用户,减少等待焦虑,体验更完整。

也可以把接口返回的数据拼成 HTML 字符串再导出,适合后端返回结构化数据、前端直接生成 PDF 的场景,与 DOM 导出两种方式按需求二选一即可,两种方式都支持表格和分页,迁移切换也简单,代码改动很小。

import { useRef } from 'react';
import { DomPDF } from 'dompdf.js';

export default function ReportPage() {
  const reportRef = useRef(null);

  const handleExport = () => {
    const pdf = new DomPDF();
    pdf.addPage(reportRef.current, { format: 'A4', margin: '15mm' });
    pdf.save('report.pdf');
  };

  return (
    <div>
      <button onClick={handleExport}>导出 PDF</button>
      <div ref={reportRef}>
        <h1>月度经营报告</h1>
        <table>
          <tbody>
            <tr><td>营收</td><td>120 万</td><td>+8%</td></tr>
          </tbody>
        </table>
      </div>
    </div>
  );
}

封装 usePdfExport Hook

把导出逻辑抽成自定义 Hook,组件里只关心“导出什么”,纸张、边距、文件名等配置统一管理,多页面复用零重复代码,改动一处全局生效,后续升级 PDF 能力也只在 Hook 里进行,影响面可控,升级风险大幅下降,页面之间不会再出现同一功能多种写法的现象,代码评审也轻松很多。

Hook 内部还可以扩展错误处理(try/catch 加用户提示)、导出中状态和进度回调,让调用方的交互体验更完整,而不需要在每个组件里重复实现,也方便统一记录导出日志,排查线上问题时数据更全,定位问题更快,导出异常时能快速定位是配置问题还是内容问题,修复效率高,线上稳定性更好。

配合 TypeScript 使用可以给 source 和 options 定义明确的类型,团队协作时接口更清晰,避免传参错误,重构时也能第一时间发现类型问题,代码质量和可维护性都更有保障,长期收益明显,值得作为团队规范推广,编译期就能拦截传参错误,接口演进更安全,团队协作更高效。

// hooks/usePdfExport.js
import { useCallback } from 'react';
import { DomPDF } from 'dompdf.js';

export function usePdfExport(filename = 'export.pdf') {
  const exportPdf = useCallback((source, options = {}) => {
    const pdf = new DomPDF();
    pdf.addPage(source, { format: 'A4', margin: '15mm', ...options });
    pdf.save(filename);
  }, [filename]);

  return { exportPdf };
}

// 使用:const { exportPdf } = usePdfExport('orders.pdf');

样式与分页注意事项

styled-components、Emotion 等 CSS-in-JS 方案生成的样式以实际样式表存在于 DOM 上,dompdf.js 快照渲染时一般可以正常还原;发现异常时优先检查关键样式是否内联,必要时把核心排版样式写成内联,兼容性最好,问题也最好排查,CSS-in-JS 生成的样式一般能正常还原,遇到异常先检查作用域和优先级,通常都能快速解决。

导出区域建议用独立容器包裹,避免被滚动容器、fixed 元素等全局布局影响;A4 对应 794px 宽度,容器宽度与纸张匹配时分页最稳定,多页文档的断点位置也可预期,打印交付都放心,客户拿到的 PDF 干净整齐,多页报表的分页断点稳定可预期,不会出现内容被裁切的情况,交付质量有保障。

需要导出但不想显示的内容可以放在屏幕外容器或隐藏区域里,导出前再渲染,确保 DOM 完整可用;导出完成后按需清理,避免影响页面布局,也防止无关元素进入 PDF,保证文档内容纯净,交付观感专业。

常见问题

Q: Next.js 里怎么用?A: dompdf.js 依赖浏览器 API,应在 useEffect 或事件回调里调用,避免在服务端渲染阶段执行;用动态 import 还能减小首屏包体,提升页面加载速度,这是 Next.js 集成的关键,按这个模式做不会踩坑,同时用动态 import 按需加载,首屏包体更小,页面加载速度更快,SSR 阶段完全不会执行浏览器代码。

Q: 导出区域包含 Canvas 图表怎么办?A: 先把 canvas 转成 dataURL 图片插入 DOM 再导出,dompdf.js 支持图片嵌入,图表也能完整呈现在 PDF 中,保持清晰锐利,图表报告场景完全够用,实现也就几行代码,导出效果与网页完全一致,图表数据不失真,客户看到的报表更专业。

Q: 与 TypeScript 兼容吗?A: 兼容,包自带类型声明,组件内直接 import 即可获得完整的类型提示,无需额外安装 @types 包,开发体验与普通库一致,接入无障碍,类型安全也有保障,重构时类型错误会在编译期第一时间暴露,接口演进也更安全,团队协作更顺畅。

⚡ 现场演示(点击生成 PDF)

下面的按钮用 dompdf.js 在浏览器端实时生成 PDF,无需后端:

Hello from dompdf.js!

这是由 dompdf.js 渲染的示例 PDF 内容。