React PDF 在 React 中生成 PDF 文档 使用 React-pdf

2025-05-27

React PDF在 React 中生成 PDF 文档 使用 React-pdf

这篇文章最初发表在我的博客上,请查看更多内容。

介绍

我最近在做一个项目,我有一个独特的(对我来说)要求,需要我从浏览器中的值数组中生成 pdf 文件,通常根据我在软件开发方面的一点经验,pdf 文件是在后端使用Puppeteer (node js) 和FPDF (PHP) 等生成的,所以我不得不寻找一个可以适合我的用例的 React 库,幸运的是我找到了React-pdf。我还找到了其他库,比如@progress/kendo-react-pdf,但我决定使用React-pdf,因为它的文档对开发人员友好。该库由Diego Muracciole
构建并由他维护。因此,在本教程/博客文章中,我将尝试简要解释 react-pdf 的工作原理,并引导您了解如何从来自Moviedb Api 的 对象数组生成 PDf

特征

当我尝试为我的用例选择合适的库时,我浏览了文档,React-pdf的一些功能让我决定使用它,我将简要地讨论一下它们:

成分

React-Pdf 使用React-Primitives规范来创建自定义组件,您可以使用这些组件来创建和构建 PDF 文档。
这些组件包括:

  • 文档
  • 看法
  • 图像
  • 文本
  • 关联
  • 笔记
  • 帆布
  • PDF查看器
  • PDF下载链接
  • Blob 提供程序

您可以查看文档以获取有关上述每个组件功能的更多详细信息,基本上这些组件可帮助您使用 JSXesques 语法创建 pdf。

造型

现在我们已经了解了如何创建 PDF 文档,那么该如何设置它的样式呢?React-pdf使用 StyleSheet API 提供了强大的样式解决方案,它可以帮助您使用 CSS、媒体查询和 Flexbox 来设置文档的样式。请查看文档了解它们支持的 CSS 属性。
如果您是 CSS-in-JS 的忠实粉丝怎么办?它们还支持完整的styled-components API。

字体

React-Pdf 有一个FontAPI,可以帮助您从不同来源加载字体并在 PDF 文档中使用。

这些功能让我选择了 React-pdf。此外,我查看了Github 仓库,发现维护者Diego Muracciole非常活跃,并且会尽力回复大多数已提交的问题。

演示

我将简要地向您介绍一个使用 MoviesDB API 生成 PDF 的简单示例。这个演示将演示如何生成年度最佳影片。

文件夹结构

project
│   package.json
└───Public
│   │   150.png
│   │   index.html
│   │   star.png
└───src
    │   Movie.jsx
    │   MovieList.jsx
    |   constant.js
    |   index.js
    |   styles.css
Enter fullscreen mode Exit fullscreen mode

index.js(条目)

import React from "react";
import ReactDOM from "react-dom";
import MovieList from "./MovieList";

import "./styles.css";

