You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React实现HTML转PDF时辅助函数内ref读取为null的问题

React HTML转PDF时Ref始终为null的问题

我参考Stack Overflow上《Create PDF file from HTML text React》的方案实现HTML转PDF,但自己写代码时,辅助函数里的ref一直读成null。试过用useEffect触发重渲染获取printRef.current的新值,也检查了return里的三元运算符,问题还是没解决。以下是我的代码:

import React, { useRef, useState, useEffect } from "react";
import { Button } from "@mui/material";
import makeStyles from "@mui/styles/makeStyles";
import { colors } from "../../../common/GlobalColors";
import Report from "../ExportPage/Report";
import html2canvas from "html2canvas";
import { jsPDF } from "jspdf";

const useStyles = makeStyles((theme) => ({
  fileDrop: {
    width: 400,
    color: `${colors.blue}`,
    fontFamily: "Roboto",
    borderRadius: 10,
  },
  fileContainer: {
    display: "flex",
    justifyContent: "center",
    padding: 10,
  },
  fileWrapper: {},
  exportWrapper: {
    display: "flex",
    justifyContent: "center",
    paddingTop: "40px",
  },
}));

const FileDropHandler: React.FC = () => {
  const styles = useStyles();
  const [isGeneratingReports, setIsGeneratingReports] = useState<boolean>(false);
  const [element, setElement] = useState<HTMLDivElement | null>(null);

  const printRef = React.useRef<HTMLDivElement>(null);

  const handleDownloadPdf = async () => {
    const canvas = await html2canvas(printRef.current);
    const data = canvas.toDataURL("image/png");
    console.log("made it");
    const pdf = new jsPDF();
    const imgProperties = pdf.getImageProperties(data);
    const pdfWidth = pdf.internal.pageSize.getWidth();
    const pdfHeight = (imgProperties.height * pdfWidth) / imgProperties.width;

    pdf.addImage(data, "PNG", 0, 0, pdfWidth, pdfHeight);
    pdf.save("print.pdf");
  };

  return (
    <>
      {isGeneratingReports ? (
        <>
          <div ref={(printRef) => setElement(printRef)}>
            <Report />
          </div>
        </>
      ) : (
        <div className={styles.fileWrapper}>
          <Button variant="contained" onClick={() => {
               handleDownloadPdf()
               setIsGeneratingReports(true);
          }}>
            Convert Files
          </Button>
        </div>
      )}
    </>
  );
};

export default FileDropHandler;

问题根源

  1. 执行顺序错了:点击按钮时先调用handleDownloadPdf,此时isGeneratingReports还是false,带ref的<div>根本没渲染,printRef.current自然是null。
  2. Ref绑定错误:你用函数式ref把DOM元素存到element状态里,但printRef本身没绑定到DOM上——函数式ref的参数是DOM元素,但你没给printRef.current赋值,只更新了element状态。

修复方案

1. 调整执行顺序,确保DOM渲染后再生成PDF

先切换状态渲染带ref的组件,等DOM更新完成后再调用PDF生成函数:

// 修改按钮点击逻辑
<Button variant="contained" onClick={() => setIsGeneratingReports(true)}>
  Convert Files
</Button>

// 用useEffect监听状态变化,DOM渲染完再生成PDF
useEffect(() => {
  if (isGeneratingReports && printRef.current) {
    const generatePdf = async () => {
      const canvas = await html2canvas(printRef.current);
      const data = canvas.toDataURL("image/png");
      const pdf = new jsPDF();
      const imgProperties = pdf.getImageProperties(data);
      const pdfWidth = pdf.internal.pageSize.getWidth();
      const pdfHeight = (imgProperties.height * pdfWidth) / imgProperties.width;

      pdf.addImage(data, "PNG", 0, 0, pdfWidth, pdfHeight);
      pdf.save("print.pdf");
      // 可选:生成后切回初始状态
      setIsGeneratingReports(false);
    };
    generatePdf();
  }
}, [isGeneratingReports]);

2. 正确绑定Ref

直接把ref绑定到div上,不用额外状态存储:

<div ref={printRef}>
  <Report />
</div>

完整修复后的代码

import React, { useRef, useState, useEffect } from "react";
import { Button } from "@mui/material";
import makeStyles from "@mui/styles/makeStyles";
import { colors } from "../../../common/GlobalColors";
import Report from "../ExportPage/Report";
import html2canvas from "html2canvas";
import { jsPDF } from "jspdf";

const useStyles = makeStyles((theme) => ({
  fileDrop: {
    width: 400,
    color: `${colors.blue}`,
    fontFamily: "Roboto",
    borderRadius: 10,
  },
  fileContainer: {
    display: "flex",
    justifyContent: "center",
    padding: 10,
  },
  fileWrapper: {},
  exportWrapper: {
    display: "flex",
    justifyContent: "center",
    paddingTop: "40px",
  },
}));

const FileDropHandler: React.FC = () => {
  const styles = useStyles();
  const [isGeneratingReports, setIsGeneratingReports] = useState<boolean>(false);
  const printRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (isGeneratingReports && printRef.current) {
      const generatePdf = async () => {
        const canvas = await html2canvas(printRef.current);
        const data = canvas.toDataURL("image/png");
        const pdf = new jsPDF();
        const imgProperties = pdf.getImageProperties(data);
        const pdfWidth = pdf.internal.pageSize.getWidth();
        const pdfHeight = (imgProperties.height * pdfWidth) / imgProperties.width;

        pdf.addImage(data, "PNG", 0, 0, pdfWidth, pdfHeight);
        pdf.save("print.pdf");
        setIsGeneratingReports(false);
      };
      generatePdf();
    }
  }, [isGeneratingReports]);

  return (
    <>
      {isGeneratingReports ? (
        <div ref={printRef}>
          <Report />
        </div>
      ) : (
        <div className={styles.fileWrapper}>
          <Button variant="contained" onClick={() => setIsGeneratingReports(true)}>
            Convert Files
          </Button>
        </div>
      )}
    </>
  );
};

export default FileDropHandler;

额外提醒

  • 如果<Report>是服务端渲染组件,要等hydration完成后再调用html2canvas。
  • 要是<Report>里有异步加载的内容,得加个加载状态,等内容加载完再触发PDF生成。

内容的提问来源于stack exchange,提问作者Ian Goodman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 05:15:32