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

如何在React函数式组件中通过render props使用react-popper

react-popper(render props模式)Popover组件实现方案

1. 外层包裹层popoverRef绑定

直接使用React内置useRef创建ref实例,原生绑定到对应包裹DOM即可,这个ref一般用于实现点击外部关闭逻辑,不需要和react-popper内部ref做特殊合并。

// 组件顶层初始化ref
const popoverRef = useRef(null);

// JSX结构中直接绑定
<Manager>
  <div
    role="presentation"
    id="popover"
    className={popoverClasses}
    onClick={onDismiss}
    onKeyUp={onDismiss}
  >
    {backdrop && <div className={styles.popover__backdrop} />}
    <div ref={popoverRef}>
      {/* Reference、Popper内容全部放在该节点内 */}
    </div>
  </div>
</Manager>

2. Reference返回ref与自定义setReferenceRef合并

Reference组件通过render props传入的ref是popper内部定位逻辑的核心依赖,不能直接覆盖、也不能直接修改入参ref的值,需要通过回调ref的方式,同时将DOM节点赋值给popper内部ref和自定义的setReferenceRef。

<Reference>
  {({ ref: popperReferenceRef }) => {
    const targetProps = {
      // 回调ref同时完成两个ref的赋值
      ref: (node) => {
        // 优先给popper内部ref赋值,保证定位逻辑正常
        if (typeof popperReferenceRef === 'function') {
          popperReferenceRef(node);
        } else {
          popperReferenceRef.current = node;
        }
        // 再给自定义ref赋值
        setReferenceRef(node);
      },
      // 键盘事件逻辑保持原有判断即可
      onKeyUp: (keyUpEvent) => {
        if (['Enter', 'Space Bar', 'Spacebar', ' '].includes(keyUpEvent.key)) {
          if (!open) {
            onEvent();
          } else {
            onReferenceClickClose();
          }
        }
      },
      // 动态事件注意传函数引用,不要加()直接执行
      [`on${event[0].toUpperCase()}${event.substr(1)}`]: !open
        ? onEvent
        : onReferenceClickClose,
    };

    return (
      <span
        role="button"
        className={styles.popover__reference}
        tabIndex={tabIndex}
        aria-haspopup="dialog"
        {...targetProps}
      >
        {reference}
        {refIcon}
      </span>
    )
  }}
</Reference>

如果不想手写合并逻辑,也可以用ref合并工具包处理,但上述手写回调逻辑足够简单,不需要额外引入依赖。

3. Popper内部内容正确渲染

Popper渲染需要注意四个核心规则,否则会出现定位失效、箭头偏移错误、初始挂载错位等问题:

  • render props返回的ref必须绑定到弹层内容的根DOM节点
  • arrowProps.ref和arrowProps.style必须绑定到自定义箭头节点
  • 仅在弹层open状态为true时渲染Popper内容,避免初始挂载时定位异常
  • render props返回的JSX必须有单一根节点,不能直接返回零散表达式
    对应实现代码:
{open && (
  <Popper
    placement={position}
    modifiers={{
      ...modifiers,
      preventOverflow: {
        boundariesElement: boundaries,
      },
    }}
  >
    {({ ref: popperContentRef, placement: currentPlacement, arrowProps }) => (
      {/* 弹层根节点必须绑定popper返回的ref */}
      <div
        ref={popperContentRef}
        className={styles.popover__content}
        data-placement={currentPlacement}
      >
        {/* 标题区域 */}
        {title && <div className={styles.popover__title}>{title}</div>}
        {/* 内容区域,传入关闭方法 */}
        <div className={styles.popover__body}>
          {typeof children === 'function' ? children(closePopover) : children}
        </div>
        {/* 箭头节点,必须绑定arrowProps的ref和style */}
        <div
          className={styles.popover__arrow}
          ref={arrowProps.ref}
          style={arrowProps.style}
        />
      </div>
    )}
  </Popper>
)}

额外注意事项

  • 绑定动态事件时不要直接执行回调函数(不要在函数名后加()),否则组件每次渲染都会自动触发事件
  • 点击外部关闭逻辑可以直接基于popoverRef实现:判断全局点击事件的target是否在popoverRef.current包含的DOM范围内,不在则触发关闭回调
  • react-popper所有render props返回的ref都不能被覆盖,必须绑定到对应DOM节点,否则定位、箭头偏移等逻辑会全部失效

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 13:24:29