@mpxjs/core 2.10.17 → 2.10.18-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.
@@ -15,6 +15,52 @@ const setRef = function (target, ref, isAsync) {
15
15
  }
16
16
  })
17
17
  }
18
+ const proxyMethods = ['boundingClientRect', 'scrollOffset']
19
+ function rewriteAliCreateSelectorQuery (target) {
20
+ const rawCreateSelectorQuery = target.createSelectorQuery
21
+ target.createSelectorQuery = function (...args) {
22
+ let selectorQuery = rawCreateSelectorQuery.apply(target, args)
23
+ const cbs = []
24
+
25
+ if (typeof selectorQuery === 'undefined') {
26
+ // 兜底 selectorQuery 在ali为 undefined 情况
27
+ // 调用 createSelectorQuery时,组件实例已经被销毁,ali this._originCreateSelectorQuery 返回 undefined。导致后续 selectorQuery[name] 报错
28
+ // 方案:对齐微信,微信实例销毁时,其他调用正常,仅 createSelectorQuery.exec 不执行回调
29
+ // 复现:setTimeout 中调用,倒计时未回调时切换页面
30
+ selectorQuery = {}
31
+ // ['boundingClientRect', 'context', 'exec', 'fields', 'in', 'node', 'scrollOffset', 'select', 'selectAll', 'selectViewport', 'toImage']
32
+ const backupMethodKeys = Object.keys(envObj.createSelectorQuery())
33
+ const backupFn = function () {
34
+ return selectorQuery
35
+ }
36
+ backupMethodKeys.forEach(key => {
37
+ selectorQuery[key] = backupFn
38
+ })
39
+ return selectorQuery
40
+ }
41
+
42
+ proxyMethods.forEach((name) => {
43
+ const originalMethod = selectorQuery[name]
44
+ selectorQuery[name] = function (cb = noop) {
45
+ cbs.push(cb)
46
+ return originalMethod.call(this)
47
+ }
48
+ })
49
+
50
+ const originalExec = selectorQuery.exec
51
+ selectorQuery.exec = function (originalCb = noop) {
52
+ const cb = function (results) {
53
+ results.forEach((item, index) => {
54
+ cbs[index] && cbs[index](item)
55
+ })
56
+ originalCb(results)
57
+ }
58
+ return originalExec.call(this, cb)
59
+ }
60
+
61
+ return selectorQuery
62
+ }
63
+ }
18
64
 
