import { useState, ComponentType, useEffect, useCallback, useRef, ReactNode, createElement } from 'react'
import { View, Image, StyleSheet, Text, TouchableOpacity } from 'react-native'
import { AnyFunc } from './types/common'
const asyncChunkMap = new Map()
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#fff'
},
loadingImage: {
width: 100,
height: 100,
marginTop: 220,
alignSelf: 'center'
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '500',
textAlign: 'center'
},
errorImage: {
marginTop: 80,
width: 220,
aspectRatio: 1,
alignSelf: 'center'
},
errorText: {
fontSize: 16,
textAlign: 'center',
color: '#333',
marginBottom: 20
},
retryButton: {
position: 'absolute',
bottom: 54,
left: 20,
right: 20,
backgroundColor: '#fff',
paddingVertical: 15,
borderRadius: 30,
marginTop: 40,
borderWidth: 1,
borderColor: '#FF5F00'
},
retryButtonText: {
color: '#FF5F00',
fontSize: 16,
fontWeight: '500',
textAlign: 'center'
}
})
interface DefaultFallbackProps {
onReload: () => void
}
const DefaultFallback = ({ onReload }: DefaultFallbackProps) => {
return (
网络出了点问题,请查看网络环境
点击重试
)
}
const DefaultLoading = () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const FastImageModule = require('@d11/react-native-fast-image')
const FastImage = FastImageModule.default || FastImageModule
return (
)
}
interface AsyncSuspenseProps {
type: 'component' | 'page'
chunkName: string
moduleId: string
innerProps: any,
getLoading?: () => ComponentType
getFallback?: () => ComponentType
getChildren: () => Promise
}
type ComponentStauts = 'pending' | 'error' | 'loaded'
const AsyncSuspense: React.FC = ({
type,
chunkName,
moduleId,
innerProps,
getLoading,
getFallback,
getChildren
}) => {
const [status, setStatus] = useState('pending')
const chunkLoaded = asyncChunkMap.has(moduleId)
const loadChunkPromise = useRef>(null)
const reloadPage = useCallback(() => {
setStatus('pending')
}, [])
useEffect(() => {
let cancelled = false
if (!chunkLoaded && status === 'pending') {
if (loadChunkPromise.current) {
loadChunkPromise
.current.then((res: ReactNode) => {
if (cancelled) return
asyncChunkMap.set(moduleId, res)
setStatus('loaded')
})
.catch((e) => {
if (cancelled) return
if (type === 'component') {
global.__mpxAppCbs.lazyLoad.forEach((cb: AnyFunc) => {
// eslint-disable-next-line node/no-callback-literal
cb({
type: 'subpackage',
subpackage: [chunkName],
errMsg: `loadSubpackage: ${e.type}`
})
})
}
if (type === 'page' && typeof mpxGlobal.__mpx.config?.rnConfig?.onLazyLoadPageError === 'function') {
mpxGlobal.__mpx.config.rnConfig.onLazyLoadPageError({
subpackage: chunkName,
errType: e.type
})
}
loadChunkPromise.current = null
setStatus('error')
})
}
}
return () => {
cancelled = true
}
}, [status])
if (chunkLoaded) {
const Comp = asyncChunkMap.get(moduleId)
return createElement(Comp, innerProps)
} else if (status === 'error') {
if (type === 'page') {
const fallback = getFallback ? getFallback() : DefaultFallback
return createElement(fallback as ComponentType, { onReload: reloadPage })
} else {
return getFallback ? createElement(getFallback(), innerProps) : null
}
} else {
if (!loadChunkPromise.current) {
loadChunkPromise.current = getChildren()
}
if (type === 'page') {
const loading = getLoading ? getLoading() : DefaultLoading
return createElement(loading)
} else {
return getFallback ? createElement(getFallback(), innerProps) : null
}
}
}
AsyncSuspense.displayName = 'MpxAsyncSuspense'
export default AsyncSuspense