如何在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
相关产品推荐
相关产品推荐

