React-PDF 完全指南:在你的应用中优雅地展示 PDF

在 Web 应用中,直接预览和展示 PDF 文件是一项非常普遍的需求,例如在线查看发票、合同、电子书或用户上传的文档。react-pdf 就是解决这一问题的优秀库,它能让你在 React 应用中轻松地渲染和控制 PDF 文档的显示。

本文将作为一份详细的入门指南,带你从零开始,快速掌握 react-pdf 的核心用法,并配置常见功能,如分页、缩放等。

目录

  1. [什么是 react-pdf?](#什么是 react-pdf? "#1-%E4%BB%80%E4%B9%88%E6%98%AF-react-pdf")
  2. [快速上手:展示你的第一个 PDF](#快速上手:展示你的第一个 PDF "#2-%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B%E5%B1%95%E7%A4%BA%E4%BD%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA-pdf")
  3. 核心组件详解
  4. 常见功能配置
  5. 常见问题与最佳实践
    • [错误:"Failed to load PDF worker"](#错误:"Failed to load PDF worker" "#%E9%94%99%E8%AF%AFfailed-to-load-pdf-worker")
    • 性能优化
  6. 总结

1. 什么是 react-pdf?

react-pdf 是一个 React 库,专门用于在浏览器中显示和渲染 已存在的 PDF 文件。它底层使用了 Mozilla 开发的著名库 PDF.js,并将其封装成易于在 React 中使用的组件。

它不是用来做什么的?(重要区别)

初学者最容易混淆的一点是 react-pdf@react-pdf/renderer 的区别:

  • react-pdf (本文主角): 用于展示 一个已有的 .pdf 文件,就像一个 PDF 阅读器。
  • @react-pdf/renderer: 用于使用 React 组件从零创建 一个新的 .pdf 文件。

一句话总结 :如果你想在网页上 PDF,用 react-pdf。如果你想生成 PDF,用 @react-pdf/renderer

核心优势

  • 与 React 生态无缝集成:使用声明式的组件语法,易于上手。
  • 功能强大:支持分页、缩放、文本选择、链接跳转、注释层等高级功能。
  • 高度可定制:你可以完全自定义 PDF 周边的 UI,如分页控制器、工具栏等。
  • 性能优异:通过 Web Worker 在后台线程处理繁重的 PDF 解析工作,不阻塞主线程。

2. 快速上手:展示你的第一个 PDF

让我们通过一个简单的例子,看看在你的应用中嵌入一个 PDF 预览是多么容易。

环境准备

确保你已有一个 React 项目(例如通过 create-react-appVite 创建)。

bash 复制代码
npx create-react-app my-pdf-viewer
cd my-pdf-viewer

安装依赖

在你的 React 项目中安装 react-pdf

bash 复制代码
npm install react-pdf
# 或者
yarn add react-pdf

配置 PDF.js Worker(关键步骤)

react-pdf 使用 PDF.js 的 "worker" 来在后台解析 PDF,以避免卡顿。你需要告诉 react-pdf 在哪里找到这个 worker 文件。这是最容易出错的一步,但配置起来很简单

在你的应用入口文件(例如 src/index.jssrc/App.js)的顶部添加以下代码:

jsx 复制代码
import { pdfjs } from 'react-pdf';

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.min.js',
  import.meta.url,
).toString();

注意 : 上述 import.meta.url 的语法适用于现代构建工具,如 Vite 和 Create React App 5+。对于旧版 CRA,你可能需要使用 copy-webpack-plugin 来手动复制 worker 文件(详见文末的常见问题部分)。

编写显示组件

现在,创建一个组件来显示 PDF。我们先从显示 PDF 的第一页开始。

  1. 准备一个 PDF 文件。你可以从网上下载一个示例 PDF,或者使用自己的文件。为了简单起见,将它放在 public 文件夹下,例如 public/sample.pdf

  2. 创建 src/PdfViewer.js 文件:

jsx 复制代码
// src/PdfViewer.js
import React, { useState } from 'react';
import { Document, Page } from 'react-pdf';
import 'react-pdf/dist/Page/AnnotationLayer.css';
import 'react-pdf/dist/Page/TextLayer.css';


function PdfViewer() {
  const [numPages, setNumPages] = useState(null);
  const [pageNumber, setPageNumber] = useState(1);

  function onDocumentLoadSuccess({ numPages }) {
    setNumPages(numPages);
  }

  return (
    <div>
      <Document
        file="/sample.pdf" // public 文件夹下的文件
        onLoadSuccess={onDocumentLoadSuccess}
      >
        <Page pageNumber={pageNumber} />
      </Document>
      <p>
        Page {pageNumber} of {numPages}
      </p>
    </div>
  );
}

export default PdfViewer;

重要: 为了让文本选择和注释正常显示,你需要引入附带的 CSS 文件。

  1. src/App.js 中使用它:
jsx 复制代码
// src/App.js
import React from 'react';
import PdfViewer from './PdfViewer';

function App() {
  return (
    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
      <PdfViewer />
    </div>
  );
}

export default App;

启动你的应用 (npm start),你就能在页面上看到 sample.pdf 的第一页了!

3. 核心组件详解

react-pdf 的 API 非常简洁,主要围绕两个核心组件。

<Document>

这是加载 PDF 的容器组件。它负责获取和解析 PDF 文件,但不直接渲染任何内容。

  • file 属性 (必需): 指定 PDF 文件的来源。可以是 URL 字符串、文件对象(来自 <input type="file">)、或 Base64 编码的字符串。
  • onLoadSuccess 属性 : 当 PDF 加载并解析成功时触发的回调函数。它会返回一个包含 numPages (总页数) 的对象,这是实现分页功能的关键。
  • onLoadError 属性 : 加载失败时的回调,参数是 error 对象。
  • options 属性 : 传递给底层 PDF.js 的高级选项,例如设置 HTTP 请求头。

<Page>

该组件用于渲染 <Document> 中加载的 PDF 的特定某一页。

  • pageNumber 属性 (必需): 你想显示的页码(从 1 开始)。
  • width / height 属性: 控制渲染出页面的宽度或高度(像素)。两者设置一个即可,页面会按比例缩放。
  • scale 属性 : 按比例缩放页面,scale={1.5} 表示放大 50%。
  • renderTextLayer 属性 :布尔值,是否渲染文本层。设为 true (默认) 可以让用户选择和复制文本。
  • renderAnnotationLayer 属性 : 布尔值,是否渲染注释层。设为 true (默认) 可以显示 PDF 中的链接等注释。

4. 常见功能配置

掌握了基础组件后,我们来实现一些实用的功能。

实现分页控制(上一页/下一页)

基于我们快速上手的例子,添加两个按钮来控制页面切换。

jsx 复制代码
// src/PdfViewer.js (完整版)
import React, { useState } from 'react';
import { Document, Page } from 'react-pdf';
import 'react-pdf/dist/Page/AnnotationLayer.css';
import 'react-pdf/dist/Page/TextLayer.css';

function PdfViewer() {
  const [numPages, setNumPages] = useState(null);
  const [pageNumber, setPageNumber] = useState(1);

  function onDocumentLoadSuccess({ numPages }) {
    setNumPages(numPages);
    setPageNumber(1); // 加载新文档时,重置到第一页
  }

  function goToPrevPage() {
    setPageNumber(prevPageNumber => Math.max(prevPageNumber - 1, 1));
  }

  function goToNextPage() {
    setPageNumber(prevPageNumber => Math.min(prevPageNumber + 1, numPages));
  }

  return (
    <div>
      <nav>
        <button onClick={goToPrevPage} disabled={pageNumber <= 1}>
          Prev
        </button>
        <button onClick={goToNextPage} disabled={pageNumber >= numPages}>
          Next
        </button>
      </nav>

      <div style={{ border: '1px solid black', marginTop: '10px' }}>
        <Document
          file="/sample.pdf"
          onLoadSuccess={onDocumentLoadSuccess}
        >
          <Page pageNumber={pageNumber} />
        </Document>
      </div>

      <p>
        Page {pageNumber} of {numPages}
      </p>
    </div>
  );
}

export default PdfViewer;

控制 PDF 尺寸与缩放

你可以通过 <Page>widthscale 属性来控制大小。

方法一:固定宽度

jsx 复制代码
<Page pageNumber={pageNumber} width={600} />

方法二:动态缩放

jsx 复制代码
const [scale, setScale] = useState(1.0);

// ...
<div>
  <button onClick={() => setScale(s => s - 0.1)}>-</button>
  <span>Zoom: {Math.round(scale * 100)}%</span>
  <button onClick={() => setScale(s => s + 0.1)}>+</button>
</div>
<Page pageNumber={pageNumber} scale={scale} />

加载不同来源的 PDF

file 属性非常灵活:

  • 从 URL 加载 :

    jsx 复制代码
    <Document file="https://example.com/document.pdf" />
  • 从用户上传的文件加载 :

    jsx 复制代码
    const [file, setFile] = useState(null);
    
    function onFileChange(event) {
      setFile(event.target.files[0]);
    }
    
    return (
      <div>
        <input type="file" onChange={onFileChange} />
        {file && <Document file={file}>...</Document>}
      </div>
    );
  • 从 Base64 字符串加载 :

    jsx 复制代码
    const base64string = 'data:application/pdf;base64,JVBERi0xLj...';
    <Document file={base64string} />

开启文本选择与注释层

默认情况下,renderTextLayerrenderAnnotationLayer 都是 true。如果你发现无法选择文本或点击链接,请确保:

  1. 你没有将它们设置为 false
  2. 你已经正确引入了 CSS 文件,如快速上手部分所示。这是最常见的原因。
jsx 复制代码
import 'react-pdf/dist/Page/AnnotationLayer.css';
import 'react-pdf/dist/Page/TextLayer.css';

// ...
<Page
  pageNumber={pageNumber}
  renderTextLayer={true}
  renderAnnotationLayer={true}
/>

5. 常见问题与最佳实践

错误:"Failed to load PDF worker"

这是最常见的问题。这意味着 react-pdf 找不到 pdf.worker.min.js 文件。

解决方案:

  1. 确认你已在应用入口配置了 workerSrc,如本文第二部分所示。

  2. 对于 Create React App (v4 及以下) 或其他基于 Webpack 的项目,你可能需要手动复制 worker 文件到 public 目录。

    • 安装 copy-webpack-plugin: npm install --save-dev copy-webpack-plugin
    • config-overrides.js (如果你用了 react-app-rewired) 或 webpack.config.js 中配置:
    javascript 复制代码
    const CopyWebpackPlugin = require('copy-webpack-plugin');
    
    module.exports = {
      //...
      plugins: [
        new CopyWebpackPlugin({
          patterns: [
            { from: 'node_modules/pdfjs-dist/build/pdf.worker.min.js', to: '' }
          ]
        })
      ]
    };
    • 然后将 workerSrc 设置为 /pdf.worker.min.js

性能优化

  • Memoization : 如果你的 PdfViewer 组件因为父组件重渲染而频繁刷新,使用 React.memo 包裹它可以避免不必要的重渲染。

  • CDN 加载 Worker : 为了减小打包体积,你可以从 CDN 加载 worker:

    jsx 复制代码
    pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;

6. 总结

react-pdf 是一个强大而直观的工具,它极大地简化了在 React 应用中展示 PDF 的复杂性。通过 <Document><Page> 这两个核心组件,结合简单的状态管理,你就可以快速实现功能完备的 PDF 阅读器,包括分页、缩放和文本交互。

记住配置 PDF.js worker 的关键步骤,并善用 onLoadSuccess 回调来构建你的交互逻辑,你将能够为你的用户提供流畅的 PDF 预览体验。

相关推荐
NeverSettle_8 小时前
React工程实践面试题深度分析2025
javascript·react.js
学前端搞口饭吃8 小时前
react reducx的使用
前端·react.js·前端框架
努力往上爬de蜗牛8 小时前
react3面试题
javascript·react.js·面试
开心不就得了8 小时前
React 进阶
前端·javascript·react.js
谢尔登8 小时前
【React】React 哲学
前端·react.js·前端框架
学前端搞口饭吃10 小时前
react context如何使用
前端·javascript·react.js
GDAL11 小时前
为什么Cesium不使用vue或者react,而是 保留 Knockout
前端·vue.js·react.js
Dragon Wu20 小时前
React state在setInterval里未获取最新值的问题
前端·javascript·react.js·前端框架
YU大宗师20 小时前
React面试题
前端·javascript·react.js
木兮xg20 小时前
react基础篇
前端·react.js·前端框架