19
65
  export default function getRefsMixin () {
20
66
  const refsMixin = {
@@ -24,8 +70,7 @@ export default function getRefsMixin () {
24
70
  this.__getRefs()
25
71
 
26
72
  if (__mpx_mode__ === 'ali') {
27
- this._originCreateSelectorQuery = this.createSelectorQuery
28
- this.createSelectorQuery = this._createSelectorQuery
73
+ rewriteAliCreateSelectorQuery(this)
29
74
  }
30
75
  },
31
76
  methods: {
@@ -68,51 +113,7 @@ export default function getRefsMixin () {
68
113
  }
69
114
  })
70
115
 
71
- const proxyMethods = ['boundingClientRect', 'scrollOffset']
72
-
73
116
  Object.assign(refsMixin.methods, {
74
- _createSelectorQuery (...args) {
75
- let selectorQuery = this._originCreateSelectorQuery(...args)
76
- const cbs = []
77
-
78
- if (typeof selectorQuery === 'undefined') {
79
- // 兜底 selectorQuery 在ali为 undefined 情况
80
- // 调用 createSelectorQuery时,组件实例已经被销毁,ali this._originCreateSelectorQuery 返回 undefined。导致后续 selectorQuery[name] 报错
81
- // 方案:对齐微信,微信实例销毁时,其他调用正常,仅 createSelectorQuery.exec 不执行回调
82
- // 复现:setTimeout 中调用,倒计时未回调时切换页面
83
- selectorQuery = {}
84
- // ['boundingClientRect', 'context', 'exec', 'fields', 'in', 'node', 'scrollOffset', 'select', 'selectAll', 'selectViewport', 'toImage']
85
- const backupMethodKeys = Object.keys(envObj.createSelectorQuery())
86
- const backupFn = function () {
87
- return selectorQuery
88
- }
89
- backupMethodKeys.forEach(key => {
90
- selectorQuery[key] = backupFn
91
- })
92
- return selectorQuery
93
- }
94
-
95
- proxyMethods.forEach((name) => {
96
- const originalMethod = selectorQuery[name]
97
- selectorQuery[name] = function (cb = noop) {
98
- cbs.push(cb)
99
- return originalMethod.call(this)
100
- }
101
- })
102
-
103
- const originalExec = selectorQuery.exec
104
- selectorQuery.exec = function (originalCb = noop) {
105
- const cb = function (results) {
106
- results.forEach((item, index) => {
107
- cbs[index] && cbs[index](item)
108
- })
109
- originalCb(results)
110
- }
111
- return originalExec.call(this, cb)
112
- }
113
-
114
- return selectorQuery
115
- },
116
117
  selectComponent (selector) {
117
118
  return this.$selectComponent(selector)
118
119
  },
@@ -1,44 +1,46 @@
1
1
  import { getByPath, hasOwn, isObject } from '@mpxjs/utils'
2
2
 
3
- export default function renderHelperMixin () {
4
- return {
5
- methods: {
6
- _i (val, handler) {
7
- let i, l, keys, key
8
- if (Array.isArray(val) || typeof val === 'string') {
9
- for (i = 0, l = val.length; i < l; i++) {
10
- handler.call(this, val[i], i)
11
- }
12
- } else if (typeof val === 'number') {
13
- for (i = 0; i < val; i++) {
14
- handler.call(this, i + 1, i)
15
- }
16
- } else if (isObject(val)) {
17
- keys = Object.keys(val)
18
- for (i = 0, l = keys.length; i < l; i++) {
19
- key = keys[i]
20
- handler.call(this, val[key], key, i)
21
- }
22
- }
23
- },
24
- // collect
25
- _c (key, value) {
26
- if (hasOwn(this.__mpxProxy.renderData, key)) {
27
- return this.__mpxProxy.renderData[key]
28
- }
29
- if (value === undefined) {
30
- value = getByPath(this, key)
31
- }
32
- this.__mpxProxy.renderData[key] = value
33
- return value
34
- },
35
- // simple collect
36
- _sc (key) {
37
- return (this.__mpxProxy.renderData[key] = this[key])
38
- },
39
- _r (skipPre, vnode) {
40
- this.__mpxProxy.renderWithData(skipPre, vnode)
3
+ export const renderHelperDefs = {
4
+ _i (val, handler) {
5
+ let i, l, keys, key
6
+ if (Array.isArray(val) || typeof val === 'string') {
7
+ for (i = 0, l = val.length; i < l; i++) {
8
+ handler.call(this, val[i], i)
9
+ }
10
+ } else if (typeof val === 'number') {
11
+ for (i = 0; i < val; i++) {
12
+ handler.call(this, i + 1, i)
41
13
  }
14
+ } else if (isObject(val)) {
15
+ keys = Object.keys(val)
16
+ for (i = 0, l = keys.length; i < l; i++) {
17
+ key = keys[i]
18
+ handler.call(this, val[key], key, i)
19
+ }
20
+ }
21
+ },
22
+ // collect
23
+ _c (key, value) {
24
+ if (hasOwn(this.__mpxProxy.renderData, key)) {
25
+ return this.__mpxProxy.renderData[key]
26
+ }
27
+ if (value === undefined) {
28
+ value = getByPath(this, key)
42
29
  }
30
+ this.__mpxProxy.renderData[key] = value
31
+ return value
32
+ },
33
+ // simple collect
34
+ _sc (key) {
35
+ return (this.__mpxProxy.renderData[key] = this[key])
36
+ },
37
+ _r (skipPre, vnode) {
38
+ this.__mpxProxy.renderWithData(skipPre, vnode)
39
+ }
40
+ }
41
+
42
+ export default function renderHelperMixin () {
43
+ return {
44
+ methods: renderHelperDefs
43
45
  }
44
46
  }
@@ -1,33 +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 } 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
- function customDimensions (dimensions) {
13
+ global.__GCC = function (className, classMap, classMapValueCache) {
14
+ if (!classMapValueCache.has(className)) {
15
+ const styleObj = classMap[className]?.(global.__formatValue)
16
+ styleObj && classMapValueCache.set(className, styleObj)
17
+ }
18
+ return classMapValueCache.get(className)
19
+ }
20
+
21
+ let dimensionsInfoInitialized = false
22
+ function useDimensionsInfo (dimensions) {
23
+ dimensionsInfoInitialized = true
12
24
  if (typeof Mpx.config.rnConfig?.customDimensions === 'function') {
13
25
  dimensions = Mpx.config.rnConfig.customDimensions(dimensions) || dimensions
14
26
  }
15
- width = dimensions.screen.width
16
- height = dimensions.screen.height
27
+ global.__mpxAppDimensionsInfo.window = dimensions.window
28
+ global.__mpxAppDimensionsInfo.screen = dimensions.screen
29
+ }
30
+
31
+ function getPageSize (window = global.__mpxAppDimensionsInfo.screen) {
32
+ return window.width + 'x' + window.height
17
33
  }
18
34
 
19
- Dimensions.addEventListener('change', customDimensions)
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
+ })
20
56
 
57
+ // TODO: 1 目前测试鸿蒙下折叠屏screen固定为展开状态下屏幕尺寸,仅window会变化,且window包含状态栏高度
58
+ // TODO: 2 存在部分安卓折叠屏机型在折叠/展开切换时,Dimensions监听到的width/height尺寸错误,并触发多次问题
21
59
  function rpx (value) {
60
+ const screenInfo = global.__mpxAppDimensionsInfo.screen
22
61
  // rn 单位 dp = 1(css)px = 1 物理像素 * pixelRatio(像素比)
23
62
  // px = rpx * (750 / 屏幕宽度)
24
- return value * width / 750
63
+ return value * screenInfo.width / 750
25
64
  }
26
65
  function vw (value) {
27
- return value * width / 100
66
+ const screenInfo = global.__mpxAppDimensionsInfo.screen
67
+ return value * screenInfo.width / 100
28
68
  }
29
69
  function vh (value) {
30
- return value * height / 100
70
+ const screenInfo = global.__mpxAppDimensionsInfo.screen
71
+ return value * screenInfo.height / 100
31
72
  }
32
73
 
33
74
  const unit = {
@@ -38,8 +79,14 @@ const unit = {
38
79
 
39
80
  const empty = {}
40
81
 
41
- function formatValue (value) {
42
- 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
+ }
43
90
  const matched = unitRegExp.exec(value)
44
91
  if (matched) {
45
92
  if (!matched[2] || matched[2] === 'px') {
@@ -180,29 +227,57 @@ function isNativeStyle (style) {
180
227
  )
181
228
  }
182
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
+
183
247
  export default function styleHelperMixin () {
184
248
  return {
185
249
  methods: {
250
+ __getSizeCount () {
251
+ return global.__mpxPageSizeCountMap[this.__pageId]
252
+ },
186
253
  __getClass (staticClass, dynamicClass) {
187
254
  return concat(staticClass, stringifyDynamicClass(dynamicClass))
188
255
  },
189
256
  __getStyle (staticClass, dynamicClass, staticStyle, dynamicStyle, hide) {
190
257
  const isNativeStaticStyle = staticStyle && isNativeStyle(staticStyle)
191
258
  let result = isNativeStaticStyle ? [] : {}
192
- const mergeResult = isNativeStaticStyle ? (o) => result.push(o) : (o) => Object.assign(result, o)
259
+ const mergeResult = isNativeStaticStyle ? (...args) => result.push(...args) : (...args) => Object.assign(result, ...args)
193
260
 
194
- const classMap = this.__getClassMap?.() || {}
195
- const appClassMap = global.__getAppClassMap?.() || {}
261
+ this.__getSizeCount()
196
262
 
197
263
  if (staticClass || dynamicClass) {
198
264
  // todo 当前为了复用小程序unocss产物,暂时进行mpEscape,等后续正式支持unocss后可不进行mpEscape
199
265
  const classString = mpEscape(concat(staticClass, stringifyDynamicClass(dynamicClass)))
266
+
200
267
  classString.split(/\s+/).forEach((className) => {
201
- if (classMap[className]) {
202
- mergeResult(classMap[className])
203
- } else if (appClassMap[className]) {
204
- // todo 全局样式在每个页面和组件中生效,以支持全局原子类,后续支持样式模块复用后可考虑移除
205
- mergeResult(appClassMap[className])
268
+ let localStyle, appStyle
269
+ if (localStyle = this.__getClassStyle?.(className)) {
270
+ if (localStyle._media?.length) {
271
+ mergeResult(localStyle._default, getMediaStyle(localStyle._media))
272
+ } else {
273
+ mergeResult(localStyle)
274
+ }
275
+ } else if (appStyle = global.__getAppClassStyle?.(className)) {
276
+ if (appStyle._media?.length) {
277
+ mergeResult(appStyle._default, getMediaStyle(appStyle._media))
278
+ } else {
279
+ mergeResult(appStyle)
280
+ }
206
281
  } else if (isObject(this.__props[className])) {
207
282
  // externalClasses必定以对象形式传递下来
208
283
  mergeResult(this.__props[className])
@@ -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
  }
@@ -124,7 +119,7 @@ export default function createApp (options) {
124
119
  const current = state.routes[state.index]
125
120
  options = {
126
121
  path: current.name,
127
- query: current.params,
122
+ query: current.name === global.__mpxInitialRouteName ? global.__mpxInitialRunParams : current.params,
128
123
  scene: 0,
129
124
  shareTicket: '',
130
125
  referrerInfo: {}
@@ -184,7 +179,7 @@ export default function createApp (options) {
184
179
  const current = state.routes[state.index]
185
180
  const options = {
186
181
  path: current.name,
187
- query: current.params,
182
+ query: current.name === global.__mpxInitialRouteName ? global.__mpxInitialRunParams : current.params,
188
183
  scene: 0,
189
184
  shareTicket: '',
190
185
  referrerInfo: {},
@@ -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
  }