制作一个命令式的 React 弹出层组件 (适用 React Native)

1,621 阅读3分钟

对于弹出层组件,React Portals 无疑是提供了一种很好的解决方案(Protal相关也可以看这里)。 如果没有 Portal的话弹出层要怎么处理呢,比如React Native环境中?

React Native中可以使用Modal组件,但是因为层级问题以及与其他组件的兼容性也是经常被诟病。

我们来解决这个问题。

曲径通幽

Modal的本质就是一个在组件树上拥有更高显示层级的view,可以覆盖整个页面,有背景色和透明度,蒙层背景中包含弹出的显示内容区,比如一个提示,对话框等等。

将蒙层的Modal和内容区分开,做成父子组件的组合模式,自定义并暴露显示子组件的方法,将子组件整个传入Modal中显示,即可达到弹出内容的效果,再提供关闭Modal的方法,用于命令式地设置Modal的隐藏。

也就是说我们的组件满足这些条件:

  • 更高的显示层级
  • 通过方法填入子组件,并暴露显示和隐藏Modal的方法

实现和效果

命令式的调用无疑需要用到Ref了,开始实现:

  1. 定义组件。包含两个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,
        width: "100%",
        height: "100%",
        position: "absolute",
        backgroundColor: "rgba(0,0,0,0.6)",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
      },
    };
    
    
  2. 将组件放到组件树较高层级的位置,这里直接放到了和App同级的位置,将定义的Ref传入。

    import Modal, { modalRef } from "./refmodal/component";
    
    ReactDOM.render(
      <React.StrictMode>
        <App />
        <Modal ref={modalRef} />
      </React.StrictMode>,
      document.getElementById("root")
    );
    
  3. 测试运行效果。

    export default function ModalRefTest() {
      function openModal() {
        const view = (
          <div style={{ width: 100, height: 100, backgroundColor: "#FFF" }}>
            <button onClick={close}>关闭</button>
          </div>
        );
        TopModal.show(view);
      }
      function close() {
        TopModal.close();
      }
      return (
        <div>
          <button onClick={openModal}>打开</button>
        </div>
      );
    }
    

    弹出框的内容很简单,只有一个按钮,用于关闭当前Modalshow方法将内容区整个div传入。

    效果:

到这里,一个简易版本的Modal已经有了初步效果。

扩展方向

将要显示的内容做成一个组件,直接将组件view传入Modal中显示。这样的方式,可以做成对话框,Toast,各种提示。这其中可能还需要对背景色作出修改,这些也可以通过函数参数的方式传入到组件中来处理。

也可以将布局的关键属性传入,组件决定内容区显示在中间还是顶部,还是底部。由此再可以扩展出actionSheet组件。

可以将API设计成show(view,config)的形式,读取config的内容,动态设置样式和布局显示。

通过React的方式添加的高层级view显示,这一思路完全适用于React Native