@mpxjs/webpack-plugin 2.10.6 → 2.10.7-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/lib/file-loader.js +1 -1
  2. package/lib/index.js +41 -13
  3. package/lib/platform/json/wx/index.js +43 -26
  4. package/lib/platform/template/wx/component-config/button.js +1 -1
  5. package/lib/platform/template/wx/component-config/fix-component-name.js +2 -2
  6. package/lib/platform/template/wx/component-config/index.js +5 -1
  7. package/lib/platform/template/wx/component-config/input.js +1 -1
  8. package/lib/platform/template/wx/component-config/sticky-header.js +23 -0
  9. package/lib/platform/template/wx/component-config/sticky-section.js +23 -0
  10. package/lib/platform/template/wx/index.js +2 -1
  11. package/lib/react/LoadAsyncChunkModule.js +68 -0
  12. package/lib/react/index.js +3 -1
  13. package/lib/react/processJSON.js +68 -12
  14. package/lib/react/processScript.js +4 -3
  15. package/lib/react/script-helper.js +92 -18
  16. package/lib/runtime/components/react/AsyncContainer.tsx +217 -0
  17. package/lib/runtime/components/react/AsyncSuspense.tsx +81 -0
  18. package/lib/runtime/components/react/context.ts +12 -3
  19. package/lib/runtime/components/react/dist/AsyncContainer.jsx +160 -0
  20. package/lib/runtime/components/react/dist/AsyncSuspense.jsx +68 -0
  21. package/lib/runtime/components/react/dist/context.js +4 -1
  22. package/lib/runtime/components/react/dist/getInnerListeners.js +1 -1
  23. package/lib/runtime/components/react/dist/mpx-button.jsx +2 -2
  24. package/lib/runtime/components/react/dist/mpx-input.jsx +1 -1
  25. package/lib/runtime/components/react/dist/mpx-movable-area.jsx +1 -1
  26. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +55 -40
  27. package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +3 -0
  28. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +17 -6
  29. package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +115 -0
  30. package/lib/runtime/components/react/dist/mpx-sticky-section.jsx +45 -0
  31. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +2 -2
  32. package/lib/runtime/components/react/dist/mpx-swiper.jsx +53 -27
  33. package/lib/runtime/components/react/dist/mpx-view.jsx +21 -7
  34. package/lib/runtime/components/react/dist/mpx-web-view.jsx +16 -30
  35. package/lib/runtime/components/react/dist/useAnimationHooks.js +2 -87
  36. package/lib/runtime/components/react/dist/utils.jsx +105 -1
  37. package/lib/runtime/components/react/getInnerListeners.ts +1 -1
  38. package/lib/runtime/components/react/mpx-button.tsx +3 -2
  39. package/lib/runtime/components/react/mpx-input.tsx +1 -1
  40. package/lib/runtime/components/react/mpx-movable-area.tsx +1 -1
  41. package/lib/runtime/components/react/mpx-movable-view.tsx +60 -41
  42. package/lib/runtime/components/react/mpx-rich-text/index.tsx +3 -0
  43. package/lib/runtime/components/react/mpx-scroll-view.tsx +68 -50
  44. package/lib/runtime/components/react/mpx-sticky-header.tsx +179 -0
  45. package/lib/runtime/components/react/mpx-sticky-section.tsx +96 -0
  46. package/lib/runtime/components/react/mpx-swiper-item.tsx +2 -2
  47. package/lib/runtime/components/react/mpx-swiper.tsx +53 -25
  48. package/lib/runtime/components/react/mpx-view.tsx +20 -7
  49. package/lib/runtime/components/react/mpx-web-view.tsx +14 -34
  50. package/lib/runtime/components/react/types/global.d.ts +15 -0
  51. package/lib/runtime/components/react/useAnimationHooks.ts +2 -85
  52. package/lib/runtime/components/react/utils.tsx +93 -1
  53. package/lib/runtime/components/web/mpx-scroll-view.vue +21 -4
  54. package/lib/runtime/components/web/mpx-sticky-header.vue +91 -0
  55. package/lib/runtime/components/web/mpx-sticky-section.vue +15 -0
  56. package/lib/runtime/optionProcessor.js +0 -2
  57. package/lib/template-compiler/compiler.js +2 -2
  58. package/lib/utils/dom-tag-config.js +17 -3
  59. package/lib/web/script-helper.js +1 -1
  60. package/package.json +4 -4
  61. package/LICENSE +0 -433
