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;
问题根源
- 执行顺序错了:点击按钮时先调用
handleDownloadPdf,此时isGeneratingReports还是false,带ref的<div>根本没渲染,printRef.current自然是null。 - 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
相关产品推荐
相关产品推荐

