diff --git a/packages/api-proxy/src/common/js/utils.js b/packages/api-proxy/src/common/js/utils.js index 44ce3bfb63..d1bf81606d 100644 --- a/packages/api-proxy/src/common/js/utils.js +++ b/packages/api-proxy/src/common/js/utils.js @@ -1,4 +1,5 @@ import { hasOwn, noop, getEnvObj, getFocusedNavigation } from '@mpxjs/utils' +import { getCurrentInstance } from '@mpxjs/core' /** * @@ -87,6 +88,14 @@ function failHandle (result, fail, complete) { typeof complete === 'function' && complete(result) } +function getPageId () { + const navigation = getFocusedNavigation() + const currentInstance = getCurrentInstance() + console.log(currentInstance, currentInstance?.getPageId, navigation, navigation?.pageId) + const id = currentInstance?.getPageId || navigation?.pageId || null + return id +} + const ENV_OBJ = getEnvObj() export { @@ -101,5 +110,6 @@ export { defineUnsupportedProps, successHandle, failHandle, - getFocusedNavigation + getFocusedNavigation, + getPageId } diff --git a/packages/api-proxy/src/platform/api/action-sheet/rnActionSheet.jsx b/packages/api-proxy/src/platform/api/action-sheet/rnActionSheet.jsx index 9bb81896aa..50c8fd2fc6 100644 --- a/packages/api-proxy/src/platform/api/action-sheet/rnActionSheet.jsx +++ b/packages/api-proxy/src/platform/api/action-sheet/rnActionSheet.jsx @@ -1,5 +1,5 @@ import { View, Text, StyleSheet } from 'react-native' -import { successHandle, failHandle } from '../../../common/js' +import { successHandle, failHandle, getPageId, error } from '../../../common/js' import Portal from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-portal/index' import { getWindowInfo } from '../system/rnSystem' import Animated, { @@ -9,7 +9,16 @@ import Animated, { } from 'react-native-reanimated' function showActionSheet (options = {}) { + const id = getPageId() const { alertText, itemList = [], itemColor = '#000000', success, fail, complete } = options + if (id === null) { + error('showActionSheet cannot be invoked outside the mpx life cycle in React Native environments') + const result = { + errMsg: 'showActionSheet:fail cannot be invoked outside the mpx life cycle in React Native environments' + } + failHandle(result, fail, complete) + return + } if (itemList.length > 6) { const result = { errMsg: 'showActionSheet:fail parameter error: itemList should not be large than 6' @@ -116,8 +125,7 @@ function showActionSheet (options = {}) { ) } - - actionSheetKey = Portal.add() + actionSheetKey = Portal.add(, id) } export { diff --git a/packages/api-proxy/src/platform/api/modal/rnModal.jsx b/packages/api-proxy/src/platform/api/modal/rnModal.jsx index eaee365bbc..a82eb7d07a 100644 --- a/packages/api-proxy/src/platform/api/modal/rnModal.jsx +++ b/packages/api-proxy/src/platform/api/modal/rnModal.jsx @@ -1,8 +1,9 @@ import { View, Dimensions, Text, StyleSheet, TouchableOpacity, ScrollView, TextInput } from 'react-native' -import { successHandle, failHandle } from '../../../common/js' +import { successHandle, failHandle, getPageId, error } from '../../../common/js' import Portal from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-portal/index' const { width, height } = Dimensions.get('window') const showModal = function (options = {}) { + const id = getPageId() const { title, content, @@ -17,6 +18,14 @@ const showModal = function (options = {}) { fail, complete } = options + if (id === null) { + error('showModal cannot be invoked outside the mpx life cycle in React Native environments') + const result = { + errMsg: 'showModal:fail cannot be invoked outside the mpx life cycle in React Native environments' + } + failHandle(result, fail, complete) + return + } const modalWidth = width * 0.8 const styles = StyleSheet.create({ modalTask: { @@ -156,7 +165,7 @@ const showModal = function (options = {}) { try { - modalKey = Portal.add(ModalView) + modalKey = Portal.add(ModalView, id) } catch (e) { const result = { errMsg: `showModal:fail invalid ${e}` diff --git a/packages/api-proxy/src/platform/api/route/index.ios.js b/packages/api-proxy/src/platform/api/route/index.ios.js index b8a0a9b93c..653f87713b 100644 --- a/packages/api-proxy/src/platform/api/route/index.ios.js +++ b/packages/api-proxy/src/platform/api/route/index.ios.js @@ -33,7 +33,7 @@ function resolvePath (relative, base) { } return stack.join('/') } -function isLock(navigationHelper, type, options) { +function isLock (navigationHelper, type, options) { if (navigationHelper.lastSuccessCallback && navigationHelper.lastFailCallback) { const res = { errMsg: `${type}:fail the previous routing event didn't complete` } failHandle(res, options.fail, options.complete) @@ -48,7 +48,7 @@ function isLock(navigationHelper, type, options) { return false } -function navigateTo (options = {}) { +function navigateTo (options = {}) { const navigationHelper = global.__navigationHelper if (isLock(navigationHelper, 'navigateTo', options)) { return diff --git a/packages/api-proxy/src/platform/api/toast/rnToast.jsx b/packages/api-proxy/src/platform/api/toast/rnToast.jsx index 6942f186d2..2909255154 100644 --- a/packages/api-proxy/src/platform/api/toast/rnToast.jsx +++ b/packages/api-proxy/src/platform/api/toast/rnToast.jsx @@ -1,5 +1,5 @@ import { View, Text, Image, StyleSheet, ActivityIndicator, Dimensions } from 'react-native' -import { successHandle, failHandle } from '../../../common/js' +import { successHandle, failHandle, getPageId, error } from '../../../common/js' import Portal from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-portal/index' let toastKey @@ -55,7 +55,16 @@ const styles = StyleSheet.create({ }) function showToast (options = {}) { + const id = getPageId() const { title, icon = 'success', image, duration = 1500, mask = false, success, fail, complete, isLoading } = options + if (id === null) { + error('showToast cannot be invoked outside the mpx life cycle in React Native environments') + const result = { + errMsg: 'showToast:fail cannot be invoked outside the mpx life cycle in React Native environments' + } + failHandle(result, fail, complete) + return + } let ToastView const successPng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFwAAABcAgMAAACegTrLAAAADFBMVEUAAAD////R0dHj4+PcME2AAAAAAXRSTlMAQObYZgAAANNJREFUSMft1FEKwkAMBNBGyBFyH4/gR8f+ehSPLtiyQ+wM2C8Rmp+WQN90N8tOZ/1/4SbbYfpp+sBVMqafgGQKs+FvkjF8GR6aj4M8NB+GT8OX5i+GT8OX4aH5MHwZHpoPw+c3fA0xGl/zYBrP92o8vwVwF3NO8tySjXmINQLAs/WX9S8bP9UGJUZSi8MAuUqsj75payPxuWn1BmrjewCCfAvA4Fmjvx8HQL4HiJEHeRmg5k2+B8gTFeRFwL7NzREB/sCKwsG7KN2VSb7XMp31i3oBatNdEForTOoAAAAASUVORK5CYII=' const errorPng = 'data:image/png;base64,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' @@ -101,7 +110,7 @@ function showToast (options = {}) { if (toastKey) { Portal.remove(toastKey) } - toastKey = Portal.add(ToastView) + toastKey = Portal.add(ToastView, id) if (!isLoading) { tId = setTimeout(() => { Portal.remove(toastKey) diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 0189823b53..7745dc25e9 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -489,7 +489,6 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { const currentPageId = useMemo(() => ++pageId, []) const intersectionObservers = useRef({}) usePageStatus(navigation, currentPageId) - useLayoutEffect(() => { const isCustom = pageConfig.navigationStyle === 'custom' navigation.setOptions({ @@ -566,7 +565,9 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { value: intersectionObservers.current }, createElement(Provider, - null, + { + pageId: currentPageId + }, createElement(defaultOptions, { navigation, diff --git a/packages/webpack-plugin/lib/runtime/components/react/context.ts b/packages/webpack-plugin/lib/runtime/components/react/context.ts index 0bc4d57cd9..5355012d96 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/context.ts +++ b/packages/webpack-plugin/lib/runtime/components/react/context.ts @@ -40,7 +40,7 @@ export interface PortalManagerContextValue { } export interface PortalContextValue { - mount: (children: React.ReactNode, key?: number) => number| undefined + mount: (children: React.ReactNode, key?: number, id?: number) => number| undefined update: (key: number, children: React.ReactNode) => void unmount: (key: number) => void manager?: PortalManagerContextValue diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-portal/index.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-portal/index.tsx index 02a82a0d65..d8bf1354a5 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-portal/index.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-portal/index.tsx @@ -1,6 +1,7 @@ import { ReactNode } from 'react' import { PortalContext, PortalContextValue } from '../context' import PortalConsumer from './portal-consumer' +import { useNavigation } from '@react-navigation/native' import PortalHost, { portal } from './portal-host' export type PortalProps = { @@ -9,10 +10,13 @@ export type PortalProps = { */ children?: ReactNode key?: string - manager?: PortalContextValue + manager?: PortalContextValue, + pageId?: number } const Portal = ({ children }:PortalProps): JSX.Element => { + const navigation = useNavigation() + const pageId = navigation?.pageId return ( {(manager) => ( diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-portal/portal-host.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-portal/portal-host.tsx index 81134e372a..724dfca79f 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-portal/portal-host.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-portal/portal-host.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, ReactNode } from 'react' +import { useEffect, useRef, ReactNode, useMemo } from 'react' import { View, DeviceEventEmitter, @@ -11,7 +11,8 @@ import { useNavigation } from '@react-navigation/native' import { PortalManagerContextValue, PortalContext } from '../context' export type PortalHostProps = { - children: ReactNode + children: ReactNode, + pageId: number } type addIdsMapsType = { @@ -38,9 +39,9 @@ const styles = StyleSheet.create({ class PortalGuard { private nextKey = 10000 - add = (e: ReactNode) => { + add = (e: ReactNode, id: number) => { const key = this.nextKey++ - TopViewEventEmitter.emit(addType, e, key) + TopViewEventEmitter.emit(addType, e, key, id) return key } @@ -57,17 +58,48 @@ class PortalGuard { */ export const portal = new PortalGuard() -const PortalHost = ({ children } :PortalHostProps): JSX.Element => { +const PortalHost = ({ children, pageId } :PortalHostProps): JSX.Element => { + const isMounted = useRef(false) const _nextKey = useRef(0) const _addType = useRef(null) const _removeType = useRef(null) const _updateType = useRef(null) const manager = useRef(null) - const focusState = useRef(false) - const _mount = (children: ReactNode, _key?: number) => { - if (!focusState.current) { - return + const queue = useRef>([]) + const _mount = (children: ReactNode, _key?: number, id?: number) => { + if (id !== pageId) return + const key = _key || _nextKey.current++ + if (!isMounted.current) { + queue.current.push({ type: 'mount', key, children }) + } else if (manager.current) { + manager.current.mount(key, children) + } + return key + } + + const _unmount = (key: number) => { + if (!isMounted.current) { + queue.current.push({ type: 'unmount', key, children }) + } else if (manager.current) { + manager.current.unmount(key) + } + } + + const _update = (key: number, children?: ReactNode) => { + if (!isMounted.current) { + const operation = { type: 'mount', key, children } + const index = queue.current.findIndex((q) => q.key === key) + if (index > -1) { + queue.current[index] = operation + } else { + queue.current.push(operation) + } + } else if (manager.current) { + manager.current.update(key, children) } + } + + const mount = (children: ReactNode, _key?: number) => { const key = _key || _nextKey.current++ if (manager.current) { manager.current.mount(key, children) @@ -75,27 +107,38 @@ const PortalHost = ({ children } :PortalHostProps): JSX.Element => { return key } - const _unmount = (key: number) => { + const unmount = (key: number) => { if (manager.current) { manager.current.unmount(key) } } - const _update = (key: number, children?: ReactNode, curPageId?: number) => { + const update = (key: number, children?: ReactNode) => { if (manager.current) { manager.current.update(key, children) } } - const navigation = useNavigation() - useEffect(() => { + + useMemo(() => { _addType.current = TopViewEventEmitter.addListener(addType, _mount) _removeType.current = TopViewEventEmitter.addListener(removeType, _unmount) _updateType.current = TopViewEventEmitter.addListener(updateType, _update) + }, []) + const navigation = useNavigation() + useEffect(() => { + while (queue.current.length && manager.current) { + const operation = queue.current.shift() + if (!operation) return + switch (operation.type) { + case 'mount': + manager.current.mount(operation.key, operation.children) + break + case 'unmount': + manager.current.unmount(operation.key) + } + } const focusSubscription = navigation.addListener('focus', () => { - focusState.current = true - }) - const blurSubscription = navigation.addListener('blur', () => { - focusState.current = false + isMounted.current = true }) return () => { @@ -103,15 +146,14 @@ const PortalHost = ({ children } :PortalHostProps): JSX.Element => { _removeType.current?.remove() _updateType.current?.remove() focusSubscription() - blurSubscription() } }, []) return (