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