@mpxjs/core 2.10.16 → 2.10.17-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.
@@ -1,37 +1,74 @@
1
- import { isObject, isArray, dash2hump, cached, isEmptyObject, hasOwn } from '@mpxjs/utils'
2
- import { Dimensions, StyleSheet } from 'react-native'
1
+ import { isObject, isArray, dash2hump, cached, isEmptyObject, hasOwn, getFocusedNavigation, noop } from '@mpxjs/utils'
2
+ import { StyleSheet, Dimensions } from 'react-native'
3
+ import { reactive } from '../../observer/reactive'
3
4
  import Mpx from '../../index'
4
5
 
5
- const rawDimensions = {
6
- screen: Dimensions.get('screen'),
7
- window: Dimensions.get('window')
6
+ global.__mpxAppDimensionsInfo = {
7
+ window: Dimensions.get('window'),
8
+ screen: Dimensions.get('screen')
8
9
  }
9
- let width, height
10
+ global.__mpxSizeCount = 0
11
+ global.__mpxPageSizeCountMap = reactive({})
10
12
 
11
- // TODO 临时适配折叠屏场景适配
12
- // const isLargeFoldableLike = (__mpx_mode__ === 'android') && (height / width < 1.5) && (width > 600)
13
- // if (isLargeFoldableLike) width = width / 2
13
+ global.__GCC = function (className, classMap, classMapValueCache) {
14
+ if (!classMapValueCache.has(className)) {
15
+ const styleObj = classMap[className]?.()
16
+ styleObj && classMapValueCache.set(className, styleObj)
17
+ }
18
+ return classMapValueCache.get(className)
19
+ }
14
20
 
15
- function customDimensions (dimensions) {
21
+ let dimensionsInfoInitialized = false
22
+ function useDimensionsInfo (dimensions) {
23
+ dimensionsInfoInitialized = true
16
24
  if (typeof Mpx.config.rnConfig?.customDimensions === 'function') {
17
25
  dimensions = Mpx.config.rnConfig.customDimensions(dimensions) || dimensions
18
26
  }
19
- width = dimensions.screen.width
20
- height = dimensions.screen.height
27
+ global.__mpxAppDimensionsInfo.window = dimensions.window
28
+ global.__mpxAppDimensionsInfo.screen = dimensions.screen
21
29
  }
22
30
 
23
- Dimensions.addEventListener('change', customDimensions)
31
+ function getPageSize (window = global.__mpxAppDimensionsInfo.screen) {
32
+ return window.width + 'x' + window.height
33
+ }
34
+
35
+ Dimensions.addEventListener('change', ({ window, screen }) => {
36
+ const oldScreen = getPageSize(global.__mpxAppDimensionsInfo.screen)
37
+ useDimensionsInfo({ window, screen })
38
+
39
+ // 对比 screen 高宽是否存在变化
40
+ if (getPageSize(screen) === oldScreen) return
41
+
42
+ global.__classCaches?.forEach(cache => cache?.clear())
43
+
44
+ // 更新全局和栈顶页面的标记,其他后台页面的标记在show之后更新
45
+ global.__mpxSizeCount++
46
+
47
+ const navigation = getFocusedNavigation()
48
+
49
+ if (navigation) {
50
+ global.__mpxPageSizeCountMap[navigation.pageId] = global.__mpxSizeCount
51
+ if (hasOwn(global.__mpxPageStatusMap, navigation.pageId)) {
52
+ global.__mpxPageStatusMap[navigation.pageId] = `resize${global.__mpxSizeCount}`
53
+ }
54
+ }
55
+ })
24
56
 
57
+ // TODO: 1 目前测试鸿蒙下折叠屏screen固定为展开状态下屏幕尺寸,仅window会变化,且window包含状态栏高度
58
+ // TODO: 2 存在部分安卓折叠屏机型在折叠/展开切换时,Dimensions监听到的width/height尺寸错误,并触发多次问题
25
59
  function rpx (value) {
60
+ const screenInfo = global.__mpxAppDimensionsInfo.screen
26
61
  // rn 单位 dp = 1(css)px = 1 物理像素 * pixelRatio(像素比)
27
62
  // px = rpx * (750 / 屏幕宽度)
28
- return value * width / 750
63
+ return value * screenInfo.width / 750
29
64
  }
30
65
  function vw (value) {
31
- return value * width / 100
66
+ const screenInfo = global.__mpxAppDimensionsInfo.screen
67
+ return value * screenInfo.width / 100
32
68
  }
33
69
  function vh (value) {
34
- return value * height / 100
70
+ const screenInfo = global.__mpxAppDimensionsInfo.screen
71
+ return value * screenInfo.height / 100
35
72
  }
36
73
 
37
74
  const unit = {
@@ -42,8 +79,14 @@ const unit = {
42
79
 
43
80
  const empty = {}
44
81
 
45
- function formatValue (value) {
46
- if (width === undefined) customDimensions(rawDimensions)
82
+ function formatValue (value, unitType) {
83
+ if (!dimensionsInfoInitialized) useDimensionsInfo(global.__mpxAppDimensionsInfo)
84
+ if (unitType === 'hairlineWidth') {
85
+ return StyleSheet.hairlineWidth
86
+ }
87
+ if (unitType && typeof unit[unitType] === 'function') {
88
+ return unit[unitType](+value)
89
+ }
47
90
  const matched = unitRegExp.exec(value)
48
91
  if (matched) {
49
92
  if (!matched[2] || matched[2] === 'px') {
@@ -184,29 +227,58 @@ function isNativeStyle (style) {
184
227
  )
185
228
  }
186
229
 
230
+ function getMediaStyle (media) {
231
+ if (!media || !media.length) return {}
232
+ const { width } = global.__mpxAppDimensionsInfo.screen
233
+ return media.reduce((styleObj, item) => {
234
+ const { options = {}, value = {} } = item
235
+ const { minWidth, maxWidth } = options
236
+ if (!isNaN(minWidth) && !isNaN(maxWidth) && width >= minWidth && width <= maxWidth) {
237
+ Object.assign(styleObj, value)
238
+ } else if (!isNaN(minWidth) && width >= minWidth) {
239
+ Object.assign(styleObj, value)
240
+ } else if (!isNaN(maxWidth) && width <= maxWidth) {
241
+ Object.assign(styleObj, value)
242
+ }
243
+ return styleObj
244
+ }, {})
245
+ }
246
+
187
247
  export default function styleHelperMixin () {
188
248
  return {
189
249
  methods: {
250
+ __getSizeCount () {
251
+ return global.__mpxPageSizeCountMap[this.__pageId]
252
+ },
190
253
  __getClass (staticClass, dynamicClass) {
191
254
  return concat(staticClass, stringifyDynamicClass(dynamicClass))
192
255
  },
193
256
  __getStyle (staticClass, dynamicClass, staticStyle, dynamicStyle, hide) {
194
257
  const isNativeStaticStyle = staticStyle && isNativeStyle(staticStyle)
195
258
  let result = isNativeStaticStyle ? [] : {}
196
- const mergeResult = isNativeStaticStyle ? (o) => result.push(o) : (o) => Object.assign(result, o)
197
-
198
- const classMap = this.__getClassMap?.() || {}
199
- const appClassMap = global.__getAppClassMap?.() || {}
259
+ const mergeResult = isNativeStaticStyle ? (...args) => result.push(...args) : (...args) => Object.assign(result, ...args)
260
+ // 使用一下 __getSizeCount 触发其 get
261
+ this.__getSizeCount()
200
262
 
201
263
  if (staticClass || dynamicClass) {
202
264
  // todo 当前为了复用小程序unocss产物,暂时进行mpEscape,等后续正式支持unocss后可不进行mpEscape
203
265
  const classString = mpEscape(concat(staticClass, stringifyDynamicClass(dynamicClass)))
266
+
204
267
  classString.split(/\s+/).forEach((className) => {
205
- if (classMap[className]) {
206
- mergeResult(classMap[className])
207
- } else if (appClassMap[className]) {
208
- // todo 全局样式在每个页面和组件中生效,以支持全局原子类,后续支持样式模块复用后可考虑移除
209
- mergeResult(appClassMap[className])
268
+ let localStyle, appStyle
269
+ const getAppClassStyle = global.__getAppClassStyle || noop
270
+ if (localStyle = this.__getClassStyle?.(className)) {
271
+ if (localStyle._media?.length) {
272
+ mergeResult(localStyle._default, getMediaStyle(localStyle._media))
273
+ } else {
274
+ mergeResult(localStyle._default)
275
+ }
276
+ } else if (appStyle = getAppClassStyle(className)) {
277
+ if (appStyle._media?.length) {
278
+ mergeResult(appStyle._default, getMediaStyle(appStyle._media))
279
+ } else {
280
+ mergeResult(appStyle._default)
281
+ }
210
282
  } else if (isObject(this.__props[className])) {
211
283
  // externalClasses必定以对象形式传递下来
212
284
  mergeResult(this.__props[className])
@@ -236,8 +308,14 @@ export default function styleHelperMixin () {
236
308
  flex: 0,
237
309
  height: 0,
238
310
  width: 0,
239
- padding: 0,
240
- margin: 0,
311
+ paddingTop: 0,
312
+ paddingRight: 0,
313
+ paddingBottom: 0,
314
+ paddingLeft: 0,
315
+ marginTop: 0,
316
+ marginRight: 0,
317
+ marginBottom: 0,
318
+ marginLeft: 0,
241
319
  overflow: 'hidden'
242
320
  })
243
321
  }
@@ -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'
@@ -10,14 +10,10 @@ import { createElement, memo, useRef, useEffect } from 'react'
10
10
  import * as ReactNative from 'react-native'
11
11
  import { initAppProvides } from './export/inject'
12
12
  import { NavigationContainer, createNativeStackNavigator, SafeAreaProvider, GestureHandlerRootView } from './env/navigationHelper'
13
- import { innerNav } from './env/nav'
13
+ import MpxNav from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-nav'
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 => {
@@ -66,21 +62,20 @@ export default function createApp (options) {
66
62
  flex: 1
67
63
  }
68
64
  },
69
- createElement(innerNav, {
70
- pageConfig: pageConfig,
65
+ createElement(MpxNav, {
66
+ pageConfig,
71
67
  navigation
72
68
  }),
73
69
  children
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,29 +203,20 @@ 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
- statusBarTranslucent: true,
219
+ statusBarTranslucent: Mpx.config.rnConfig.statusBarTranslucent ?? true,
234
220
  statusBarBackgroundColor: 'transparent'
235
221
  }
236
222
 
@@ -3,7 +3,7 @@ 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'
@@ -13,15 +13,15 @@ import MpxKeyboardAvoidingView from '@mpxjs/webpack-plugin/lib/runtime/component
13
13
  import {
14
14
  IntersectionObserverContext,
15
15
  KeyboardAvoidContext,
16
+ ProviderContext,
16
17
  RouteContext
17
18
  } from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/context'
18
19
  import { PortalHost, useSafeAreaInsets } from '../env/navigationHelper'
19
- import { useInnerHeaderHeight } from '../env/nav'
20
+ import { useInnerHeaderHeight } from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-nav'
20
21
 
21
- const ProviderContext = createContext(null)
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
  })
@@ -302,6 +302,7 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
302
302
  global.__mpxPagesMap = global.__mpxPagesMap || {}
303
303
  global.__mpxPagesMap[props.route.key] = [instance, props.navigation]
304
304
  setFocusedNavigation(props.navigation)
305
+ set(global.__mpxPageSizeCountMap, pageId, global.__mpxSizeCount)
305
306
  // App onLaunch 在 Page created 之前执行
306
307
  if (!global.__mpxAppHotLaunched && global.__mpxAppOnLaunch) {
307
308
  global.__mpxAppOnLaunch(props.navigation)
@@ -313,14 +314,14 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
313
314
 
314
315
  if (type === 'page') {
315
316
  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])
317
+ const decodedQuery = {}
318
+ const rawQuery = props.route.params || {}
319
+ if (isObject(rawQuery)) {
320
+ for (const key in rawQuery) {
321
+ decodedQuery[key] = decodeURIComponent(rawQuery[key])
321
322
  }
322
323
  }
323
- proxy.callHook(ONLOAD, [loadParams])
324
+ proxy.callHook(ONLOAD, [rawQuery, decodedQuery])
324
325
  }
325
326
 
326
327
  Object.assign(proxy, {
@@ -375,9 +376,18 @@ const triggerPageStatusHook = (mpxProxy, event) => {
375
376
  }
376
377
  }
377
378
 
378
- const triggerResizeEvent = (mpxProxy) => {
379
- const type = mpxProxy.options.__type__
379
+ const triggerResizeEvent = (mpxProxy, sizeRef) => {
380
+ const oldSize = sizeRef.current.size
380
381
  const systemInfo = getSystemInfo()
382
+ const newSize = systemInfo.size
383
+
384
+ if (oldSize && oldSize.windowWidth === newSize.windowWidth && oldSize.windowHeight === newSize.windowHeight) {
385
+ return
386
+ }
387
+
388
+ Object.assign(sizeRef.current, systemInfo)
389
+
390
+ const type = mpxProxy.options.__type__
381
391
  const target = mpxProxy.target
382
392
  mpxProxy.callHook(ONRESIZE, [systemInfo])
383
393
  if (type === 'page') {
@@ -389,6 +399,8 @@ const triggerResizeEvent = (mpxProxy) => {
389
399
  }
390
400
 
391
401
  function usePageEffect (mpxProxy, pageId) {
402
+ const sizeRef = useRef(getSystemInfo())
403
+
392
404
  useEffect(() => {
393
405
  let unWatch
394
406
  const hasShowHook = hasPageHook(mpxProxy, [ONSHOW, 'show'])
@@ -399,27 +411,40 @@ function usePageEffect (mpxProxy, pageId) {
399
411
  unWatch = watch(() => pageStatusMap[pageId], (newVal) => {
400
412
  if (newVal === 'show' || newVal === 'hide') {
401
413
  triggerPageStatusHook(mpxProxy, newVal)
414
+ // 仅在尺寸确实变化时才触发resize事件
415
+ triggerResizeEvent(mpxProxy, sizeRef)
416
+
417
+ // 如果当前全局size与pagesize不一致,在show之后触发一次resize事件
418
+ if (newVal === 'show' && global.__mpxPageSizeCountMap[pageId] !== global.__mpxSizeCount) {
419
+ // 刷新__mpxPageSizeCountMap, 每个页面仅会执行一次,直接驱动render刷新
420
+ global.__mpxPageSizeCountMap[pageId] = global.__mpxSizeCount
421
+ }
402
422
  } else if (/^resize/.test(newVal)) {
403
- triggerResizeEvent(mpxProxy)
423
+ triggerResizeEvent(mpxProxy, sizeRef)
404
424
  }
405
425
  }, { sync: true })
406
426
  }
407
427
  }
408
428
  return () => {
409
429
  unWatch && unWatch()
430
+ del(global.__mpxPageSizeCountMap, pageId)
410
431
  }
411
432
  }, [])
412
433
  }
413
434
 
414
435
  let pageId = 0
415
436
  const pageStatusMap = global.__mpxPageStatusMap = reactive({})
416
-
417
437
  function usePageStatus (navigation, pageId) {
418
438
  navigation.pageId = pageId
419
439
  if (!hasOwn(pageStatusMap, pageId)) {
420
440
  set(pageStatusMap, pageId, '')
421
441
  }
422
442
  useEffect(() => {
443
+ if (navigation.isFocused && navigation.isFocused()) {
444
+ Promise.resolve().then(() => {
445
+ pageStatusMap[pageId] = 'show'
446
+ })
447
+ }
423
448
  const focusSubscription = navigation.addListener('focus', () => {
424
449
  pageStatusMap[pageId] = 'show'
425
450
  })
@@ -497,7 +522,6 @@ function getLayoutData (headerHeight) {
497
522
 
498
523
  export function PageWrapperHOC (WrappedComponent, pageConfig = {}) {
499
524
  return function PageWrapperCom ({ navigation, route, ...props }) {
500
- const rootRef = useRef(null)
501
525
  const keyboardAvoidRef = useRef(null)
502
526
  const intersectionObservers = useRef({})
503
527
  const currentPageId = useMemo(() => ++pageId, [])
@@ -515,7 +539,7 @@ export function PageWrapperHOC (WrappedComponent, pageConfig = {}) {
515
539
  navigation.layout = getLayoutData(headerHeight)
516
540
 
517
541
  useEffect(() => {
518
- const dimensionListener = ReactNative.Dimensions.addEventListener('change', ({ screen }) => {
542
+ const dimensionListener = ReactNative.Dimensions.addEventListener('change', ({ window, screen }) => {
519
543
  navigation.layout = getLayoutData(headerHeight)
520
544
  })
521
545
  return () => dimensionListener?.remove()
@@ -545,36 +569,36 @@ export function PageWrapperHOC (WrappedComponent, pageConfig = {}) {
545
569
  // android存在第一次打开insets都返回为0情况,后续会触发第二次渲染后正确
546
570
  navigation.insets = useSafeAreaInsets()
547
571
  return withKeyboardAvoidingView(
548
- createElement(ReactNative.View,
549
- {
550
- style: {
551
- flex: 1,
552
- backgroundColor: currentPageConfig?.backgroundColor || '#fff',
553
- // 解决页面内有元素定位relative left为负值的时候,回退的时候还能看到对应元素问题
554
- overflow: 'hidden'
555
- },
556
- ref: rootRef
557
- },
558
- createElement(RouteContext.Provider,
559
- {
560
- value: routeContextValRef.current
561
- },
562
- createElement(IntersectionObserverContext.Provider,
572
+ createElement(ReactNative.View,
563
573
  {
564
- value: intersectionObservers.current
574
+ style: {
575
+ flex: 1,
576
+ // 页面容器背景色
577
+ backgroundColor: currentPageConfig?.backgroundColorContent || '#fff',
578
+ // 解决页面内有元素定位relative left为负值的时候,回退的时候还能看到对应元素问题
579
+ overflow: 'hidden'
580
+ }
565
581
  },
566
- createElement(PortalHost,
567
- null,
568
- createElement(WrappedComponent, {
569
- ...props,
570
- navigation,
571
- route,
572
- id: currentPageId
573
- })
582
+ createElement(RouteContext.Provider,
583
+ {
584
+ value: routeContextValRef.current
585
+ },
586
+ createElement(IntersectionObserverContext.Provider,
587
+ {
588
+ value: intersectionObservers.current
589
+ },
590
+ createElement(PortalHost,
591
+ null,
592
+ createElement(WrappedComponent, {
593
+ ...props,
594
+ navigation,
595
+ route,
596
+ id: currentPageId
597
+ })
598
+ )
599
+ )
574
600
  )
575
- )
576
601
  )
577
- )
578
602
  )
579
603
  }
580
604
  }