@mpxjs/core 2.10.17 → 2.10.18-beta.1

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.
@@ -1,6 +1,6 @@
1
1
  import transferOptions from '../core/transferOptions'
2
2
  import builtInKeysMap from './patch/builtInKeysMap'
3
- import { makeMap, spreadProp, getFocusedNavigation, hasOwn } from '@mpxjs/utils'
3
+ import { makeMap, spreadProp, getFocusedNavigation, hasOwn, callWithErrorHandling } from '@mpxjs/utils'
4
4
  import { mergeLifecycle } from '../convertor/mergeLifecycle'
5
5
  import { LIFECYCLE } from '../platform/patch/lifecycle/index'
6
6
  import Mpx from '../index'
@@ -14,10 +14,6 @@ import MpxNav from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-
14
14
 
15
15
  const appHooksMap = makeMap(mergeLifecycle(LIFECYCLE).app)
16
16
 
17
- function getPageSize (window = ReactNative.Dimensions.get('window')) {
18
- return window.width + 'x' + window.height
19
- }
20
-
21
17
  function filterOptions (options, appData) {
22
18
  const newOptions = {}
23
19
  Object.keys(options).forEach(key => {
@@ -74,13 +70,12 @@ export default function createApp (options) {
74
70
  )
75
71
  }
76
72
  const getComponent = () => {
77
- return item.displayName ? item : item()
73
+ return item.displayName ? item : callWithErrorHandling(item, null, 'require page script')
78
74
  }
79
75
  if (key === initialRouteName) {
80
76
  return createElement(Stack.Screen, {
81
77
  name: key,
82
78
  getComponent,
83
- initialParams,
84
79
  layout: headerLayout
85
80
  })
86
81
  }
@@ -208,26 +203,17 @@ export default function createApp (options) {
208
203
  if (Mpx.config.rnConfig.disableAppStateListener) return
209
204
  onAppStateChange(state)
210
205
  })
211
-
212
- let count = 0
213
- let lastPageSize = getPageSize()
214
- const resizeSubScription = ReactNative.Dimensions.addEventListener('change', ({ window }) => {
215
- const pageSize = getPageSize(window)
216
- if (pageSize === lastPageSize) return
217
- lastPageSize = pageSize
218
- const navigation = getFocusedNavigation()
219
- if (navigation && hasOwn(global.__mpxPageStatusMap, navigation.pageId)) {
220
- global.__mpxPageStatusMap[navigation.pageId] = `resize${count++}`
221
- }
222
- })
223
206
  return () => {
224
207
  appState.state = 'exit'
225
208
  changeSubscription && changeSubscription.remove()
226
- resizeSubScription && resizeSubScription.remove()
227
209
  }
228
210
  }, [])
229
211
 
230
212
  const { initialRouteName, initialParams } = initialRouteRef.current
