One, foreword

In daily development, the use of Modal is essential, but if a page has too many Modal popover, the use of Modal must be declared in the Render () method, which is definitely a scary thing, as in the following scenario

   render() {
    if (this.state.isLoading) {
      return (
        <View style={{flex: 1}} >
          <LoadingView />
        </View>
      );
    } else {
      return (
        <SafeAreaView style={styles.main} forceInset={{top: 'never', bottom: 'always'}} >
          <ScrollView
            style={{flex: 1.backgroundColor: StyleConfig.color_background}}
            contentContainerStyle={{paddingTop: 0.paddingBottom: 50 + Global.bottomOfSafeArea}}
            automaticallyAdjustContentInsets={false}
            keyboardShouldPersistTaps="handled"
            showsHorizontalScrollIndicator={false}
            removeClippedSubviews={true}
            showsVerticalScrollIndicator={false}>
            <View>
              <PersonalDataView
                props={this.props}
                isAdd={true}
                relationSex={this.state.relationSex}
                relation={this.state.relation}
                imageUrl={this.state.imageUrl}
                userInfo={this.state.userInfo}
                isFamilyMemeber={RelationShipMap.isFamilyMember(this.state.relation)}
                loginType={this.state.loginType}
                uploadImgCallBack={(fileId, picUrl) = >{ this.fileId = fileId; this.setState({imageUrl: picUrl}); }} selectRelationCallback={relation => { this.isCheckMember = true; if (relation ! = null) { this.setState({relation: Number.parseInt(relation)}); this.setAddress(relation); this.getFamilyMemberByRelation(relation); } }} idCardChangeCallBack={idCardNo => { this.setState({relationSex: Common.isMaleFromIdCard(idCardNo) ? 1:0}); let value = {}; value[FamilyMemberKey.IDCARD] = idCardNo; this.props.form.setFieldsValue(value); }} / > {/ * whether to join the peace pipe * /} {RelationShipMap. IsFamilyMember (this) state) base) && (<JoinAnxinView
                  style={{marginTop: 15}}
                  isJoinAnxin={this.isJoinAnxin}
                  promptText={'Adding this member to the comfort tube may cause a change in the value of the family's resilience to risk '}joinAnxinChange={checked= >{ this.isJoinAnxin = checked; }}} / >)<View style={{height: 44.alignItems: 'center', marginTop: 30}} >
                <XLargeButton
                  onPress={()= >{ if (! RelationShipMap.isFamilyMember(this.state.relation)) { this.isJoinAnxin = false; } if (this.checkParams()) { if (this.isCheckMember) { this.checkMember(); } else { let forms = this.props.form.getFieldsValue(); this.updateMember(this.insurantId, forms); }}}}> Save</XLargeButton>
              </View>
            </View>
            <Text
              style={{
                alignSelf: 'center',
                marginTop: 15.marginLeft: 20.marginRight: 20.marginBottom: 20.color: '#Awesome!',
                fontSize: 13,
              }}>The above information is only used for xinyizhan insurance business, we will keep it strictly confidential.</Text>
          </ScrollView>{/* Operation is my prompt popup */}<Modal
            transparent={true}
            visible={this.state.modalMeVisiable}
            onRequestClose={()= >{ this.setState({modalMeVisiable: false}); }} ><View style={styles.modalStyle}>
              <AnXinDialog
                message={'Id number already in use '}isHideCancale={true}
                okEvent={()= >{ this.setState({modalMeVisiable: false}); }} / ></View>
          </Modal>{/* Overwrite prompt popover */}<Modal
            transparent={true}
            visible={this.state.modalCoverVisiable}
            onRequestClose={()= >{ this.setState({modalCoverVisiable: false}); }} ><View style={styles.modalStyle}>
              <AnXinDialog
                message={'The ID number already exists in the family member, if you choose to update, we will update the member's information, select the button to discard and update. '}isHideCancale={false}
                cancelEvent={()= >{ UMengBridge.log(AddFamilyMember.AddFamilyMemberCancle); this.setState({modalCoverVisiable: false}); this.insurantId = ''; }} okEvent={() => { UMengBridge.log(AddFamilyMember.AddFamilyMemberUpdate); this.setState({modalCoverVisiable: false}); this.updateMember(this.insurantId, this.forms); }} / ></View>
          </Modal>{/* Error popup */}<Modal
            transparent={true}
            visible={this.state.modalErrorVisiable}
            onRequestClose={()= >{ this.setState({modalErrorVisiable: false}); }} ><View style={styles.modalStyle}>
              <AnXinDialog
                message={this.state.errorMsg}
                isHideCancale={true}
                okEvent={()= >{ this.setState({modalErrorVisiable: false}); }} / ></View>
          </Modal>{/* Select existing contact */}<Modal
            transparent={true}
            visible={this.state.modalSelectMember}
            onRequestClose={()= > {
              this.setState({modalSelectMember: false});
            }}>
            {this.getSelectFamilyMember()}
          </Modal>
          <XWaitingHUD ref="hud" isVisible={this.state.showHUD} />
        </SafeAreaView>); }}Copy the code

You can see most of the above code length, all Modal declarations, code readability is extremely poor!

Two, the implementation principle

So is there a way to optimize this? The answer is yes, of course, because there are existing schemes, such as AntDesign for Modal, which internally calls static methods to achieve Modal pop-ups, so let’s see how it does that. First locate the internal implementation of the key code: modal.alert ()

import React from 'react';
import Portal from '.. /portal';
import AlertContainer from './AlertContainer';
import { Action, CallbackOnBackHandler } from './PropsType';

export default function a(
  title: React.ReactNode,
  content: React.ReactNode,
  actions: Action[] = [{ text: 'sure'}], onBackHandler? : CallbackOnBackHandler,) {
  const key = Portal.add(
    <AlertContainer
      title={title}
      content={content}
      actions={actions}
      onAnimationEnd={(visible: boolean) = >{ if (! visible) { Portal.remove(key); } }} onBackHandler={onBackHandler} />,);return key;
}

Copy the code

As you can see there is a key class Portal here, continue to see how portal.add () is implemented:

class PortalGuard {
  private nextKey = 10000;
  add = (e: React.ReactNode) = > {
    const key = this.nextKey++;
    TopViewEventEmitter.emit(addType, e, key);
    return key;
  };
  remove = (key: number) = > TopViewEventEmitter.emit(removeType, key);
}
Copy the code

Here you can see that a global notification is sent. Let’s continue to locate the method for handling notifications:

export default class PortalHost extends React.Component<PortalHostProps> {
  static displayName = 'Portal.Host';

  _nextKey = 0; _queue: Operation[] = []; _manager? : PortalManager;componentDidMount() {
    const manager = this._manager;
    const queue = this._queue;

    TopViewEventEmitter.addListener(addType, this._mount);
    TopViewEventEmitter.addListener(removeType, this._unmount);

    while (queue.length && manager) {
      const action = queue.pop();
      if(! action) {continue;
      }
      // tslint:disable-next-line:switch-default
      switch (action.type) {
        case 'mount':
          manager.mount(action.key, action.children);
          break;
        case 'update':
          manager.update(action.key, action.children);
          break;
        case 'unmount':
          manager.unmount(action.key);
          break; }}}componentWillUnmount() {
    TopViewEventEmitter.removeListener(addType, this._mount);
    TopViewEventEmitter.removeListener(removeType, this._unmount);
  }
  _setManager = (manager? : any) = > {
    this._manager = manager;
  };

  _mount = (children: React.ReactNode, _key? : number) = > {
    const key = _key || this._nextKey++;
    if (this._manager) {
      this._manager.mount(key, children);
    } else {
      this._queue.push({ type: 'mount', key, children });
    }

    return key;
  };

  _update = (key: number, children: React.ReactNode) = > {
    if (this._manager) {
      this._manager.update(key, children);
    } else {
      const op: Operation = { type: 'mount', key, children };
      const index = this._queue.findIndex(
        o= > o.type === 'mount' || (o.type === 'update' && o.key === key),
      );

      if (index > -1) {
        this._queue[index] = op;
      } else {
        this._queue.push(op); }}}; _unmount =(key: number) = > {
    if (this._manager) {
      this._manager.unmount(key);
    } else {
      this._queue.push({ type: 'unmount', key }); }};render() {
    return (
      <PortalContext.Provider
        value={{
          mount: this._mount.update: this._update.unmount: this._unmount,}} >
        {/* Need collapsable=false here to clip the elevations, otherwise they appear above Portal components */}
        <View style={styles.container} collapsable={false}>
          {this.props.children}
        </View>
        <PortalManager ref={this._setManager} />
      </PortalContext.Provider>); }}Copy the code

Here we can see that add() finally goes to this._manager.mount(key, children); In this method, we further analyze the methods in PortalManager,

import React from 'react';
import { View, StyleSheet } from 'react-native';
export type State = {
  portals: ArrayThe < {key: number; children: React.ReactNode; } >. };export type PortalManagerState = {
  portals: any[];
};
/** * Portal host is the component which actually renders all Portals. */
export default class PortalManager extends React.PureComponent<
  {},
  PortalManagerState
> {
  state: State = {
    portals: [],}; mount =(key: number, children: React.ReactNode) = > {
    this.setState(state= > ({
      portals: [...state.portals, { key, children }],
    }));
  };
  update = (key: number, children: React.ReactNode) = >
    this.setState(state= > ({
      portals: state.portals.map(item= > {
        if (item.key === key) {
          return { ...item, children };
        }
        returnitem; })})); unmount =(key: number) = >
    this.setState(state= > ({
      portals: state.portals.filter(item= >item.key ! == key), }));render() {
    return this.state.portals.map(({ key, children }, i) = > (
      <View
        key={key}
        collapsable={
          false/ *Need collapsable=false here to clip the elevations.otherwise they appear above sibling components */
        }
        pointerEvents="box-none"
        style={[StyleSheet.absoluteFill, { zIndex: 1000 + i }]}
      >
        {children}
      </View>)); }}Copy the code

You can see that these are maintaining an array of Portals, and you should update this array of Portals to implement Modal implicit. Using zIndex to control the hierarchy’s priority, use the absolute layout StyleSheet. AbsoluteFill to float at the top. So here we just found the place to draw, where do we draw the port Manager? Go up through the IDE layer by layer and finally locate here:

import * as React from 'react';
import LocaleProvider, { Locale } from '.. /locale-provider';
import Portal from '.. /portal';
import { Theme, ThemeProvider } from '.. /style';
exportinterface ProviderProps { locale? : Partial<Locale>; theme? : Partial<Theme>; }export default class Provider extends React.Component<ProviderProps> {
  render() {
    return (
      <LocaleProvider locale={this.props.locale}>
        <ThemeProvider value={this.props.theme}>
          <Portal.Host>{this.props.children}</Portal.Host>
        </ThemeProvider>
      </LocaleProvider>); }}Copy the code

This is the top-level Provider view of AntDesign. To implement AntDesign’s popup view that calls static methods directly in the project, we must use the Provider control to wrap our App, like this:

    render() {
      const Router = this.Router;
      return (
        <View style={{flex: 1}} >
          <Provider>
            {Platform.OS === 'ios' ? <StatusBar barStyle={this.state.barStyle} /> : null}
            <Router
              screenProps={this.props}
              {. getPersistenceFunctions(pageName)}
              renderLoadingExperimental={()= > <ActivityIndicator />}
              onNavigationStateChange={this.onNavigationStateChange.bind(this)}
            />
          </Provider>
        </View>
      );
    }
Copy the code

Third, optimization

As analyzed in the previous section, if we want to implement AntDesign’s method of directly calling the pop-up view of static methods, we just need to use the Provider control to wrap the topmost view of our App, and then call portal.add () anywhere to achieve this effect. To make this popover easier, we define a utility class:

import {Portal} from '@ant-design/react-native';
import BaseDialog from '.. /components/views/BaseDialog';
import React from 'react';

/** * Notes: Universal popover builder * Time: 2020/9/30 0030 15:37 *@author Guo Hanlin * /
export default class DialogUtil {
  public containView: JSX.Element;
  public okText: string;
  public okEvent: Function; public cancelText? : string; public cancelEvent? :Function;
  /** * Comments: view Key * time: 2020/9/30 0030 15:35 *@author Guo Hanlin * /
  private key: number;

  /** * Note: display * time: 2020/9/30 0030 15:36 *@author Guo Hanlin * /
  public show() {
    this.key = Portal.add(
      <BaseDialog
        cancelEvent={this.cancelEvent}
        cancelText={this.cancelText}
        containView={this.containView}
        okEvent={this.okEvent}
        okText={this.okText}
      />,); }/** * Comments: hidden * Time: 2020/9/30 0030 15:36 *@author Guo Hanlin * /
  public hidden() {
    Portal.remove(this.key); }}Copy the code

This BaseDialog over here is a generic Modal popover control in our business, and you can pass in any view you want to customize. The implementation is as follows:

/** * Comments: General base popup * time: 2020/5/190019 13:46 *@author Guo Hanlin * /
export default function BaseDialog(props: Props) {
  const [modalVisible, setModalVisible] = useState(true);
  return (
    <Modal
      animationType={'none'}
      onRequestClose={()= > {
        setModalVisible(false);
      }}
      transparent={true}
      visible={modalVisible}>
      <View
        style={{
          backgroundColor: 'rgba(0.0.0.0.5) ',alignItems: 'center',
          justifyContent: 'center',
          flex: 1,
        }}>
        <View style={styles.mainStyle}>The gradient * /} {/ *<LinearGradient
            start={{x: 0.0.y: 0.5}}
            end={{x: 1.0.y: 0.5}}
            colors={['#fc704e', '#ff9547']}
            style={{
              flexDirection: 'row',
              borderTopLeftRadius: 8.borderTopRightRadius: 8.overflow: 'hidden'}} >
            <View style={{flex: 1.height: 4}} / >
          </LinearGradient>{/* content area */} {props. ContainView} {/* Confirm button */}<TouchableOpacity
            style={styles.okButtonStyle}
            onPress={()= >{ setModalVisible(false); props.okEvent && props.okEvent(); }} ><LinearGradient
              style={styles.okLinearGradientStyle}
              start={{x: 0.0.y: 0.5}}
              end={{x: 1.0.y: 0.5}}
              colors={['#fc704e', '#ff9547']} >
              <Text style={{fontSize: 16.color: '#ffffff', lineHeight: 22}} >{props.okText}</Text>
            </LinearGradient>
          </TouchableOpacity>{/* Cancel button */}<TouchableOpacity
            onPress={()= >{ setModalVisible(false); props.cancelEvent && props.cancelEvent(); }} ><Text style={styles.cancelButtonStyle}>{props.cancelText ? CancelText: 'cancel '}</Text>
          </TouchableOpacity>
        </View>
      </View>
    </Modal>
  );
}
Copy the code

Finally, to display pop-ups in code, you don’t have to explicitly declare Modal pop-ups as you did in Chapter 1, you just call them like this:

const dialog = new DialogUtil();
dialog.okText = 'Go open';
dialog.okEvent = () = > {
CommonBridge.gotoNotificationSetting();
dialog.hidden();
};
dialog.containView = renderNotificationTip();
dialog.cancelEvent = () = > {
RouterPageBridge.gotoRouterSkipSystem(RouterUri.MessageCenterPage);
dialog.hidden();
};
dialog.show();
Copy the code