对于弹出层组件,React Portals 无疑是提供了一种很好的解决方案(Protal相关也可以看这里)。 如果没有 Portal
的话弹出层要怎么处理呢,比如React Native
环境中?
React Native
中可以使用Modal
组件,但是因为层级问题以及与其他组件的兼容性也是经常被诟病。
我们来解决这个问题。
曲径通幽
Modal
的本质就是一个在组件树上拥有更高显示层级的view
,可以覆盖整个页面,有背景色和透明度,蒙层背景中包含弹出的显示内容区,比如一个提示,对话框等等。
将蒙层的Modal
和内容区分开,做成父子组件的组合模式,自定义并暴露显示子组件的方法,将子组件整个传入Modal
中显示,即可达到弹出内容的效果,再提供关闭Modal
的方法,用于命令式地设置Modal
的隐藏。
也就是说我们的组件满足这些条件:
- 更高的显示层级
- 通过方法填入子组件,并暴露显示和隐藏
Modal
的方法
实现和效果
命令式的调用无疑需要用到Ref
了,开始实现:
-
定义组件。包含两个
state
,一个view
,一个控制显示与隐藏的flag
。暴露出两个方法,显示和关闭。需要显示的内容view
通过方法参数传入。import React, { useImperativeHandle, useState } from "react"; function Modal(props, ref) { const [view, setView] = useState([]); const [isShow, setIsShow] = useState(0); useImperativeHandle(ref, () => ({ init: (view) => { setView(view); setIsShow(true); }, close: () => setIsShow(false), })); if (isShow) { return <div style={sts.wrap}>{view}</div>; } else { return null; } } export default React.forwardRef(Modal); export const modalRef = React.createRef(); export const TopModal = { show, close }; function show(view) { modalRef.current.init(view); } function close() { modalRef.current.close(); } // 蒙层 style const sts = { wrap: { zIndex: 100, top: 0, left: 0, "100%", height: "100%", position: "absolute", backgroundColor: "rgba(0,0,0,0.6)", display: "flex", justifyContent: "center", alignItems: "center", }, };
-
将组件放到组件树较高层级的位置,这里直接放到了和
App
同级的位置,将定义的Ref
传入。import Modal, { modalRef } from "./refmodal/component"; ReactDOM.render( <React.StrictMode> <App /> <Modal ref={modalRef} /> </React.StrictMode>, document.getElementById("root") );
-
测试运行效果。
export default function ModalRefTest() { function openModal() { const view = ( <div style={{ 100, height: 100, backgroundColor: "#FFF" }}> <button onClick={close}>关闭</button> </div> ); TopModal.show(view); } function close() { TopModal.close(); } return ( <div> <button onClick={openModal}>打开</button> </div> ); }
弹出框的内容很简单,只有一个按钮,用于关闭当前
Modal
,show
方法将内容区整个div
传入。效果:
到这里,一个简易版本的Modal
已经有了初步效果。
扩展方向
将要显示的内容做成一个组件,直接将组件view
传入Modal
中显示。这样的方式,可以做成对话框,Toast,各种提示。这其中可能还需要对背景色作出修改,这些也可以通过函数参数的方式传入到组件中来处理。
也可以将布局的关键属性传入,组件决定内容区显示在中间还是顶部,还是底部。由此再可以扩展出actionSheet
组件。
可以将API
设计成show(view,config)
的形式,读取config
的内容,动态设置样式和布局显示。
通过React
的方式添加的高层级view
显示,这一思路完全适用于React Native
。