213
+ if (!global.__mpxAppHotLaunched) {
214
+ global.__mpxInitialRouteName = initialRouteName
215
+ global.__mpxInitialRunParams = initialParams
216
+ }
231
217
  const navScreenOpts = {
232
218
  headerShown: false,
233
219
  statusBarTranslucent: Mpx.config.rnConfig.statusBarTranslucent ?? true,
@@ -1,7 +1,7 @@
1
1
  import { createNativeStackNavigator } from '@react-navigation/native-stack'
2
2
  import { NavigationContainer, StackActions } from '@react-navigation/native'
3
3
  import PortalHost from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-portal/portal-host'
4
- import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'
4
+ import { SafeAreaProvider, useSafeAreaInsets, initialWindowMetrics } from 'react-native-safe-area-context'
5
5
  import { GestureHandlerRootView } from 'react-native-gesture-handler'
6
6
 
7
7
  export {
@@ -11,5 +11,6 @@ export {
11
11
  GestureHandlerRootView,
12
12
  PortalHost,
13
13
  SafeAreaProvider,
14
- useSafeAreaInsets
14
+ useSafeAreaInsets,
15
+ initialWindowMetrics
15
16
  }
@@ -3,25 +3,25 @@ import * as ReactNative from 'react-native'
3
3
  import { ReactiveEffect } from '../../observer/effect'
4
4
  import { watch } from '../../observer/watch'
5
5
  import { del, reactive, set } from '../../observer/reactive'
6
- import { hasOwn, isFunction, noop, isObject, isArray, getByPath, collectDataset, hump2dash, dash2hump, callWithErrorHandling, wrapMethodsWithErrorHandling, error, setFocusedNavigation } from '@mpxjs/utils'
6
+ import { hasOwn, isFunction, noop, isObject, isArray, getByPath, collectDataset, hump2dash, dash2hump, callWithErrorHandling, wrapMethodsWithErrorHandling, error, setFocusedNavigation, getDefaultValueByType } from '@mpxjs/utils'
7
7
  import MpxProxy from '../../core/proxy'
8
8
  import { BEFOREUPDATE, ONLOAD, UPDATED, ONSHOW, ONHIDE, ONRESIZE, REACTHOOKSEXEC } from '../../core/innerLifecycle'
9
9
  import mergeOptions from '../../core/mergeOptions'
10
10
  import { queueJob, hasPendingJob } from '../../observer/scheduler'
11
11
  import { createSelectorQuery, createIntersectionObserver } from '@mpxjs/api-proxy'
12
- import MpxKeyboardAvoidingView from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-keyboard-avoiding-view'
13
12
  import {
14
13
  IntersectionObserverContext,
15
14
  KeyboardAvoidContext,
16
15
  ProviderContext,
17
16
  RouteContext
18
17
  } from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/context'
19
- import { PortalHost, useSafeAreaInsets } from '../env/navigationHelper'
18
+ import { PortalHost, useSafeAreaInsets, initialWindowMetrics } from '../env/navigationHelper'
20
19
  import { useInnerHeaderHeight } from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-nav'
20
+ import Mpx from '../../index'
21
21
 
22
22
  function getSystemInfo () {
23
- const windowDimensions = ReactNative.Dimensions.get('window')
24
- const screenDimensions = ReactNative.Dimensions.get('screen')
23
+ const windowDimensions = global.__mpxAppDimensionsInfo.window
24
+ const screenDimensions = global.__mpxAppDimensionsInfo.screen
25
25
  return {
26
26
  deviceOrientation: windowDimensions.width > windowDimensions.height ? 'landscape' : 'portrait',
27
27
  size: {
@@ -172,8 +172,8 @@ const instanceProto = {
172
172
  type: field
173
173
  }
174
174
  }
175
- // 处理props默认值
176
- propsData[key] = field.value
175
+ // 处理props默认值,没有显式设置value时根据type获取默认值,与微信小程序原生行为保持一致
176
+ propsData[key] = hasOwn(field, 'value') ? field.value : getDefaultValueByType(field.type)
177
177
  }
178
178
  }
179
179
  })
@@ -295,13 +295,18 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
295
295
  instance[key] = method.bind(instance)
296
296
  })
297
297
  }