@@ -3,30 +3,74 @@ const createHelpers = require('../helpers')
3
3
  const parseRequest = require('../utils/parse-request')
4
4
  const shallowStringify = require('../utils/shallow-stringify')
5
5
  const normalize = require('../utils/normalize')
6
+ const addQuery = require('../utils/add-query')
7
+ const path = require('path')
8
+ const { isBuildInReactTag } = require('../utils/dom-tag-config')
6
9
 
7
10
  function stringifyRequest (loaderContext, request) {
8
11
  return loaderUtils.stringifyRequest(loaderContext, request)
9
12
  }
10
13
 
11
- // function getAsyncChunkName (chunkName) {
12
- // if (chunkName && typeof chunkName !== 'boolean') {
13
- // return `/* webpackChunkName: "${chunkName}" */`
14
- // }
15
- // return ''
16
- // }
14
+ function getMpxComponentRequest (component) {
15
+ return JSON.stringify(addQuery(`@mpxjs/webpack-plugin/lib/runtime/components/react/dist/${component}`, { isComponent: true }))
16
+ }
17
+
18
+ const mpxAsyncSuspense = getMpxComponentRequest('AsyncSuspense')
19
+
20
+ function getAsyncChunkName (chunkName) {
21
+ if (chunkName && typeof chunkName !== 'boolean') {
22
+ return `/* webpackChunkName: "${chunkName}/index" */`
23
+ }
24
+ return ''
25
+ }
17
26
 
