在生产环境中测试,无水印。
随时随地满足您的需求。
获得30天的全功能产品。
几分钟内就能启动并运行。
在您的产品试用期间,全面访问我们的支持工程团队。
欢迎来到从 React 应用程序创建 PDF 文档的教程! 在本教程中,我们将探讨生成 PDF 的各种库,并学习如何使用流行的 jsPDF 库直接从 React 组件创建 PDF 文件。 那么,让我们开始吧!
PDF(便携式文档格式)是一种广泛使用的文件格式,用于共享和打印文档,同时保持其布局和格式。 作为 React Web 开发人员,您可能会遇到需要直接从 React 应用程序生成 PDF 文档(如发票、报告或销售合同)的情况。
在 React 应用程序中创建 PDF 文档可能是一项艰巨的任务,尤其是对于新手而言。 值得庆幸的是,我们可以使用几个第三方库来大大简化这一过程。 每个库都有自己独特的功能和实用程序,可满足不同的使用情况。 让我们来详细了解一下这些库。
jsPDF 是一个广受开发人员欢迎的库,用于从 JavaScript 生成 PDF 文件。 其主要卖点之一是简单易懂。 它的语法和用法都很简单明了,可以让您立即将 HTML 内容转换为 PDF 文件。
jsPDF 是一个强大的解决方案,可在浏览器和服务器环境中运行,是各种 JavaScript 应用程序的绝佳选择。
pdfmake 是一个纯 JavaScript 的客户端/服务器端 PDF 打印解决方案。 该库具有全面的 API 和灵活的布局选项,是创建更复杂 PDF 的绝佳选择。 使用 pdfmake,您可以使用简单的 JavaScript 对象定义文档内容和结构,然后将其转换为有效的 PDF 文档。
React-PDF 是一个独特的库,为使用 React 组件创建 PDF 文件提供了强大的功能。 您无需在 JavaScript 对象中手动编写文档结构,而是可以像创建典型的 React 应用程序一样使用可重复使用的组件和道具来创建 PDF。 IronPDF 网站有一个使用 React-PDF 库创建 PDF 的教程。
虽然这三个库都提供了在 React 中生成 PDF 文档的强大工具,但由于 jsPDF 的简单性、灵活性和在社区中的广泛采用,我们将在本教程中使用 jsPDF。 它为初学者提供了较低的入门门槛,其强大的功能集使其成为许多使用案例的合适选择。 我们将探讨的 jsPDF 原理将为您生成 PDF 打下坚实的基础,如果您的项目需要,您还可以更轻松地使用其他库。
在我们深入学习本教程之前,有必要确保您已充分掌握必要的工具和知识,以便顺利地学习。 本教程的前提条件如下:
首先,您应该对 React 有基本的了解,React 是一种流行的 JavaScript 库,用于构建用户界面,尤其是单页面应用程序。 您应该熟悉 React 中的概念,如 JSX(JavaScript XML)、组件、状态和属性(props)。
您还应在计算机上设置一个用于构建 React 应用程序的开发环境。 这包括一个文本编辑器或集成开发环境 (IDE)。 Visual Studio Code、Atom 或 Sublime Text 等文本编辑器都是不错的选择。
为了管理我们的项目及其依赖性,我们将使用Node.js和npm(Node包管理器)。 确保您的计算机上安装了 Node.js。 Node.js 是一种 JavaScript 运行时,允许我们在服务器上运行 JavaScript。 它已安装 npm,因此您可以管理项目所需的库。
您可以通过运行以下终端命令来检查 Node.js 和 npm 是否已安装:
node -v
npm -vnode -v
npm -v这些命令将分别显示您系统上安装的 Node.js 和 npm 版本。 如果您没有安装它们,或您的版本已过时,您应该从他们的官方下载页面下载并安装最新的长期支持 (LTS) 版本的 Node.js。
让我们从设置 React 项目开始。 打开终端,导航到您要创建项目的目录。 运行以下命令创建一个新的 React 应用程序:
npx create-react-app pdf-from-reactnpx create-react-app pdf-from-react
此命令将创建一个名为pdf-from-react的新目录,其中包含基本的React项目结构。
接下来,导航到项目目录:
cd pdf-from-reactcd pdf-from-react现在,我们可以在代码编辑器中打开该项目,并继续执行。
首先,我们需要安装必要的软件包。 使用以下终端命令安装react、react-dom、@mui/material和jspdf。
npm install jspdf @mui/material @emotion/react @emotion/styled @mui/icons-materialnpm install jspdf @mui/material @emotion/react @emotion/styled @mui/icons-material首先,我们要为应用程序导入必要的依赖项。 这些包括来自Material-UI库的各种组件、用于生成PDF的jsPDF库以及样式工具。
import React, { useState } from "react";
import "./App.css";
import {
Button,
TextField,
Box,
Container,
Typography,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Snackbar,
Alert,
} from "@mui/material";
import Grid from "@mui/material/Grid";
import DeleteIcon from "@mui/icons-material/Delete";
import jsPDF from "jspdf";
import { styled } from "@mui/material/styles";
import { tableCellClasses } from "@mui/material/TableCell";jsx为了在我们的应用中添加一致的跨浏览器行为,我们使用了 MUI 库中的styled实用工具来创建StyledTableCell和StyledTableRow。
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white,
},
[`&.${tableCellClasses.body}`]: {
fontSize: 14,
},
}));
const StyledTableRow = styled(TableRow)(({ theme }) => ({
"&:nth-of-type(odd)": {
backgroundColor: theme.palette.action.hover,
},
"&:last-child td, &:last-child th": {
border: 0,
},
}));jsx我们应用程序的主要组件是App组件。 我们有四个状态变量:customerName 和 customerAddress 用于跟踪客户的数据,items 用于跟踪发票中的项目列表,error 用于在必要时显示错误信息。
function App() {
// State variables
const [customerName, setCustomerName] = useState("");
const [customerAddress, setCustomerAddress] = useState("");
const [items, setItems] = useState([{ name: "", quantity: "", price: "" }]);
const [error, setError] = useState(false);jsx在该代码块中,我们定义了处理用户交互的函数:更改项目详情、添加新项目和删除项目。 handleItemChange 函数在用户修改项目属性时更新项目的属性。 addItem函数将新项目添加到列表中。deleteItem函数从列表中删除一个项目。
const handleItemChange = (index, event) => {
let newItems = [...items];
newItems[index][event.target.name] = event.target.value;
setItems(newItems);
};
const addItem = () => {
setItems([...items, { name: "", quantity: "", price: "" }]);
};
const deleteItem = (index) => {
let newItems = [...items];
newItems.splice(index, 1);
setItems(newItems);
};jsx以下是generateInvoice函数代码:
// Generate invoice
const generateInvoice = () => {
// Validate the input fields
if (
!customerName
!customerAddress
items.some((item) => !item.name
!item.quantity
!item.price)
) {
setError(true);
return;
}
// Create a new jsPDF instance
let doc = new jsPDF("p", "pt");
// Add invoice header
doc.setFontSize(24);
doc.text("Invoice", 40, 60);
doc.setFontSize(10);
doc.text("Invoice Number: 123456", 40, 90);
doc.text("Date: " + new Date().toDateString(), 40, 110);
doc.text(`Customer Name: ${customerName}`, 40, 130);
doc.text(`Customer Address: ${customerAddress}`, 40, 150);
// Add items section
doc.setFontSize(14);
doc.text("Items:", 40, 200);
doc.line(40, 210, 550, 210);
// Add item details
doc.setFontSize(12);
let yOffset = 240;
let total = 0;
items.forEach((item) => {
let itemTotal = item.quantity * item.price;
total += itemTotal;
doc.text(`Item: ${item.name}`, 40, yOffset);
doc.text(`Quantity: ${item.quantity}`, 200, yOffset);
doc.text(`Price: $${item.price}`, 300, yOffset);
doc.text(`Total: $${itemTotal}`, 400, yOffset);
yOffset += 20;
});
// Add total
doc.line(40, yOffset, 550, yOffset);
doc.setFontSize(14);
doc.text(`Total: $${total}`, 400, yOffset + 30);
// Save the generated PDF as "invoice.pdf"
doc.save("invoice.pdf");
// Reset error state
setError(false);
};jsx在generateInvoice函数中,我们首先对输入字段进行验证,以确保填写了客户姓名、客户地址和项目详细信息。 如果这些字段中的任何一个为空,我们将error状态设为true并提前返回。
接下来,我们通过调用new jsPDF("p", "pt")创建一个新的jsPDF实例。 第一个参数“p”指定页面方向为纵向,第二个参数“pt”指定测量单位为点。
然后,我们开始将内容添加到 PDF 文档中。 我们使用doc.setFontSize设置字体大小,并使用doc.text方法在页面上的特定坐标添加文本。 我们添加了发票抬头,包括标题、发票号码、日期、客户名称和客户地址。
在标题之后,我们通过设置字体大小并使用 doc.line 添加一条线来添加“项目”部分,以将项目与标题分开。 接下来,我们遍历items数组中的每一项,通过将数量与价格相乘来计算每项的总价。我们更新total变量,使其等于所有项总价的和。
对于每个项目,我们使用doc.text将项目名称、数量、价格和项目总计添加到PDF文档中。 我们增加yOffset变量,以便为每个项目移动到下一行。 最后,我们添加一条线来将项目与总计分开,并使用doc.text在文档的右下角添加总金额。
一旦内容添加完成,我们使用doc.save("invoice.pdf")将生成的PDF保存为用户计算机上的“invoice.pdf”。 最后,我们将error状态重置为false,以清除任何先前的验证错误。
return语句包含处理渲染过程的JSX代码。 它包括客户姓名和地址的输入字段、用于输入项目详细信息的表格、用于添加项目和生成发票的按钮,以及用于显示验证错误的错误提示栏。
它使用来自Material-UI库的组件,例如Button、TextField、Box、Container、Typography、Table、TableBody、TableCell、TableContainer、TableHead、TableRow、Paper、IconButton、Snackbar和Alert,以创建基本组件。 这些组件用于创建表单字段、表格、按钮和错误通知。
return (
<Container maxWidth="md">
<Box sx={{ my: 4 }}>
<Typography variant="h3" component="h1" gutterBottom>
Create Invoice
</Typography>
{/* Customer Name and Address fields */}
<Grid container spacing={3}>
<Grid item xs={6}>
<TextField
label="Customer Name"
fullWidth
margin="normal"
value={customerName}
onChange={(e) => setCustomerName(e.target.value)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label="Customer Address"
fullWidth
margin="normal"
value={customerAddress}
onChange={(e) => setCustomerAddress(e.target.value)}
/>
</Grid>
</Grid>
{/* Items table */}
<TableContainer component={Paper}>
<Table sx={{ minWidth: 700 }} aria-label="invoice table">
<TableHead>
<TableRow>
<StyledTableCell>Item Name</StyledTableCell>
<StyledTableCell align="left">Quantity</StyledTableCell>
<StyledTableCell align="left">Price</StyledTableCell>
<StyledTableCell align="left">Action</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((item, index) => (
<StyledTableRow key={index}>
<StyledTableCell component="th" scope="row">
<TextField
fullWidth
value={item.name}
onChange={(event) => handleItemChange(index, event)}
name="name"
/>
</StyledTableCell>
<StyledTableCell align="right">
<TextField
fullWidth
value={item.quantity}
onChange={(event) => handleItemChange(index, event)}
name="quantity"
/>
</StyledTableCell>
<StyledTableCell align="right">
<TextField
fullWidth
value={item.price}
onChange={(event) => handleItemChange(index, event)}
name="price"
/>
</StyledTableCell>
<StyledTableCell align="right">
<IconButton onClick={() => deleteItem(index)}>
<DeleteIcon />
</IconButton>
</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>
{/* Buttons */}
<Box mt={2} display="flex" gap={2}>
<Button variant="contained" onClick={addItem}>
Add Item
</Button>
<Button variant="outlined" color="success" onClick={generateInvoice}>
Generate Invoice
</Button>
</Box>
</Box>
{/* Error Snackbar */}
<Snackbar
open={error}
autoHideDuration={6000}
onClose={() => setError(false)}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
>
<Alert onClose={() => setError(false)} severity="error">
Please fill in all required fields.
</Alert>
</Snackbar>
</Container>
);jsx以下是完整的 App.js 代码,您可以复制并粘贴到您的项目中:
import React, { useState } from "react";
import "./App.css";
import {
Button,
TextField,
Box,
Container,
Typography,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Snackbar,
Alert,
} from "@mui/material";
import Grid from "@mui/material/Grid";
import DeleteIcon from "@mui/icons-material/Delete";
import jsPDF from "jspdf";
import { styled } from "@mui/material/styles";
import { tableCellClasses } from "@mui/material/TableCell";
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white,
},
[`&.${tableCellClasses.body}`]: {
fontSize: 14,
},
}));
const StyledTableRow = styled(TableRow)(({ theme }) => ({
"&:nth-of-type(odd)": {
backgroundColor: theme.palette.action.hover,
},
"&:last-child td, &:last-child th": {
border: 0,
},
}));
function App() {
// State variables
const [customerName, setCustomerName] = useState("");
const [customerAddress, setCustomerAddress] = useState("");
const [items, setItems] = useState([{ name: "", quantity: "", price: "" }]);
const [error, setError] = useState(false);
// Event handler for item changes
const handleItemChange = (index, event) => {
let newItems = [...items];
newItems[index][event.target.name] = event.target.value;
setItems(newItems);
};
// Add new item to the list
const addItem = () => {
setItems([...items, { name: "", quantity: "", price: "" }]);
};
// Delete an item from the list
const deleteItem = (index) => {
let newItems = [...items];
newItems.splice(index, 1);
setItems(newItems);
};
// Generate invoice
const generateInvoice = () => {
// Validate the input fields
if (
!customerName
!customerAddress
items.some((item) => !item.name
!item.quantity
!item.price)
) {
setError(true);
return;
}
// Create a new jsPDF instance
let doc = new jsPDF("p", "pt");
// Add invoice header
doc.setFontSize(24);
doc.text("Invoice", 40, 60);
doc.setFontSize(10);
doc.text("Invoice Number: 123456", 40, 90);
doc.text("Date: " + new Date().toDateString(), 40, 110);
doc.text(`Customer Name: ${customerName}`, 40, 130);
doc.text(`Customer Address: ${customerAddress}`, 40, 150);
// Add items section
doc.setFontSize(14);
doc.text("Items:", 40, 200);
doc.line(40, 210, 550, 210);
// Add item details
doc.setFontSize(12);
let yOffset = 240;
let total = 0;
items.forEach((item) => {
let itemTotal = item.quantity * item.price;
total += itemTotal;
doc.text(`Item: ${item.name}`, 40, yOffset);
doc.text(`Quantity: ${item.quantity}`, 200, yOffset);
doc.text(`Price: $${item.price}`, 300, yOffset);
doc.text(`Total: $${itemTotal}`, 400, yOffset);
yOffset += 20;
});
// Add total
doc.line(40, yOffset, 550, yOffset);
doc.setFontSize(14);
doc.text(`Total: $${total}`, 400, yOffset + 30);
// Save the generated PDF as "invoice.pdf"
doc.save("invoice.pdf");
// Reset error state
setError(false);
};
return (
<Container maxWidth="md">
<Box sx={{ my: 4 }}>
<Typography variant="h3" component="h1" gutterBottom>
Create Invoice
</Typography>
{/* Customer Name and Address fields */}
<Grid container spacing={3}>
<Grid item xs={6}>
<TextField
label="Customer Name"
fullWidth
margin="normal"
value={customerName}
onChange={(e) => setCustomerName(e.target.value)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label="Customer Address"
fullWidth
margin="normal"
value={customerAddress}
onChange={(e) => setCustomerAddress(e.target.value)}
/>
</Grid>
</Grid>
{/* Items table */}
<TableContainer component={Paper}>
<Table sx={{ minWidth: 700 }} aria-label="invoice table">
<TableHead>
<TableRow>
<StyledTableCell>Item Name</StyledTableCell>
<StyledTableCell align="left">Quantity</StyledTableCell>
<StyledTableCell align="left">Price</StyledTableCell>
<StyledTableCell align="left">Action</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((item, index) => (
<StyledTableRow key={index}>
<StyledTableCell component="th" scope="row">
<TextField
fullWidth
value={item.name}
onChange={(event) => handleItemChange(index, event)}
name="name"
/>
</StyledTableCell>
<StyledTableCell align="right">
<TextField
fullWidth
value={item.quantity}
onChange={(event) => handleItemChange(index, event)}
name="quantity"
/>
</StyledTableCell>
<StyledTableCell align="right">
<TextField
fullWidth
value={item.price}
onChange={(event) => handleItemChange(index, event)}
name="price"
/>
</StyledTableCell>
<StyledTableCell align="right">
<IconButton onClick={() => deleteItem(index)}>
<DeleteIcon />
</IconButton>
</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>
{/* Buttons */}
<Box mt={2} display="flex" gap={2}>
<Button variant="contained" onClick={addItem}>
Add Item
</Button>
<Button variant="outlined" color="success" onClick={generateInvoice}>
Generate Invoice
</Button>
</Box>
</Box>
{/* Error Snackbar */}
<Snackbar
open={error}
autoHideDuration={6000}
onClose={() => setError(false)}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
>
<Alert onClose={() => setError(false)} severity="error">
Please fill in all required fields.
</Alert>
</Snackbar>
</Container>
);
}
export default App;jsx这是App.css代码:
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: bold;
/* This is the weight for bold in Poppins */
color: #FF6347;
/* This is the color Tomato. Replace with your preferred color */
}
body {
font-family: 'Poppins', sans-serif;
background-color: #E9F8F4;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}要测试 PDF 生成功能,请在终端运行以下命令:
npm startnpm start这将启动开发服务器,您可以在浏览器中访问应用程序,网址为http://localhost:3000。

在输入字段中填写客户姓名、地址和项目详情,然后单击 "生成发票 "按钮。 PDF 文件将下载到您的电脑,您可以打开它查看生成发票的全页视图。

点击 "生成发票 "按钮后,将生成 PDF 文件。

如果您尝试生成带有任何空字段的 PDF,右上角将显示错误信息。

IronPDF for Node.js 是一个全面的 Node.js PDF 库,以高精确性、易用性和速度见长。 它提供了大量功能,可直接从 React 中的 HTML、URL 和图像生成、编辑和格式化 PDF。 IronPDF 支持各种平台,包括 Windows、MacOS、Linux、Docker 以及 Azure 和 AWS 等云平台,可确保跨平台兼容性。 其用户友好的 API 使开发人员能够快速将 PDF 生成和操作集成到他们的 Node.js 项目中。
IronPDF Node.js 的显著特点包括:完美的像素渲染、丰富的格式选项以及高级编辑功能,如合并和拆分 PDF、添加注释和创建 PDF 表单。
以下是从HTML文件、HTML字符串和URL生成PDF文档的示例:
import { PdfDocument } from "@ironsoftware/ironpdf";
(async () => {
const pdfFromUrl = await PdfDocument.fromUrl("https://getbootstrap.com/");
await pdfFromUrl.saveAs("website.pdf");
const pdfFromHtmlFile = await PdfDocument.fromHtml("design.html");
await pdfFromHtmlFile.saveAs("markup.pdf");
const pdfFromHtmlString = await PdfDocument.fromHtml("<p>Hello World</p>");
await pdfFromHtmlString.saveAs("markup_with_assets.pdf");
})();js有关PDF相关任务的更多代码示例,请访问此IronPDF代码示例页面。
总之,在 React 应用程序中创建 PDF 并不可怕。 有了正确的工具和清晰的理解,您就可以毫不费力地生成精美、结构合理的 PDF 文档。 我们研究了 jsPDF、pdfmake 和 React-PDF 等各种库,每个库都有自己的优势和独特功能。
通过 IronPDF 简单的 JavaScript 框架和库集成过程、优秀的文档以及响应迅速的技术支持,开发人员可以迅速开始使用,使其成为在 Node.js 应用程序中生成专业级 PDF 的首选。
IronPDF 提供完整功能的免费试用。 它也适用于其他语言,如C# .NET、Java 和 Python。 请访问 IronPDF 网站 了解更多详情。