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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 09:10:24