18
- function buildPagesMap ({ localPagesMap, loaderContext, jsonConfig }) {
27
+ function getAsyncComponent (componentName, componentRequest, chunkName, fallback) {
28
+ return `getComponent(memo(forwardRef(function(props, ref) {
29
+ return createElement(
30
+ getComponent(require(${mpxAsyncSuspense})),
31
+ {
32
+ type: 'component',
33
+ props: Object.assign({}, props, { ref }),
34
+ chunkName: ${JSON.stringify(chunkName)},
35
+ request: ${JSON.stringify(componentRequest)},
36
+ loading: getComponent(require(${fallback})),
37
+ getChildren: () => import(${getAsyncChunkName(chunkName)}${componentRequest})
38
+ }
39
+ )
40
+ })))`
41
+ }
42
+
43
+ function getAsyncPage (componentName, componentRequest, chunkName, fallback, loading) {
44
+ fallback = fallback && `getComponent(require('${fallback}?isComponent=true'))`
45
+ loading = loading && `getComponent(require('${loading}?isComponent=true'))`
46
+ return `getComponent(function(props) {
47
+ return createElement(
48
+ getComponent(require(${mpxAsyncSuspense})),
49
+ {
50
+ type: 'page',
51
+ props: props,
52
+ chunkName: ${JSON.stringify(chunkName)},
53
+ request: ${JSON.stringify(componentRequest)},
54
+ fallback: ${fallback},
55
+ loading: ${loading},
56
+ getChildren: () => import(${getAsyncChunkName(chunkName)}${componentRequest})
57
+ }
58
+ )
59
+ })`
60
+ }
61
+
62
+ function buildPagesMap ({ localPagesMap, loaderContext, jsonConfig, rnConfig }) {
19
63
  let firstPage = ''
20
64
  const pagesMap = {}
21
65
  Object.keys(localPagesMap).forEach((pagePath) => {
22
66
  const pageCfg = localPagesMap[pagePath]
23
67
  const pageRequest = stringifyRequest(loaderContext, pageCfg.resource)
24
- // if (pageCfg.async) {
25
- // pagesMap[pagePath] = `lazy(function(){return import(${getAsyncChunkName(pageCfg.async)} ${pageRequest}).then(function(res){return getComponent(res, {__mpxPageRoute: ${JSON.stringify(pagePath)}, displayName: "Page"})})})`
26
- // } else {
68
+ if (pageCfg.async) {
69
+ pagesMap[pagePath] = getAsyncPage(pagePath, pageRequest, pageCfg.async, rnConfig.asyncChunk && rnConfig.asyncChunk.fallback, rnConfig.asyncChunk && rnConfig.asyncChunk.loading)
70
+ } else {
27
71
  // 为了保持小程序中app->page->component的js执行顺序,所有的page和component都改为require引入
28
- pagesMap[pagePath] = `getComponent(require(${pageRequest}), {__mpxPageRoute: ${JSON.stringify(pagePath)}, displayName: "Page"})`
29
- // }
72
+ pagesMap[pagePath] = `getComponent(require(${pageRequest}), {__mpxPageRoute: ${JSON.stringify(pagePath)}, displayName: "Page"})`
73
+ }
30
74
  if (pagePath === jsonConfig.entryPagePath) {
31
75
  firstPage = pagePath
32
76
  }
@@ -42,16 +86,45 @@ function buildPagesMap ({ localPagesMap, loaderContext, jsonConfig }) {
42
86
 
43
87
  function buildComponentsMap ({ localComponentsMap, builtInComponentsMap, loaderContext, jsonConfig }) {
44
88
  const componentsMap = {}
89
+ const mpx = loaderContext.getMpx()
45
90
  if (localComponentsMap) {
46
91
  Object.keys(localComponentsMap).forEach((componentName) => {
47
92
  const componentCfg = localComponentsMap[componentName]
48
93
  const componentRequest = stringifyRequest(loaderContext, componentCfg.resource)
49
- // RN中暂不支持异步加载
50
- // if (componentCfg.async) {
51
- // componentsMap[componentName] = `lazy(function(){return import(${getAsyncChunkName(componentCfg.async)}${componentRequest}).then(function(res){return getComponent(res, {displayName: ${JSON.stringify(componentName)}})})})`
52
- // } else {
53
- componentsMap[componentName] = `getComponent(require(${componentRequest}), {displayName: ${JSON.stringify(componentName)}})`
54
- // }
94
+ if (componentCfg.async) {
95
+ const placeholder = jsonConfig.componentPlaceholder && jsonConfig.componentPlaceholder[componentName]
96
+ if (placeholder) {
97
+ if (localComponentsMap[placeholder]) {
98
+ const placeholderCfg = localComponentsMap[placeholder]
99
+ const placeholderRequest = stringifyRequest(loaderContext, placeholderCfg.resource)
100
+ if (placeholderCfg.async) {
101
+ loaderContext.emitWarning(
102
+ new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} should not be a async component, please check!`)
103
+ )
104
+ }
105
+ componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async, placeholderRequest)
106
+ } else if (mpx.globalComponents[placeholder]) {
107
+ const { queryObj, rawResourcePath } = parseRequest(mpx.globalComponents[placeholder])
108
+ const placeholderRequest = JSON.stringify(path.resolve(queryObj.context, rawResourcePath))
109
+ componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async, placeholderRequest)
110
+ } else {
111
+ const tag = `mpx-${placeholder}`
112
+ if (!isBuildInReactTag(tag)) {
113
+ loaderContext.emitError(
114
+ new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} is not built-in component, please check!`)
115
+ )
116
+ }
117
+ componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async, getMpxComponentRequest(tag))
118
+ }
119
+ } else {
120
+ loaderContext.emitError(
121
+ new Error(`[json processor][${loaderContext.resource}]: ${componentName} has no componentPlaceholder, please check!`)
122
+ )
123
+ componentsMap[componentName] = getAsyncComponent(componentName, componentRequest, componentCfg.async)
124
+ }
125
+ } else {
126
+ componentsMap[componentName] = `getComponent(require(${componentRequest}), {displayName: ${JSON.stringify(componentName)}})`
127
+ }
55
128
  })
56
129
  }
57
130
  if (builtInComponentsMap) {
@@ -107,6 +180,7 @@ global.__mpxPageConfig = ${JSON.stringify(jsonConfig.window)}
107
180
  global.__getAppComponents = function () {
108
181
  return ${shallowStringify(componentsMap)}
109
182
  }
183
+ global.__preloadRule = ${JSON.stringify(jsonConfig.preloadRule)}
110
184
  global.currentInject.getPages = function () {
111
185
  return ${shallowStringify(pagesMap)}
112
186
  }
@@ -0,0 +1,217 @@
1
+ import { ComponentType, ReactNode, Component, Suspense } from 'react'
2
+ import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native'
3
+ import FastImage from '@d11/react-native-fast-image'
4
+ import Animated, { useAnimatedStyle } from 'react-native-reanimated'
5
+
6
+ type PageWrapper = {
7
+ children: ReactNode
8
+ }
9
+
10
+ export const PageWrapper = ({ children }: PageWrapper) => {
11
+ const animatedStyle = useAnimatedStyle(() => ({
12
+ transform: [{ translateY: -0 }],
13
+ flexBasis: 'auto',
14
+ flex: 1
15
+ }))
16
+
17
+ return (
18
+ <Animated.View
19
+ style={[
20
+ animatedStyle
21
+ ]}
22
+ >
23
+ {children}
24
+ </Animated.View>
25
+ )
26
+ }
27
+
28
+ const styles = StyleSheet.create({
29
+ container: {
30
+ flex: 1,
31
+ padding: 20,
32
+ backgroundColor: '#fff'
33
+ },
34
+ loadingImage: {
35
+ width: 100,
36
+ height: 100,
37
+ marginTop: 220,
38
+ alignSelf: 'center'
39
+ },
40
+ buttonText: {
41
+ color: '#fff',
42
+ fontSize: 16,
43
+ fontWeight: '500',
44
+ textAlign: 'center'
45
+ },
46
+ errorImage: {
47
+ marginTop: 80,
48
+ width: 220,
49
+ aspectRatio: 1,
50
+ alignSelf: 'center'
51
+ },
52
+ errorText: {
53
+ fontSize: 16,
54
+ textAlign: 'center',
55
+ color: '#333',
56
+ marginBottom: 20
57
+ },
58
+ retryButton: {
59
+ position: 'absolute',
60
+ bottom: 54,
61
+ left: 20,
62
+ right: 20,
63
+ backgroundColor: '#fff',
64
+ paddingVertical: 15,
65
+ borderRadius: 30,
66
+ marginTop: 40,
67
+ borderWidth: 1,
68
+ borderColor: '#FF5F00'
69
+ },
70
+ retryButtonText: {
71
+ color: '#FF5F00',
72
+ fontSize: 16,
73
+ fontWeight: '500',
74
+ textAlign: 'center'
75
+ }
76
+ })
77
+
78
+ type AsyncType = 'page' | 'component'
79
+
80
+ interface PropsType<T extends AsyncType> {
81
+ type: T
82
+ props: object
83
+ loading: ComponentType<unknown>
84
+ fallback: ComponentType<unknown>
85
+ children: (props: any) => ReactNode | ReactNode
86
+ }
87
+
88
+ interface StateType {
89
+ hasError: boolean,
90
+ key: number
91
+ }
92
+
93
+ interface ComponentError extends Error {
94
+ request?: string
95
+ type: 'timeout' | 'fail'
96
+ }
97
+
98
+ export const DefaultLoading = () => {
99
+ return (
100
+ <View style={styles.container}>
101
+ <FastImage
102
+ style={styles.loadingImage}
103
+ source={{ uri: 'https://dpubstatic.udache.com/static/dpubimg/439jiCVOtNOnEv9F2LaDs_loading.gif' }}
104
+ resizeMode={FastImage.resizeMode.contain}></FastImage>
105
+ </View>
106
+ )
107
+ }
108
+
109
+ export interface DefaultFallbackProps {
110
+ onReload: () => void
111
+ }
112
+
113
+ export const DefaultFallback = ({ onReload }: DefaultFallbackProps) => {
114
+ return (
115
+ <View style={styles.container}>
116
+ <Image
117
+ source={{ uri: 'https://dpubstatic.udache.com/static/dpubimg/Vak5mZvezPpKV5ZJI6P9b_drn-fallbak.png' }}
118
+ style={styles.errorImage}
119
+ resizeMode="contain"
120
+ />
121
+ <Text style={styles.errorText}>网络出了点问题,请查看网络环境</Text>
122
+ <TouchableOpacity
123
+ style={styles.retryButton}
124
+ onPress={onReload}
125
+ activeOpacity={0.7}
126
+ >
127
+ <Text style={styles.retryButtonText}>点击重试</Text>
128
+ </TouchableOpacity>
129
+ </View>
130
+ )
131
+ }
132
+
133
+ export default class AsyncContainer extends Component<PropsType<AsyncType>, StateType> {
134
+ private suspenseFallback: ReactNode
135
+ private errorFallback: ReactNode
136
+
137
+ constructor (props: PropsType<AsyncType>) {
138
+ super(props)
139
+ this.state = {
140
+ hasError: false,
141
+ key: 0
142
+ }
143
+ this.suspenseFallback = this.getSuspenseFallback()
144
+ this.errorFallback = this.getErrorFallback()
145
+ }
146
+
147
+ // render 阶段收集到的错误
148
+ static getDerivedStateFromError (error: ComponentError): StateType | undefined {
149
+ if (error.name === 'ChunkLoadError') {
150
+ return {
151
+ hasError: true,
152
+ key: 0
153
+ }
154
+ } else {
155
+ // 被外层捕获
156
+ throw error
157
+ }
158
+ }
159
+
160
+ componentDidCatch (error: ComponentError): void {
161
+ if (error.name === 'ChunkLoadError' && this.props.type === 'component') {
162
+ const request = error.request || ''
163
+ const subpackage = request.split('/').filter((i: string) => !!i)[0]
164
+ global.onLazyLoadError({
165
+ type: 'subpackage',
166
+ subpackage: [subpackage],
167
+ errMsg: `loadSubpackage: ${error.type}`
168
+ })
169
+ }
170
+ }
171
+
172
+ reloadPage () {
173
+ this.setState((prevState) => {
174
+ return {
175
+ hasError: false,
176
+ key: prevState.key + 1
177
+ }
178
+ })
179
+ }
180
+
181
+ getSuspenseFallback () {
182
+ if (this.props.type === 'page') {
183
+ const Fallback = this.props.loading || DefaultLoading
184
+ return <Fallback />
185
+ } else {
186
+ const Fallback = this.props.loading
187
+ return <Fallback {...this.props.props}></Fallback>
188
+ }
189
+ }
190
+
191
+ getErrorFallback () {
192
+ if (this.props.type === 'page') {
193
+ const Fallback = this.props.fallback as ComponentType<DefaultFallbackProps> || DefaultFallback
194
+ return <Fallback onReload={this.reloadPage.bind(this)}></Fallback>
195
+ } else {
196
+ const Fallback = this.props.loading
197
+ return <Fallback {...this.props.props}></Fallback>
198
+ }
199
+ }
200
+
201
+ render () {
202
+ if (this.state.hasError) {
203
+ if (this.props.type === 'component') {
204
+ return this.errorFallback
205
+ } else {
206
+ return (<PageWrapper>{this.errorFallback}</PageWrapper>)
207
+ }
208
+ } else {
209
+ return (
210
+ <Suspense fallback={this.suspenseFallback} key={this.state.key}>
211
+ {typeof this.props.children === 'function' ? this.props.children(this.props.props) : this.props.children}
212
+ {/* {this.props.children(this.props.props)} */}
213
+ </Suspense>
214
+ )
215
+ }
216
+ }
217
+ }
@@ -0,0 +1,81 @@
1
+ import { useState, ComponentType, useEffect, useCallback, useRef } from 'react'
2
+ import { DefaultFallback, DefaultLoading, PageWrapper } from './AsyncContainer'
3
+ import type { DefaultFallbackProps } from './AsyncContainer'
4
+
5
+ const asyncChunkMap = new Map()
6
+
7
+ interface props {
8
+ type: 'component' | 'page'
9
+ chunkName: string
10
+ request: string
11
+ props: any,
12
+ loading: ComponentType<unknown>
13
+ fallback: ComponentType<unknown>
14
+ getChildren: () => Promise<unknown>
15
+ }
16
+
17
+ const AsyncSuspense: React.FC<props> = ({ type, props, chunkName, request, loading, fallback, getChildren }) => {
18
+ const [status, setStatus] = useState('pending')
19
+ const loaded = asyncChunkMap.has(request)
20
+ const [, setKey] = useState(0)
21
+ const chunkPromise = useRef<null | Promise<unknown>>(null)
22
+
23
+ const reloadPage = useCallback(() => {
24
+ setKey((preV) => preV + 1)
25
+ console.log('[mpxAsyncSuspense]: reload page')
26
+ setStatus('pending')
27
+ }, [])
28
+
29
+ useEffect(() => {
30
+ if (!loaded && status === 'pending') {
31
+ // todo 清楚副作用?
32
+ console.log('the current :', chunkPromise.current)
33
+ chunkPromise.current!
34
+ .then((m: any) => {
35
+ console.log('[mpxAsyncSuspense]: load sucess')
36
+ asyncChunkMap.set(request, m.__esModule ? m.default : m)
37
+ setStatus('loaded')
38
+ })
39
+ .catch((e) => {
40
+ if (type === 'component') {
41
+ console.log(11111, e)
42
+ global.onLazyLoadError({
43
+ type: 'subpackage',
44
+ subpackage: [chunkName],
45
+ errMsg: `loadSubpackage: ${e.type}`
46
+ })
47
+ }
48
+ console.log('[mpxAsyncSuspense]: load eror', e)
49
+ chunkPromise.current = null
50
+ setStatus('error')
51
+ })
52
+ }
53
+ })
54
+
55
+ if (loaded) {
56
+ const Comp = asyncChunkMap.get(request)
57
+ return <Comp {...props}></Comp>
58
+ } else if (status === 'error') {
59
+ console.log('the status is:', status)
60
+ if (type === 'page') {
61
+ const Fallback = fallback as ComponentType<DefaultFallbackProps> || DefaultFallback
62
+ return <><PageWrapper><Fallback onReload={reloadPage}></Fallback></PageWrapper></>
63
+ } else {
64
+ const Fallback = loading
65
+ return <Fallback {...props}></Fallback>
66
+ }
67
+ } else {
68
+ if (!chunkPromise.current) {
69
+ chunkPromise.current = getChildren()
70
+ }
71
+ if (type === 'page') {
72
+ const Fallback = loading || DefaultLoading
73
+ return <PageWrapper><Fallback /></PageWrapper>
74
+ } else {
75
+ const Fallback = loading
76
+ return <Fallback {...props}></Fallback>
77
+ }
78
+ }
79
+ }
80
+
81
+ export default AsyncSuspense
@@ -1,5 +1,6 @@
1
1
  import { createContext, Dispatch, MutableRefObject, SetStateAction } from 'react'
2
- import { NativeSyntheticEvent } from 'react-native'
2
+ import { NativeSyntheticEvent, Animated } from 'react-native'
3
+ import { noop } from '@mpxjs/utils'
3
4
 
4
5
  export type LabelContextValue = MutableRefObject<{
5
6
  triggerChange: (evt: NativeSyntheticEvent<TouchEvent>) => void
@@ -42,7 +43,8 @@ export interface PortalContextValue {
42
43
  }
43
44
 
44
45
  export interface ScrollViewContextValue {
45
- gestureRef: React.RefObject<any> | null
46
+ gestureRef: React.RefObject<any> | null,
47
+ scrollOffset: Animated.Value
46
48
  }
47
49
 
48
50
  export interface RouteContextValue {
@@ -50,6 +52,11 @@ export interface RouteContextValue {
50
52
  navigation: Record<string, any>
51
53
  }
52
54
 
55
+ export interface StickyContextValue {
56
+ registerStickyHeader: Function,
57
+ unregisterStickyHeader: Function
58
+ }
59
+
53
60
  export const MovableAreaContext = createContext({ width: 0, height: 0 })
54
61
 
55
62
  export const FormContext = createContext<FormContextValue | null>(null)
@@ -72,6 +79,8 @@ export const SwiperContext = createContext({})
72
79
 
73
80
  export const KeyboardAvoidContext = createContext<KeyboardAvoidContextValue | null>(null)
74
81
 
75
- export const ScrollViewContext = createContext<ScrollViewContextValue>({ gestureRef: null })
82
+ export const ScrollViewContext = createContext<ScrollViewContextValue>({ gestureRef: null, scrollOffset: new Animated.Value(0) })
76
83
 
77
84
  export const PortalContext = createContext<PortalContextValue>(null as any)
85
+
86
+ export const StickyContext = createContext<StickyContextValue>({ registerStickyHeader: noop, unregisterStickyHeader: noop })
@@ -0,0 +1,160 @@
1
+ import { Component, Suspense } from 'react';
2
+ import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
3
+ import FastImage from '@d11/react-native-fast-image';
4
+ import Animated, { useAnimatedStyle } from 'react-native-reanimated';
5
+ export const PageWrapper = ({ children }) => {
6
+ const animatedStyle = useAnimatedStyle(() => ({
7
+ transform: [{ translateY: -0 }],
8
+ flexBasis: 'auto',
9
+ flex: 1
10
+ }));
11
+ return (<Animated.View style={[
12
+ animatedStyle
13
+ ]}>
14
+ {children}
15
+ </Animated.View>);
16
+ };
17
+ const styles = StyleSheet.create({
18
+ container: {
19
+ flex: 1,
20
+ padding: 20,
21
+ backgroundColor: '#fff'
22
+ },
23
+ loadingImage: {
24
+ width: 100,
25
+ height: 100,
26
+ marginTop: 220,
27
+ alignSelf: 'center'
28
+ },
29
+ buttonText: {
30
+ color: '#fff',
31
+ fontSize: 16,
32
+ fontWeight: '500',
33
+ textAlign: 'center'
34
+ },
35
+ errorImage: {
36
+ marginTop: 80,
37
+ width: 220,
38
+ aspectRatio: 1,
39
+ alignSelf: 'center'
40
+ },
41
+ errorText: {
42
+ fontSize: 16,
43
+ textAlign: 'center',
44
+ color: '#333',
45
+ marginBottom: 20
46
+ },
47
+ retryButton: {
48
+ position: 'absolute',
49
+ bottom: 54,
50
+ left: 20,
51
+ right: 20,
52
+ backgroundColor: '#fff',
53
+ paddingVertical: 15,
54
+ borderRadius: 30,
55
+ marginTop: 40,
56
+ borderWidth: 1,
57
+ borderColor: '#FF5F00'
58
+ },
59
+ retryButtonText: {
60
+ color: '#FF5F00',
61
+ fontSize: 16,
62
+ fontWeight: '500',
63
+ textAlign: 'center'
64
+ }
65
+ });
66
+ export const DefaultLoading = () => {
67
+ return (<View style={styles.container}>
68
+ <FastImage style={styles.loadingImage} source={{ uri: 'https://dpubstatic.udache.com/static/dpubimg/439jiCVOtNOnEv9F2LaDs_loading.gif' }} resizeMode={FastImage.resizeMode.contain}></FastImage>
69
+ </View>);
70
+ };
71
+ export const DefaultFallback = ({ onReload }) => {
72
+ return (<View style={styles.container}>
73
+ <Image source={{ uri: 'https://dpubstatic.udache.com/static/dpubimg/Vak5mZvezPpKV5ZJI6P9b_drn-fallbak.png' }} style={styles.errorImage} resizeMode="contain"/>
74
+ <Text style={styles.errorText}>网络出了点问题,请查看网络环境</Text>
75
+ <TouchableOpacity style={styles.retryButton} onPress={onReload} activeOpacity={0.7}>
76
+ <Text style={styles.retryButtonText}>点击重试</Text>
77
+ </TouchableOpacity>
78
+ </View>);
79
+ };
80
+ export default class AsyncContainer extends Component {
81
+ suspenseFallback;
82
+ errorFallback;
83
+ constructor(props) {
84
+ super(props);
85
+ this.state = {
86
+ hasError: false,
87
+ key: 0
88
+ };
89
+ this.suspenseFallback = this.getSuspenseFallback();
90
+ this.errorFallback = this.getErrorFallback();
91
+ }
92
+ // render 阶段收集到的错误
93
+ static getDerivedStateFromError(error) {
94
+ if (error.name === 'ChunkLoadError') {
95
+ return {
96
+ hasError: true,
97
+ key: 0
98
+ };
99
+ }
100
+ else {
101
+ // 被外层捕获
102
+ throw error;
103
+ }
104
+ }
105
+ componentDidCatch(error) {
106
+ if (error.name === 'ChunkLoadError' && this.props.type === 'component') {
107
+ const request = error.request || '';
108
+ const subpackage = request.split('/').filter((i) => !!i)[0];
109
+ global.onLazyLoadError({
110
+ type: 'subpackage',
111
+ subpackage: [subpackage],
112
+ errMsg: `loadSubpackage: ${error.type}`
113
+ });
114
+ }
115
+ }
116
+ reloadPage() {
117
+ this.setState((prevState) => {
118
+ return {
119
+ hasError: false,
120
+ key: prevState.key + 1
121
+ };
122
+ });
123
+ }
124
+ getSuspenseFallback() {
125
+ if (this.props.type === 'page') {
126
+ const Fallback = this.props.loading || DefaultLoading;
127
+ return <Fallback />;
128
+ }
129
+ else {
130
+ const Fallback = this.props.loading;
131
+ return <Fallback {...this.props.props}></Fallback>;
132
+ }
133
+ }
134
+ getErrorFallback() {
135
+ if (this.props.type === 'page') {
136
+ const Fallback = this.props.fallback || DefaultFallback;
137
+ return <Fallback onReload={this.reloadPage.bind(this)}></Fallback>;
138
+ }
139
+ else {
140
+ const Fallback = this.props.loading;
141
+ return <Fallback {...this.props.props}></Fallback>;
142
+ }
143
+ }
144
+ render() {
145
+ if (this.state.hasError) {
146
+ if (this.props.type === 'component') {
147
+ return this.errorFallback;
148
+ }
149
+ else {
150
+ return (<PageWrapper>{this.errorFallback}</PageWrapper>);
151
+ }
152
+ }
153
+ else {
154
+ return (<Suspense fallback={this.suspenseFallback} key={this.state.key}>
155
+ {typeof this.props.children === 'function' ? this.props.children(this.props.props) : this.props.children}
156
+ {/* {this.props.children(this.props.props)} */}
157
+ </Suspense>);
158
+ }
159
+ }
160
+ }