ReactNativePopup实现示例

React Native 官方提供了 Modal 组件,但 Modal 是属于全屏的弹出层,当 Modal 显示时,操作区域只有 Modal 里的元素,而且焦点会被 Modal 劫持。虽然移动端不常见,但有些场景还是希望可以用轻量级一点的 Popup。

在 React Native 里,元素的层级是不可以被穿透的,子元素无论如何都不能遮挡父元素。所以选择了在顶层添加 Popup,设置绝对定位,显示时根据指定元素来动态调整 Popup 的位置的方案。

 

具体实现

Popup 会有显示或隐藏两种状态,使用一个 state 来控制。

const Component = () => {
const [visible, setVisible] = useState(false);

return (
  <>
    {visible && <></>}
  </>
);
};

Popup 的 属于视图类组件,UI 结构包括:

  • 一个作为容器的 View,由于 iOS 有刘海,所以在 iOS 上需要使用 SafeAreaView 来避免被刘海遮挡。同时添加一个点击事件监听当点击时关闭 Popup 。
  • 一个指向目标对象的三角形。
  • 一个包裹内容的 View。

由于 Popup 的位置和内容是动态的,所以需要两个 state 存储相关数据。

  • 一个存储位置相关的 CSS。
  • 一个存储动态内容。
const Component = ({ style, ...other }) => {
const [visible, setVisible] = useState(false);
const [popupStyle, setPopupStyle] = useState({});
const [content, setContent] = useState(null);

const onPress = useCallback(() => {
  setVisible(false);
}, []);

return (
  <>
    {visible &&
      createElement(
        Platform.OS === 'ios' ? SafeAreaView : View,
        {
          style: {
            ...styles.popup,
            ...popupStyle,
          },
        },
        <TouchableOpacity onPress={onPress}>
          <View style={styles.triangle} />
          <View style={{ ...styles.content, ...style }} {...other}>
            {content}
          </View>
        </TouchableOpacity>,
      )}
  </>
);
};

const styles = StyleSheet.create({
popup: {
  position: 'absolute',
  zIndex: 99,
  shadowColor: '#333',
  shadowOpacity: 0.12,
  shadowOffset: { width: 2 },
  borderRadius: 4,
},
triangle: {
  width: 0,
  height: 0,
  marginLeft: 12,
  borderLeftWidth: 8,
  borderLeftColor: 'transparent',
  borderRightWidth: 8,
  borderRightColor: 'transparent',
  borderBottomWidth: 8,
  borderBottomColor: 'white',
},
content: {
  backgroundColor: 'white',
},
});

因为是全局的 Popup,所以选择了一个全局变量来提供 Popup 相关的操作方法。

如果全局 Popup 不适用,可以改成在需要时插入 Popup 并使用 ref 来提供操作方法。

目标元素,动态内容和一些相关的可选配置都是在调用 show 方法时通过参数传入的,

useEffect(() => {
global.$popup = {
  show: (triggerRef, render, options = {}) => {
    const { x: offsetX = 0, y: offsetY = 0 } = options.offset || {};
    triggerRef.current.measure((x, y, width, height, left, top) => {
      setPopupStyle({
        top: top + height + offsetY,
        left: left + offsetX,
      });
      setContent(render());
      setVisible(true);
    });
  },
  hide: () => {
    setVisible(false);
  },
};
}, []);

完整代码

import React, {
createElement,
forwardRef,
useState,
useEffect,
useCallback,
} from 'react';
import PropTypes from 'prop-types';
import {
View,
SafeAreaView,
Platform,
TouchableOpacity,
StyleSheet,
} from 'react-native';

const Component = ({ style, ...other }, ref) => {
const [visible, setVisible] = useState(false);
const [popupStyle, setPopupStyle] = useState({});
const [content, setContent] = useState(null);

const onPress = useCallback(() => {
  setVisible(false);
}, []);

useEffect(() => {
  global.$popup = {
    show: (triggerRef, render, options = {}) => {
      const { x: offsetX = 0, y: offsetY = 0 } = options.offset || {};
      triggerRef.current.measure((x, y, width, height, left, top) => {
        setPopupStyle({
          top: top + height + offsetY,
          left: left + offsetX,
        });
        setContent(render());
        setVisible(true);
      });
    },
    hide: () => {
      setVisible(false);
    },
  };
}, []);

return (
  <>
    {visible &&
      createElement(
        Platform.OS === 'ios' ? SafeAreaView : View,
        {
          style: {
            ...styles.popup,
            ...popupStyle,
          },
        },
        <TouchableOpacity onPress={onPress}>
          <View style={styles.triangle} />
          <View style={{ ...styles.content, ...style }} {...other}>
            {content}
          </View>
        </TouchableOpacity>,
      )}
  </>
);
};

Component.displayName = 'Popup';

Component.prototype = {};

const styles = StyleSheet.create({
popup: {
  position: 'absolute',
  zIndex: 99,
  shadowColor: '#333',
  shadowOpacity: 0.12,
  shadowOffset: { width: 2 },
  borderRadius: 4,
},
triangle: {
  width: 0,
  height: 0,
  marginLeft: 12,
  borderLeftWidth: 8,
  borderLeftColor: 'transparent',
  borderRightWidth: 8,
  borderRightColor: 'transparent',
  borderBottomWidth: 8,
  borderBottomColor: 'white',
},
content: {
  backgroundColor: 'white',
},
});

export default forwardRef(Component);

 

使用方法

  • 在入口文件页面内容的末尾插入 Popup 元素。

    // App.jsx
    import Popup from './Popup';
    
    const App = () => {
    return (
      <>
        ...
        <Popup />
      </>
    );
    };
  • 使用全局变量控制。

    // 显示
    $popup.show();
    // 隐藏
    $popup.hide();

关于React Native Popup实现示例的文章就介绍至此,更多相关React Native Popup内容请搜索编程宝库以前的文章,希望以后支持编程宝库

 介绍这个项目是一个商城的后台管理系统,用umi2.0搭建,状态管理使用dva,想要实现类似vue keep-alive的效果。具体表现为:从列表页A跳转A的 ...