ResizeObserver始终返回相同contentRect,react-pdf响应式失效排查
问题原因及解决方案
核心问题
你遇到的宽度值不更新问题,根源在于**useRef的current属性变化不会触发组件重新渲染**,加上直接传递containerRef.current给useResizeObserver,导致钩子无法正确监听DOM元素的大小变化:
- 组件首次渲染时,
containerRef.current为null,调用useResizeObserver(null),此时钩子内部的useEffect因目标元素不存在,不会创建ResizeObserver监听。 - 组件挂载后,
containerRef.current被赋值为DOM元素,但useRef的current变化不会触发组件重新渲染,因此useResizeObserver不会被再次调用,钩子永远不会创建监听,宽度值始终保持初始的0。
解决方案
以下两种修复方式选其一即可:
方式一:改用回调Ref + useState跟踪DOM元素
修改PDFViewer组件,用useState存储DOM元素,配合回调Ref触发组件重渲染:
"use client"; import React, { useState } from "react"; import { Document, Page } from "react-pdf"; import "react-pdf/dist/esm/Page/AnnotationLayer.css"; import { pdfjs } from "react-pdf"; import "react-pdf/dist/Page/TextLayer.css"; import type { PDFDocumentProxy } from 'pdfjs-dist'; import useResizeObserver from "@/hooks/useResizeObserver"; pdfjs.GlobalWorkerOptions.workerSrc = new URL( "pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url ).toString(); const PDFViewer: React.FC = () => { const [numPages, setNumPages] = useState<number>(0); const [pageNumber, setPageNumber] = useState<number>(1); // 用useState存储DOM元素,回调Ref赋值时触发组件重渲染 const [containerElement, setContainerElement] = useState<HTMLDivElement | null>(null); // 传递存储的DOM元素给钩子 const { width: pdfWidth, height: pdfHeight } = useResizeObserver(containerElement); const pdfUrl = "/docs/menu.pdf"; function onDocumentLoadSuccess({ numPages: nextNumPages }: PDFDocumentProxy): void { setNumPages(nextNumPages); } return ( <div className="flex flex-row left-2 mt-4 justify-center items-center w-full h-auto px-4"> <div className="flex flex-1 font-generalRegular justify-right text-right text-primary text-6xl px-4"> {" < "} </div> {/* 使用回调Ref将元素存入state */} <div id="containerRef" ref={setContainerElement} className="flex flex-8 flex-col font-generalRegular justify-center text-center text-primary text-sm px-4"> <p>Menu</p> <p>Page {pageNumber} of {numPages}</p> <p>Width: {pdfWidth}</p> {/* 初始宽度为0时避免渲染Page组件,防止异常 */} {pdfWidth > 0 && ( <Document file={pdfUrl} onLoadSuccess={onDocumentLoadSuccess}> <Page pageNumber={pageNumber} width={pdfWidth}/> </Document> )} </div> <div className="flex flex-1 font-generalRegular justify-left text-left text-primary text-6xl px-4"> {" > "} </div> </div> ); }; export default PDFViewer;
方式二:修改useResizeObserver钩子接收RefObject
调整自定义钩子,让它接收useRef返回的对象本身,而非current值,内部自动监听current的变化:
import { useEffect, useState, useCallback, RefObject, useRef } from "react"; interface Size { width: number; height: number; } const useResizeObserver = (targetRef: RefObject<HTMLElement>): Size => { const [size, setSize] = useState<Size>({ width: 0, height: 0 }); // 存储当前监听的元素,避免重复监听 const observedElementRef = useRef<HTMLElement | null>(null); const handleResize = useCallback((entries: ResizeObserverEntry[]) => { const [entry] = entries; if (entry) { setSize({ width: entry.contentRect.width, height: entry.contentRect.height, }); } }, []); useEffect(() => { const currentElement = targetRef.current; // 如果元素未变化,直接返回 if (currentElement === observedElementRef.current) return; // 断开之前的监听 if (observedElementRef.current) { const prevObserver = new ResizeObserver(() => {}); prevObserver.disconnect(); } observedElementRef.current = currentElement; if (!currentElement) return; const resizeObserver = new ResizeObserver(handleResize); resizeObserver.observe(currentElement); return () => { resizeObserver.disconnect(); observedElementRef.current = null; }; }, [targetRef, handleResize]); return size; }; export default useResizeObserver;
然后修改PDFViewer中的调用,传递containerRef对象:
const containerRef = useRef<HTMLDivElement | null>(null); // 传递ref对象而非current值 const { width: pdfWidth, height: pdfHeight } = useResizeObserver(containerRef);
额外优化
为避免初始宽度为0时Page组件渲染异常,建议添加条件渲染(如方式一中的{pdfWidth > 0 && ...}),确保宽度有效后再渲染PDF内容。
内容的提问来源于stack exchange,提问作者Bawenang Rukmoko Pardian Putra
相关产品推荐
相关产品推荐

