React集成PDFtron WebViewer报同一HTML元素创建多实例错误如何修复
问题原因
- 核心问题是WebViewer的实例创建逻辑放在了依赖
[files, viewerUrl]的useEffect中:每次files更新、或者用户切换下拉框修改viewerUrl时,都会重新调用WebViewer()创建新实例,全部挂载到同一个id为viewer的DOM元素上,直接触发重复实例报错。 - 直接使用
document.getElementById('viewer')获取DOM不符合React最佳实践,首次渲染时DOM可能还未挂载,会拿到空值。 - 缺少实例清理逻辑,组件卸载时没有销毁WebViewer实例,可能造成内存泄漏和重复实例问题。
- 原代码还存在语法错误:return中
<div className="container">后直接跟了多余的</div>,同时将逻辑或||错写为位或|,都会导致运行异常。
修复方案
- 用
useRef分别存储WebViewer实例和viewer的DOM引用,避免重复创建实例,也保证能正确获取到挂载的DOM节点。 - 拆分useEffect逻辑:只在组件首次挂载、DOM准备完成时创建一次WebViewer实例,后续切换PDF文件时直接调用实例的
loadDocument方法加载新文件即可,不需要重复创建实例。 - 新增清理逻辑,组件卸载时销毁WebViewer实例,避免内存泄漏。
修复后的完整代码如下:
import { url } from "../../../../config.json"; import React, { useState, useEffect, useRef } from "react"; import { getProject } from "../../../../services/projectService"; import { useParams } from "react-router-dom"; import WebViewer from "@pdftron/webviewer"; import { getCurrentUser } from "../../../../services/userService"; import { Redirect } from "react-router-dom"; import { deleteImage } from "../../../../services/projectService"; const MyContracts = () => { const [project, setProject] = useState({}); const [files, setFiles] = useState([]); const { id } = useParams(); // 存储viewer DOM引用 const viewerDiv = useRef(null); // 存储WebViewer实例 const wvInstance = useRef(null); const user = getCurrentUser(); const [viewerUrl, setViewerUrl] = useState(`${url}/files/testing.pdf`); useEffect(() => { getProject(id) .then(res => { setProject(res.data); setFiles(res.data.files.contracts); }) .catch(error => console.log(error.message)); }, [id]); // 只在首次挂载时创建一次WebViewer实例 useEffect(() => { // 避免重复创建 if (wvInstance.current || !viewerDiv.current) return; WebViewer( { path: `${url}/lib`, initialDoc: viewerUrl, fullAPI: true, }, viewerDiv.current ).then(instance => { // 存储实例到ref wvInstance.current = instance; }); // 组件卸载时销毁实例 return () => { if (wvInstance.current) { wvInstance.current.dispose(); wvInstance.current = null; } }; }, []); // 监听viewerUrl变化,切换加载的PDF,不需要重新创建实例 useEffect(() => { if (!wvInstance.current) return; wvInstance.current.loadDocument(viewerUrl); }, [viewerUrl]); if (!user) return <Redirect to="/private-area/sign-in" />; if (user && (user.isAdmin || (user._id === project.userID))) return ( <div className="container"> {/********** PDF VIEWER ************/} <div className="web-viewer" ref={viewerDiv}></div> {/********** PDF Gallery ************/} {files !== undefined && ( <> <h2 className="text-rtl h3Title mt-2">בחר קובץ</h2> <select id="select" className="col-12 text-rtl px-0" onChange={e => setViewerUrl(e.target.value)}> {files.map((file, index) => ( <option value={`${url}${file.url}`} key={index}> {file.name} </option> ))} </select> </> )} </div> ); return null; }; export default MyContracts;
内容的提问来源于stack exchange,提问作者David Yakin
相关产品推荐
相关产品推荐

