React Portal元素宽度获取异常:值为0或undefined的解决方法
解决Portal元素宽度获取问题
因为开启isPortal={true}后,Dropdown的真实DOM会被挂载到页面的其他节点(比如body下),你现在给Dropdown组件加的ref,拿到的大概率是原组件位置的占位容器(通常是个空元素),而非Portal里的实际渲染元素,所以才会拿到0或undefined的宽度值。
下面是两种可行的解决方法:
方法一:让Dropdown组件暴露Portal内部元素的ref
如果是你自己封装的Dropdown组件,修改组件内部逻辑,用forwardRef和useImperativeHandle把Portal里的真实元素ref传递出来:
// 封装Dropdown组件时的代码 import React, { forwardRef, useImperativeHandle, useRef } from 'react'; import ReactDOM from 'react-dom'; type DropdownProps = { setDropdownVisible: (visible: boolean) => void; title: React.ReactNode; className?: string; isPortal?: boolean; }; const Dropdown = forwardRef<HTMLDivElement, DropdownProps>((props, ref) => { const portalInnerRef = useRef<HTMLDivElement>(null); // 将内部元素的ref暴露给外部 useImperativeHandle(ref, () => portalInnerRef.current!); const portalTarget = document.body; // 或者你指定的挂载节点 return props.isPortal ? ( ReactDOM.createPortal( <div ref={portalInnerRef} className={props.className}> {/* Dropdown的内容 */} {props.title} </div>, portalTarget ) ) : ( <div ref={portalInnerRef} className={props.className}> {props.title} </div> ); }); // 调用Dropdown的代码 const YourComponent = () => { const dropdownRef = useRef<HTMLDivElement>(null); const [dropdownVisible, setDropdownVisible] = useState(false); const [width, setWidth] = useState(0); useLayoutEffect(() => { if (dropdownRef.current) { const currentWidth = dropdownRef.current.clientWidth; setWidth(currentWidth); console.log(currentWidth); } }, [dropdownVisible]); // 依赖dropdownVisible,状态变化时重新计算宽度 return ( <Dropdown setDropdownVisible={setDropdownVisible} title={<DropdownTitle />} ref={dropdownRef} className={`user-info flex max-w-full justify-center whitespace-nowrap border pr-1 sm:py-0 ${ dropdownVisible ? ' min-w-[180px] rounded-t-[25px] p-2 sm:p-0' : 'rounded-full'}`} isPortal={true} /> ); };
方法二:直接通过类名查询DOM获取元素
如果无法修改Dropdown组件的源码,可以利用元素的唯一类名直接查询Portal里的DOM元素:
const YourComponent = () => { const [dropdownVisible, setDropdownVisible] = useState(false); const [width, setWidth] = useState(0); useLayoutEffect(() => { // 用唯一类名查找Portal中的元素 const dropdownElement = document.querySelector('.user-info'); if (dropdownElement instanceof HTMLElement) { const currentWidth = dropdownElement.clientWidth; setWidth(currentWidth); console.log(currentWidth); } }, [dropdownVisible]); return ( <Dropdown setDropdownVisible={setDropdownVisible} title={<DropdownTitle />} className={`user-info flex max-w-full justify-center whitespace-nowrap border pr-1 sm:py-0 ${ dropdownVisible ? ' min-w-[180px] rounded-t-[25px] p-2 sm:p-0' : 'rounded-full'}`} isPortal={true} /> ); };
关键注意点
- 一定要把
dropdownVisible加入useLayoutEffect的依赖数组,因为当这个状态变化时,元素的样式和显示状态会改变,宽度也会跟着变化,需要重新计算。 - 函数组件里优先用
useRef代替createRef,createRef每次渲染都会生成新的引用,稳定性不如useRef。
内容的提问来源于stack exchange,提问作者Salil Rajkarnikar
相关产品推荐
相关产品推荐