298
-
298
+ const loadParams = {}
299
299
  if (type === 'page') {
300
300
  const props = propsRef.current
301
301
  instance.route = props.route.name
302
302
  global.__mpxPagesMap = global.__mpxPagesMap || {}
303
303
  global.__mpxPagesMap[props.route.key] = [instance, props.navigation]
304
304
  setFocusedNavigation(props.navigation)
305
+
306
+ if (!global.__mpxAppHotLaunched && global.__mpxInitialRunParams) {
307
+ Object.assign(loadParams, global.__mpxInitialRunParams)
308
+ }
309
+ set(global.__mpxPageSizeCountMap, pageId, global.__mpxSizeCount)
305
310
  // App onLaunch 在 Page created 之前执行
306
311
  if (!global.__mpxAppHotLaunched && global.__mpxAppOnLaunch) {
307
312
  global.__mpxAppOnLaunch(props.navigation)
@@ -310,17 +315,16 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
310
315
 
311
316
  const proxy = instance.__mpxProxy = new MpxProxy(rawOptions, instance)
312
317
  proxy.created()
313
-
314
318
  if (type === 'page') {
315
319
  const props = propsRef.current
316
- const loadParams = {}
317
- // 此处拿到的props.route.params内属性的value被进行过了一次decode, 不符合预期,此处额外进行一次encode来与微信对齐
318
- if (isObject(props.route.params)) {
319
- for (const key in props.route.params) {
320
- loadParams[key] = encodeURIComponent(props.route.params[key])
320
+ const decodedQuery = {}
321
+ const rawQuery = Object.assign({}, loadParams, props.route.params || {})
322
+ if (isObject(rawQuery)) {
323
+ for (const key in rawQuery) {
324
+ decodedQuery[key] = decodeURIComponent(rawQuery[key])
321
325
  }
322
326
  }
323
- proxy.callHook(ONLOAD, [loadParams])
327
+ proxy.callHook(ONLOAD, [rawQuery, decodedQuery])
324
328
  }
325
329
 
326
330
  Object.assign(proxy, {
@@ -375,9 +379,18 @@ const triggerPageStatusHook = (mpxProxy, event) => {
375
379
  }
376
380
  }
377
381
 
378
- const triggerResizeEvent = (mpxProxy) => {
379
- const type = mpxProxy.options.__type__
382
+ const triggerResizeEvent = (mpxProxy, sizeRef) => {
383
+ const oldSize = sizeRef.current.size
380
384
  const systemInfo = getSystemInfo()
385
+ const newSize = systemInfo.size
386
+
387
+ if (oldSize && oldSize.windowWidth === newSize.windowWidth && oldSize.windowHeight === newSize.windowHeight) {
388
+ return
389
+ }
390
+
391
+ Object.assign(sizeRef.current, systemInfo)
392
+
393
+ const type = mpxProxy.options.__type__
381
394
  const target = mpxProxy.target
382
395
  mpxProxy.callHook(ONRESIZE, [systemInfo])
383
396
  if (type === 'page') {
@@ -388,7 +401,9 @@ const triggerResizeEvent = (mpxProxy) => {
388
401
  }
389
402
  }
390
403
 
391
- function usePageEffect (mpxProxy, pageId) {
404
+ function usePageEffect (mpxProxy, pageId, type) {
405
+ const sizeRef = useRef(getSystemInfo())
406
+
392
407
  useEffect(() => {
393
408
  let unWatch
394
409
  const hasShowHook = hasPageHook(mpxProxy, [ONSHOW, 'show'])
@@ -399,21 +414,31 @@ function usePageEffect (mpxProxy, pageId) {
399
414
  unWatch = watch(() => pageStatusMap[pageId], (newVal) => {
400
415
  if (newVal === 'show' || newVal === 'hide') {
401
416
  triggerPageStatusHook(mpxProxy, newVal)
417
+ // 仅在尺寸确实变化时才触发resize事件
418
+ triggerResizeEvent(mpxProxy, sizeRef)
419
+
420
+ // 如果当前全局size与pagesize不一致,在show之后触发一次resize事件
421
+ if (type === 'page' && newVal === 'show' && global.__mpxPageSizeCountMap[pageId] !== global.__mpxSizeCount) {
422
+ // 刷新__mpxPageSizeCountMap, 每个页面仅会执行一次,直接驱动render刷新
423
+ global.__mpxPageSizeCountMap[pageId] = global.__mpxSizeCount
424
+ }
402
425
  } else if (/^resize/.test(newVal)) {
403
- triggerResizeEvent(mpxProxy)
426
+ triggerResizeEvent(mpxProxy, sizeRef)
404
427
  }
405
428
  }, { sync: true })
406
429
  }
407
430
  }
408
431
  return () => {
409
432
  unWatch && unWatch()
433
+ if (type === 'page') {
434
+ del(global.__mpxPageSizeCountMap, pageId)
435
+ }
410
436
  }
411
437
  }, [])
412
438
  }
413
439
 
414
440
  let pageId = 0
415
441
  const pageStatusMap = global.__mpxPageStatusMap = reactive({})
416
-
417
442
  function usePageStatus (navigation, pageId) {
418
443
  navigation.pageId = pageId
419
444
  if (!hasOwn(pageStatusMap, pageId)) {
@@ -486,7 +511,17 @@ function getLayoutData (headerHeight) {
486
511
  // 在横屏状态下 screen.height = window.height + bottomVirtualHeight
487
512
  // 在正常状态 screen.height = window.height + bottomVirtualHeight + statusBarHeight
488
513
  const isLandscape = screenDimensions.height < screenDimensions.width
489
- const bottomVirtualHeight = isLandscape ? screenDimensions.height - windowDimensions.height : ((screenDimensions.height - windowDimensions.height - ReactNative.StatusBar.currentHeight) || 0)
514
+ let bottomVirtualHeight = 0
515
+ if (ReactNative.Platform.OS === 'android') {
516
+ if (isLandscape) {
517
+ bottomVirtualHeight = screenDimensions.height - windowDimensions.height
518
+ } else {
519
+ bottomVirtualHeight = initialWindowMetrics?.insets?.bottom || 0
520
+ if (typeof mpxGlobal.__mpx.config?.rnConfig?.getBottomVirtualHeight === 'function') {
521
+ bottomVirtualHeight = mpxGlobal.__mpx.config?.rnConfig?.getBottomVirtualHeight() || 0
522
+ }
523
+ }
524
+ }
490
525
  return {
491
526
  left: 0,
492
527
  top: headerHeight,
@@ -519,7 +554,7 @@ export function PageWrapperHOC (WrappedComponent, pageConfig = {}) {
519
554
  navigation.layout = getLayoutData(headerHeight)
520
555
 
521
556
  useEffect(() => {
522
- const dimensionListener = ReactNative.Dimensions.addEventListener('change', ({ screen }) => {
557
+ const dimensionListener = ReactNative.Dimensions.addEventListener('change', ({ window, screen }) => {
523
558
  navigation.layout = getLayoutData(headerHeight)
524
559
  })
525
560
  return () => dimensionListener?.remove()
@@ -529,6 +564,8 @@ export function PageWrapperHOC (WrappedComponent, pageConfig = {}) {
529
564
  usePageStatus(navigation, currentPageId)
530
565
 
531
566
  const withKeyboardAvoidingView = (element) => {
567
+ if (currentPageConfig.disableKeyboardAvoiding) return element
568
+ const MpxKeyboardAvoidingView = require('@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-keyboard-avoiding-view').default
532
569
  return createElement(KeyboardAvoidContext.Provider,
533
570
  {
534
571
  value: keyboardAvoidRef
@@ -549,39 +586,53 @@ export function PageWrapperHOC (WrappedComponent, pageConfig = {}) {
549
586
  // android存在第一次打开insets都返回为0情况,后续会触发第二次渲染后正确
550
587
  navigation.insets = useSafeAreaInsets()
551
588
  return withKeyboardAvoidingView(
552
- createElement(ReactNative.View,
589
+ createElement(ReactNative.View,
590
+ {
591
+ style: {
592
+ flex: 1,
593
+ // 页面容器背景色
594
+ backgroundColor: currentPageConfig?.backgroundColorContent || '#fff',
595
+ // 解决页面内有元素定位relative left为负值的时候,回退的时候还能看到对应元素问题
596
+ overflow: 'hidden'
597
+ }
598
+ },
599
+ createElement(RouteContext.Provider,
600
+ {
601
+ value: routeContextValRef.current
602
+ },
603
+ createElement(IntersectionObserverContext.Provider,
553
604
  {
554
- style: {
555
- flex: 1,
556
- // 页面容器背景色
557
- backgroundColor: currentPageConfig?.backgroundColorContent || '#fff',
558
- // 解决页面内有元素定位relative left为负值的时候,回退的时候还能看到对应元素问题
559
- overflow: 'hidden'
560
- }
605
+ value: intersectionObservers.current
561
606
  },
562
- createElement(RouteContext.Provider,
563
- {
564
- value: routeContextValRef.current
565
- },
566
- createElement(IntersectionObserverContext.Provider,
567
- {
568
- value: intersectionObservers.current
569
- },
570
- createElement(PortalHost,
571
- null,
572
- createElement(WrappedComponent, {
573
- ...props,
574
- navigation,
575
- route,
576
- id: currentPageId
577
- })
578
- )
579
- )
607
+ createElement(PortalHost,
608
+ null,
609
+ createElement(WrappedComponent, {
610
+ ...props,
611
+ navigation,
612
+ route,
613
+ id: currentPageId
614
+ })
580
615
  )
616
+ )
581
617
  )
618
+ )
582
619
  )
583
620
  }
584
621
  }
622
+
623
+ function updateProps (instance, props, validProps) {
624
+ Object.keys(validProps).forEach((key) => {
625
+ if (hasOwn(props, key)) {
626
+ instance[key] = props[key]
627
+ } else {
628
+ const altKey = hump2dash(key)
629
+ if (hasOwn(props, altKey)) {
630
+ instance[key] = props[altKey]
631
+ }
632
+ }
633
+ })
634
+ }
635
+
585
636
  export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
586
637
  rawOptions = mergeOptions(rawOptions, type, false)
587
638
  const componentsMap = currentInject.componentsMap
@@ -634,16 +685,14 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
634
685
 
635
686
  if (!isFirst) {
636
687
  // 处理props更新
637
- Object.keys(validProps).forEach((key) => {
638
- if (hasOwn(props, key)) {
639
- instance[key] = props[key]
640
- } else {
641
- const altKey = hump2dash(key)
642
- if (hasOwn(props, altKey)) {
643
- instance[key] = props[altKey]
644
- }
645
- }
646
- })
688
+ if (Mpx.config.forceFlushSync) {
689
+ // 避免开启forceFlushSync时react报错:Cannot update a component while rendering a different component
690
+ Promise.resolve().then(() => {
691
+ updateProps(instance, props, validProps)
692
+ })
693
+ } else {
694
+ updateProps(instance, props, validProps)
695
+ }
647
696
  }
648
697
 
649
698
  useEffect(() => {
@@ -653,7 +702,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
653
702
  }
654
703
  })
655
704
 
656
- usePageEffect(proxy, pageId)
705
+ usePageEffect(proxy, pageId, type)
657
706
  useEffect(() => {
658
707
  proxy.mounted()
659
708
  return () => {