对于弹出层组件,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。