function App() {
    return (
        <div className="App">
              <MovieList />
        </div>
    );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Enter fullscreen mode Exit fullscreen mode

index.js 是应用程序的入口点。它渲染了<MovieList/>应用程序的父组件。

MovieList.jsx

import React, { useState } from "react";
import Axios from "axios";
import { PDFDownloadLink } from "@react-pdf/renderer";
import { API_KEY } from "./constants";
import { PdfDocument } from "./Movie";

const years = [
  { value: "2010", text: "2010" },
  { value: "2011", text: "2011" },
  { value: "2012", text: "2012" },
  { value: "2013", text: "2013" },
  { value: "2014", text: "2014" },
  { value: "2015", text: "2015" },
  { value: "2016", text: "2016" },
  { value: "2017", text: "2017" },
  { value: "2018", text: "2018" },
  { value: "2019", text: "2019" }
];

export default function MovieList() {
  const [year, setYear] = useState("");
  const [movieDetails, setDetails] = useState([]);
  const [show, setHide] = useState(false)

  const fetchMovie = async e => {
    setYear(e.target.value);
    try {
      let res = await Axios(
        `https://api.themoviedb.org/3/discover/movie?api_key=${API_KEY}&primary_release_year=${year}&sort_by=vote_average.desc`
      );
      setDetails(res.data.results);
      setHide(true)
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <div className="container">
      <h2>Best movies of the year</h2>
      <label htmlFor="movies">Select Year</label>
      <select id="movies" className="select" onChange={fetchMovie}>
        <option defaultValue="" disabled>
          Select your option
        </option>
        {years.map((year, index) => {
          return (
            <option key={index} value={year.value}>
              {year.text}
            </option>
          );
        })}
      </select>
      {show &&<PDFDownloadLink
        document={<PdfDocument data={movieDetails} />}
        fileName="movielist.pdf"
        style={{
          textDecoration: "none",
          padding: "10px",
          color: "#4a4a4a",
          backgroundColor: "#f2f2f2",
          border: "1px solid #4a4a4a"
        }}
      >
        {({ blob, url, loading, error }) =>
          loading ? "Loading document..." : "Download Pdf"
        }
      </PDFDownloadLink>}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

MovieList.jsx组件包含此应用程序中的大部分逻辑。我们PDFDownloadLink从导入@react-pdf/renderer,这基本上是一个锚标签,使我们能够生成和下载 PDF 文档。PDFDownloadLink它接受一个documentprops,这些 props 是我们即将使用本文前面列出的一些 React 原语创建的 PDF 模板。它还接受一个filename可用于定义 PDF 文档文件名的 props、一个style用于向链接标签添加内联样式的 props、一个className如果您喜欢使用类来设置样式的 props,以及children一个用于锚标签内容的 props。

Movie.jsx

import React from "react";
import {
    Page,
    Text,
    View,
    Document,
    StyleSheet,
    Image
} from "@react-pdf/renderer";
import moment from "moment";

const POSTER_PATH = "https://image.tmdb.org/t/p/w154";

const styles = StyleSheet.create({
    page: {
        backgroundColor: "#ffffff"
    },
    section: {
        margin: 10,
        padding: 10,
        flexGrow: 1
    },
    movieContainer: {
        backgroundColor: "#f6f6f5",
        display: "flex",
        flexDirection: "row",
        padding: 5
    },
    movieDetails: {
        display: "flex",
        marginLeft: 5
    },
    movieTitle: {
        fontSize: 15,
        marginBottom: 10
    },
    movieOverview: {
        fontSize: 10
    },

    image: {
        height: 200,
        width: 150
    },
    subtitle: {
        display: "flex",
        justifyContent: "space-between",
        flexDirection: "row",
        width: 150,
        alignItems: "center",
        marginBottom: 12
    },
    vote: {
        display: "flex",
        flexDirection: "row"
    },
    rating: {
        height: 10,
        width: 10
    },
    vote_text: {
        fontSize: 10
    },
    vote_pop: {
        fontSize: 10,
        padding: 2,
        backgroundColor: "#61C74F",
        color: "#fff"
    },
    vote_pop_text: {
        fontSize: 10,
        marginLeft: 4
    },
    overviewContainer: {
        minHeight: 110
    },
    detailsFooter: {
        display: "flex",
        flexDirection: "row"
    },
    lang: {
        fontSize: 8,
        fontWeight: 700
    },
    vote_average: {
        fontSize: 8,
        marginLeft: 4,
        fontWeight: "bold"
    }
});

export function PdfDocument(props) {
    console.log("pdf props", props.data);
    return (
        <Document>
            <Page style={styles.page}>
                {props.data
                    ? props.data.map((a, index) => {
                            return (
                                <View key={index} style={styles.movieContainer}>
                                    <Image
                                        style={styles.image}
                                        source={
                                            a.poster_path !== null
                                                ? `${POSTER_PATH}${a.poster_path}`
                                                : "150.jpg"
                                        }
                                    />
                                    <View style={styles.movieDetails}>
                                        <Text style={styles.movieTitle}>{a.title}</Text>
                                        <View style={styles.subtitle}>
                                            <View style={styles.vote}>
                                                <Image source="star.png" style={styles.rating} />
                                                <Text style={styles.vote_text}>{a.vote_count}</Text>
                                            </View>
                                            <View style={styles.vote}>
                                                <Text style={styles.vote_pop}>{a.popularity}</Text>
                                                <Text style={styles.vote_pop_text}>Popularity</Text>
                                            </View>
                                        </View>
                                        <View style={styles.overviewContainer}>
                                            <Text style={styles.movieOverview}>{a.overview}</Text>
                                        </View>
                                        <View style={styles.detailsFooter}>
                                            <Text style={styles.lang}>
                                                Language: {a.original_language.toUpperCase()}
                                            </Text>
                                            <Text style={styles.vote_average}>
                                                Average Votes: {a.vote_average}
                                            </Text>
                                            <Text style={styles.vote_average}>
                                                Release Date:{" "}
                                                {moment(a.release_date, "YYYY-MM-DD").format(
                                                    " MMMM D Y"
                                                )}
                                            </Text>
                                        </View>
                    </View>
                    </View>
                );
                })
            : ""}
            </Page>
        </Document>
    );
}
Enter fullscreen mode Exit fullscreen mode

Movie.jsx组件是我们要生成的 PDF 的模板,我们在这里使用 React 基元(VIEW、DOCUMENT)定义 PDF 的结构以及样式。因此,我将简要介绍一下我在这里使用的一些 React-pdf API。

  • StyleSheet.create():它可以帮助您定义要在文档中使用的样式,它接受一个包含要在文档中使用的所有 CSS 的对象,并返回一个您可以通过styleprop 应用于任何 PDF 元素的对象。

  • Document:该PDFDownloadLink documentprop 仅接受类型的组件Document,因此在创建 PDF 模板时,这必须是组件的根,并且仅接受类型的子项Page,它Document只是 PDF 模板的包装器,它接受一些可选的props

  • Page:这表示文档中的一个页面,Pages一个文档中可以有多个页面。它接受一些属性来定义size页面,orientation或者用于页面换wrap

  • View:我想将这个组件与 HTML 进行比较div,它可以帮助您分段或划分文档。道具

  • Text:该组件用于在文档上显示文本并对其应用样式。道具

  • Image:该组件用于在文档上显示图像(网络或本地),这些图像可以是PNG,JPG或base64。

演示应用程序

结论

在使用这个库之前,我从未想过可以在客户端生成 PDF。React-pdf 不仅能帮你做到这一点,还能让你使用 JSXesque 语法来构建和设计 PDF 文档。我知道这个演示很简单,但我认为这个库在某些用例中可能会有用。

文章来源:https://dev.to/finallynero/generate-pdf-documents-in-react-using-react-pdf-4ka7
PREV
React-Router v5.1 中引入的 Hooks
NEXT
React & Firebase:将 Firebase 添加到 React 应用 注意:随着 Firebase 库 v9 的发布,此文章已过时。入门总结