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

React数组项目多Ref实现PopperJS Tooltip报错求助

解决React遍历项目数组时PopperJS Tooltip的Ref问题

我在网页中添加项目列表,希望用户悬停项目图片时,左侧显示包含项目详情的Tooltip,用PopperJS实现。单个项目用useRef能正常运行,但处理数组项目时,尝试给每个项目创建不同useRef时出现错误:Function components cannot have string refs,现有代码如下,求解决方法。


原项目列表组件代码

import React, { useState, useRef } from "react";
import Card from "./UI/Card";
import ToolTip from "./UI/Tooltip";
import ProjectData from "./data/Projects.json";

const Projects = (props) => {
  const MidpointRef = useRef();
  const SpacesRef = useRef();
  const MovieMinderRef = useRef();
  const midpointTooltipRef = useRef();
  const spacesTooltipRef = useRef();
  const MovieMinderTooltipRef = useRef();
  const [isOpen, setIsOpen] = useState(false);

  const toggleToolTip = () => {
    setIsOpen(!isOpen);
  };

  return ProjectData.map((project) => {
    return (
      <Card>
        <div
          key={project.id}
          className={`flex project-thumbnail ${project.Name}`}
          onMouseEnter={toggleToolTip}
          onMouseLeave={toggleToolTip}
          boxRef={`${project.Name}Ref`}
        >
          <button
            class="view-button"
            onMouseEnter={props.onMouseEnter}
            onMouseLeave={props.onMouseLeave}
          >
            Click to view
          </button>
        </div>
        <ToolTip
          ref={`${project.Name}TooltipRef`}
          toolTipState={isOpen}
          boxRef={`${project.Name}Ref`}
        />
      </Card>
    );
  });
};

注:onMouseEnter/onMouseLeave为缩小光标事件处理函数


原Tooltip组件代码

import classNames from "classnames";
import { useRef, forwardRef } from "react";
import { usePopper } from "react-popper";

const ToolTip = forwardRef((props, ref) => {
  const { attributes } = usePopper(
    props.boxRef.current,
    ref.current,
    {}
  );
  return (
    <div
      id="tooltip"
      role="tooltip"
      className={classNames("tooltip", {
        "tooltip-active": props.toolTipState,
      })}
      ref={ref}
      {...attributes.popper}
    >
      My tooltip
    </div>
  );
});

export default ToolTip;

解决方法及修改后的代码

核心问题是:字符串Ref已被React废弃,且单个状态会导致所有Tooltip同步显示/隐藏。以下是修正方案:

修改后的Projects组件

import React, { useState, useRef } from "react";
import Card from "./UI/Card";
import ToolTip from "./UI/Tooltip";
import ProjectData from "./data/Projects.json";

const Projects = (props) => {
  // 用对象统一存储所有项目的容器Ref和Tooltip Ref
  const projectRefs = useRef({});
  const tooltipRefs = useRef({});
  // 为每个项目单独维护Tooltip显示状态
  const [tooltipStates, setTooltipStates] = useState({});

  // 切换单个项目的Tooltip状态
  const toggleToolTip = (projectId) => {
    setTooltipStates(prev => ({
      ...prev,
      [projectId]: !prev[projectId]
    }));
  };

  return ProjectData.map((project) => {
    return (
      <Card key={project.id}>
        <div
          className={`flex project-thumbnail ${project.Name}`}
          onMouseEnter={() => toggleToolTip(project.id)}
          onMouseLeave={() => toggleToolTip(project.id)}
          // 通过ref回调存入对应项目的DOM元素
          ref={el => projectRefs.current[project.id] = el}
        >
          <button
            className="view-button"
            onMouseEnter={props.onMouseEnter}
            onMouseLeave={props.onMouseLeave}
          >
            Click to view
          </button>
        </div>
        <ToolTip
          // 存入对应项目的Tooltip DOM元素
          ref={el => tooltipRefs.current[project.id] = el}
          toolTipState={tooltipStates[project.id] || false}
          // 传递当前项目的容器Ref
          boxRef={projectRefs.current[project.id]}
          // 可传入项目详情用于渲染
          projectDetails={project.Description}
        />
      </Card>
    );
  });
};

export default Projects;

修改后的Tooltip组件

import classNames from "classnames";
import { forwardRef, useEffect, useRef } from "react";
import { usePopper } from "react-popper";

const ToolTip = forwardRef((props, ref) => {
  const popperRef = useRef(null);
  
  // 同步forwardRef的引用
  useEffect(() => {
    popperRef.current = ref.current;
  }, [ref]);

  // 确保容器Ref存在时再初始化Popper,避免空引用错误
  const { attributes, styles } = usePopper(
    props.boxRef,
    popperRef.current,
    {
      placement: "left", // 设置Tooltip显示在左侧
      modifiers: [
        {
          name: "offset",
          options: {
            offset: [0, 10], // 调整偏移量避免紧贴元素
          },
        },
      ],
    }
  );

  return (
    <div
      role="tooltip"
      className={classNames("tooltip", {
        "tooltip-active": props.toolTipState,
      })}
      ref={ref}
      style={styles.popper}
      {...attributes.popper}
    >
      {props.projectDetails || "项目详情"}
    </div>
  );
});

export default ToolTip;

关键修改点说明

  1. Ref管理优化:用projectRefs和tooltipRefs两个对象Ref统一存储所有项目的DOM元素,通过ref回调函数绑定,彻底避免字符串Ref的错误。
  2. 状态独立控制:用tooltipStates对象存储每个项目的Tooltip状态,确保只有当前悬停的项目Tooltip会触发显示/隐藏,不会影响其他项目。
  3. Popper配置完善:添加了左侧显示的placement配置,增加偏移量优化显示效果,同时增加空引用判断避免初始化错误。
  4. React规范修正:将button的class改为React规范的className,给循环根元素Card添加了唯一key。

内容的提问来源于stack exchange,提问作者d709859

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 20:58:27