点击元素动态添加React Tooltip组件的问题排查
分析Tooltip组件两种实现的错误原因&解决方案
我来帮你拆解这两种尝试里的问题,顺便给你一套符合「多Tooltip同时显示」需求的可行方案~
一、第一种尝试:直接实例化React组件并appendChild的错误
你遇到的TypeError:参数不是Node类型核心原因是:
- React组件不是原生DOM节点:
new Tooltip()得到的是React组件的实例对象,而appendChild只接受浏览器能识别的原生DOM元素,两者完全不是一个东西,自然会报错。 - 顺带提个小问题:你给Tooltip传参的方式也错了——React组件的props是通过第一个参数的对象传入的,应该写成
new Tooltip({tooltipLeft, tooltipTop}),不过这不是导致报错的核心原因。
二、第二种尝试:ReactDOM.createPortal的错误
虽然没报错,但Tooltip不显示的问题有三个关键点:
- Portal的第一个参数必须是React元素,不是组件实例:你用
new Tooltip(...)得到的是组件实例,不是JSX元素(也就是<Tooltip />这种形式),所以Portal根本没被正确创建。 - Portal需要作为React渲染上下文的一部分:你只是在事件回调里调用了
createPortal(),但没有把它纳入React的渲染流程——Portal必须作为组件render输出的一部分,或者通过状态触发渲染,直接调用不会让它显示在页面上。 - 还有个样式笔误:Tooltip组件里的
right: this.props.tooltipTop应该改成top: this.props.tooltipTop,而且样式值要加px单位,不然定位完全失效。
三、符合你需求的解决方案(多Tooltip同时显示)
因为你需要支持大量元素自定义、同时显示多个Tooltip,最适合用状态驱动+Portal的方式:用一个数组状态存储所有需要显示的Tooltip的位置和唯一标识,然后遍历数组渲染对应的Portal。
修正后的App组件代码
import React, { useState } from 'react'; import ReactDOM from 'react-dom'; import Tooltip from './Tooltip'; import './App.css'; function App() { // 存储所有待显示的Tooltip:每个元素包含唯一id、定位坐标 const [tooltips, setTooltips] = useState([]); const clickHandler = (event) => { const pos = event.target.getBoundingClientRect(); // 计算Tooltip的定位(居中对齐点击元素) const tooltipLeft = pos.left + pos.width / 2; const tooltipTop = pos.top - 30; // 生成唯一id,避免重复 const tooltipId = `${event.target.className}-${Date.now()}`; // 添加新Tooltip到状态数组 setTooltips(prev => [...prev, { id: tooltipId, left: tooltipLeft, top: tooltipTop }]); }; // 关闭指定Tooltip的逻辑 const closeTooltip = (targetId) => { setTooltips(prev => prev.filter(tooltip => tooltip.id !== targetId)); }; return ( <div className='wrapper'> <div className='box one' onClick={clickHandler}></div> <div className='box two' onClick={clickHandler}></div> <div className='box three' onClick={clickHandler}></div> {/* 遍历渲染所有Tooltip的Portal */} {tooltips.map(tooltip => ( ReactDOM.createPortal( <Tooltip key={tooltip.id} tooltipLeft={tooltip.left} tooltipTop={tooltip.top} onClose={() => closeTooltip(tooltip.id)} />, document.body // 挂载到body,避免被父元素的overflow样式影响 ) ))} </div> ); } export default App;
修正后的Tooltip组件代码
class Tooltip extends React.Component { basicStyle = { width: 80, height: 30, backgroundColor: '#333333', color: '#fff', position: 'absolute', left: `${this.props.tooltipLeft}px`, // 必须加px单位 top: `${this.props.tooltipTop}px`, // 修复之前的right笔误 transform: 'translateX(-50%)', // 让Tooltip居中对齐点击元素 textAlign: 'center', lineHeight: '30px', cursor: 'pointer' }; render() { return ( <div style={this.basicStyle} onClick={this.props.onClose}> tooltip </div> ); } } export default Tooltip;
方案优势
- 完全通过状态驱动,不需要给每个元素手动加条件渲染或钩子
- 支持同时显示任意数量的Tooltip
- Portal挂载到body,避免被父元素的
overflow: hidden等样式遮挡 - 每个Tooltip有唯一标识,关闭时可以精准移除
内容的提问来源于stack exchange,提问作者Clyde The Cloud
相关产品推荐
相关产品推荐

