React可复用Cupertino-Pane组件嵌套时如何感知遮罩点击同步显示状态
问题定位
当前的组件状态流存在断连:
show状态的唯一持有方是父组件Feed,通过prop单向传入IonDrawer控制显隐- 但
IonDrawer内部触发的遮罩点击、底部下滑关闭、快速滑动关闭等行为,只调用了CupertinoPane的原生隐藏方法,没有把“面板已关闭”的信号回传给父组件 - 最终就会出现:视觉上面板已经消失,但父组件的
show值仍然是true,下次点击按钮时因为状态没有从false到true的跳转,会出现面板不弹出、状态错乱的问题
修复方案
1. 给IonDrawer增加向上回传关闭事件的能力
在原有组件基础上新增onClose回调prop,所有触发面板关闭的场景,都主动调用这个回调通知父组件更新状态。同时注意规避React闭包的常见坑:CupertinoPane实例只在组件挂载时初始化一次,直接绑定初始props的话,后续props更新永远拿不到最新的回调,需要用ref存最新的回调值。
修改后的完整组件代码:
import { CupertinoPane } from "cupertino-pane"; import { useEffect, useRef } from "react"; import { PanelProps } from "../../interfaces"; // 补充props类型定义 interface PanelProps { panelKey: string; show: boolean; children: React.ReactNode; onClose?: () => void; } const IonDrawer = ({ panelKey, show, children, onClose }: PanelProps) => { const drawerRef = useRef<CupertinoPane>(); // 存储最新的onClose回调,避免闭包捕获旧值 const onCloseRef = useRef(onClose); useEffect(() => { onCloseRef.current = onClose; }, [onClose]); const hidePanel = async () => { await drawerRef.current?.hide(); // 面板完成隐藏后,通知父组件更新状态 onCloseRef.current?.(); }; useEffect(() => { drawerRef.current = new CupertinoPane(`.${panelKey}`, { backdrop: true, bottomClose: true, buttonClose: false, parentElement: "body", fastSwipeClose: true, fitHeight: true, // 所有内置关闭事件统一走hidePanel逻辑 onBackdropTap: () => hidePanel(), onBottomClose: () => hidePanel(), onFastSwipeClose: () => hidePanel(), }); // 组件卸载时销毁实例,避免多实例下的残留DOM和内存泄漏 return () => { drawerRef.current?.destroy(); }; }, [panelKey]); // 同步外部传入的show状态变化 useEffect(() => { if (show) { drawerRef.current?.present({ animate: true }); } else { hidePanel(); } }, [show]); return <div className={panelKey}>{children}</div>; }; export default IonDrawer;
2. 父组件接入关闭回调
在Feed组件调用IonDrawer时,传入onClose回调,触发时把自身的show状态切回false:
const Feed: FC<FeedProps> = ({ panelKey }) => { const [show, setShow] = useState<boolean>(false); return ( <> <IonDrawer show={show} panelKey={panelKey} onClose={() => setShow(false)} > <h3>John Doe</h3> </IonDrawer> <IonButton className="btn" fill="clear" color="secondary" onClick={() => setShow(true)} > <div className="d-flex align-center"> <IoEllipsisHorizontalCircleOutline size="20" /> </div> </IonButton> </> ); };
落地效果
- 点击按钮时,
setShow(true)触发IonDrawer的effect,弹出面板 - 触发遮罩点击、底部拖拽关闭、快速滑动关闭时,
IonDrawer会先调用原生hide方法关闭面板,再触发onClose回调把父组件的show切回false - 多Feed实例场景下,每个实例持有独立的
show状态,通过唯一的panelKey绑定对应面板,不会出现交叉干扰
内容的提问来源于stack exchange,提问作者AbdulAzeez Olanrewaju
相关产品推荐
相关产品推荐

