@mpxjs/core 2.9.62 → 2.9.65

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.
@@ -8,4 +8,4 @@ declare let __mpx_env__: string
8
8
  declare module '*?resolve' {
9
9
  const resourcePath: string
10
10
  export default resourcePath
11
- }
11
+ }
package/@types/index.d.ts CHANGED
@@ -7,10 +7,8 @@
7
7
  /// <reference path="./global.d.ts" />
8
8
  /// <reference path="./node.d.ts" />
9
9
 
10
- // @ts-ignore
11
10
  import { GetComputedType } from '@mpxjs/store'
12
11
 
13
- // @ts-ignore
14
12
  export * from '@mpxjs/store'
15
13
 
16
14
  // utils
@@ -34,7 +32,7 @@ type Data = object | (() => object)
34
32
  export type PropType<T> = {
35
33
  __type: T
36
34
  } & (
37
- T extends String
35
+ T extends string
38
36
  ? StringConstructor
39
37
  : T extends number
40
38
  ? NumberConstructor
@@ -116,7 +114,7 @@ interface Context {
116
114
  refs: ObjectOf<WechatMiniprogram.NodesRef & ComponentIns<{}, {}, {}, {}, []>>
117
115
  asyncRefs: ObjectOf<Promise<WechatMiniprogram.NodesRef & ComponentIns<{}, {}, {}, {}, []>>>
118
116
 
119
- forceUpdate (params?: object, callback?: () => void): void
117
+ forceUpdate (params?: object, options?: object | (() => void), callback?: () => void): void
120
118
 
121
119
  selectComponent: ReplaceWxComponentIns['selectComponent']
122
120
  selectAllComponents: ReplaceWxComponentIns['selectAllComponents']
@@ -185,7 +183,7 @@ export interface MpxComponentIns {
185
183
 
186
184
  $watch (expr: string | (() => any), handler: WatchHandler | WatchOptWithHandler, options?: WatchOpt): () => void
187
185
 
188
- $forceUpdate (params?: object, callback?: () => void): void
186
+ $forceUpdate (params?: object, options?: object | (() => void), callback?: () => void): void
189
187
 
190
188
  $nextTick (fn: () => void): void
191
189
 
@@ -427,9 +425,7 @@ export interface ComputedRef<T = any> extends WritableComputedRef<T> {
427
425
  [ComputedRefSymbol]: true
428
426
  }
429
427
 
430
- export interface WritableComputedRef<T> extends Ref<T> {
431
- // readonly effect: ReactiveEffect<T>
432
- }
428
+ export type WritableComputedRef<T> = Ref<T>
433
429
 
434
430
  type WatchCallback<T> = (
435
431
  value: T,
@@ -463,7 +459,6 @@ interface EffectScope {
463
459
  resume (ignoreDirty?: boolean): void
464
460
  }
465
461
 
466
-
467
462
  type StringObj = {
468
463
  [k: string]: string | StringObj
469
464
  }
@@ -490,7 +485,6 @@ interface UseI18n {
490
485
  mergeLocaleMessage (locale: string, messages: StringObj): void
491
486
  }
492
487
 
493
-
494
488
  export function ref<T extends object> (
495
489
  value: T
496
490
  ): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
@@ -530,7 +524,6 @@ export function computed<T> (
530
524
  options: WritableComputedOptions<T>
531
525
  ): WritableComputedRef<T>
532
526
 
533
-
534
527
  export function watchEffect (
535
528
  effect: (onCleanup: (cleanupFn: () => void) => void) => void,
536
529
  options?: WatchEffectOptions
@@ -546,7 +539,6 @@ export function watchPostEffect (
546
539
  options?: WatchEffectOptions
547
540
  ): void
548
541
 
549
-
550
542
  export function watch<T extends MultiWatchSources> (
551
543
  sources: [...T],
552
544
  callback: WatchCallback<{
package/@types/node.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- // @ts-ignore
2
1
  declare let global: Record<string, any> // in web, we use global varible to do some things, here to declare
3
2
 
4
3
  type Dict<T> = {
@@ -7,7 +6,6 @@ type Dict<T> = {
7
6
 
8
7
  type EnvType = Dict<string>
9
8
 
10
- // @ts-ignore
11
9
  declare let process: {
12
10
  env: EnvType
13
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpxjs/core",
3
- "version": "2.9.62",
3
+ "version": "2.9.65",
4
4
  "description": "mpx runtime core",
5
5
  "keywords": [
6
6
  "miniprogram",
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "main": "src/index.js",
21
21
  "dependencies": {
22
- "@mpxjs/utils": "^2.9.59",
22
+ "@mpxjs/utils": "^2.9.65",
23
23
  "lodash": "^4.1.1",
24
24
  "miniprogram-api-typings": "^3.10.0"
25
25
  },
@@ -32,6 +32,7 @@
32
32
  "react": "*",
33
33
  "react-native": "*",
34
34
  "react-native-gesture-handler": "^2.19.0",
35
+ "react-native-linear-gradient": "^2.8.3",
35
36
  "react-native-safe-area-context": "^4.10.1",
36
37
  "react-native-webview": "^13.10.5",
37
38
  "vue": "^2.7.10",
@@ -75,6 +76,9 @@
75
76
  },
76
77
  "react-native-gesture-handler": {
77
78
  "optional": true
79
+ },
80
+ "react-native-linear-gradient": {
81
+ "optional": true
78
82
  }
79
83
  },
80
84
  "publishConfig": {
@@ -93,5 +97,5 @@
93
97
  "url": "https://github.com/didi/mpx/issues"
94
98
  },
95
99
  "sideEffects": false,
96
- "gitHead": "c31a343e7ef2c1c1585002752d8c50f016a858ae"
100
+ "gitHead": "24efa90e90b4d42c285ca61739cb9e4d0696976c"
97
101
  }
@@ -5,7 +5,7 @@ import {
5
5
  import { implemented } from '../core/implement'
6
6
 
7
7
  // 暂不支持的wx选项,后期需要各种花式支持
8
- const unsupported = ['relations', 'moved', 'definitionFilter', 'onShareAppMessage', 'options']
8
+ const unsupported = ['relations', 'moved', 'definitionFilter', 'onShareAppMessage']
9
9
 
10
10
  function convertErrorDesc (key) {
11
11
  error(`Options.${key} is not supported in runtime conversion from wx to react native.`, global.currentResource)
package/src/index.js CHANGED
@@ -138,6 +138,7 @@ Mpx.config = {
138
138
  ignoreProxyWhiteList: ['id', 'dataset', 'data'],
139
139
  observeClassInstance: false,
140
140
  errorHandler: null,
141
+ warnHandler: null,
141
142
  proxyEventHandler: null,
142
143
  setDataHandler: null,
143
144
  forceFlushSync: false,
@@ -14,13 +14,13 @@ import { dynamicRefsMixin, dynamicRenderHelperMixin, dynamicSlotMixin } from '..
14
14
  import styleHelperMixin from './styleHelperMixin'
15
15
  import directiveHelperMixin from './directiveHelperMixin'
16
16
 
17
- export default function getBuiltInMixins (options, type) {
17
+ export default function getBuiltInMixins ({ type, rawOptions = {} }) {
18
18
  let bulitInMixins
19
19
  if (__mpx_mode__ === 'ios' || __mpx_mode__ === 'android') {
20
20
  bulitInMixins = [
21
21
  proxyEventMixin(),
22
22
  directiveHelperMixin(),
23
- styleHelperMixin(type),
23
+ styleHelperMixin(),
24
24
  refsMixin(),
25
25
  i18nMixin()
26
26
  ]
@@ -46,7 +46,7 @@ export default function getBuiltInMixins (options, type) {
46
46
  relationsMixin(type)
47
47
  ]
48
48
  // 此为纯增强类mixins,原生模式下不需要注入
49
- if (!options.__nativeRender__) {
49
+ if (!rawOptions.__nativeRender__) {
50
50
  bulitInMixins = bulitInMixins.concat([
51
51
  renderHelperMixin(),
52
52
  showMixin(type),
@@ -14,23 +14,21 @@ function refreshMs () {
14
14
  }
15
15
  }
16
16
 
17
- let loading = null
18
-
19
17
  function showLoading (vm) {
20
18
  const { backgroundColor = 'transparent', backgroundTextStyle = 'dark' } = vm.$options.__mpxPageConfig
21
- loading = document.createElement('div')
22
- loading.className = 'pull-down-loading'
23
- loading.style.cssText = `background-color: ${backgroundColor};`
19
+ vm.__mpxloading = document.createElement('div')
20
+ vm.__mpxloading.className = 'pull-down-loading'
21
+ vm.__mpxloading.style.cssText = `background-color: ${backgroundColor};`
24
22
  const dot = document.createElement('div')
25
23
  dot.className = `dot-flashing ${backgroundTextStyle}`
26
- loading.append(dot)
27
- vm.$el.prepend(loading)
24
+ vm.__mpxloading.append(dot)
25
+ vm.$el.prepend(vm.__mpxloading)
28
26
  }
29
27
 
30
28
  function hideLoading (vm) {
31
- if (loading) {
32
- vm.$el.removeChild(loading)
33
- loading = null
29
+ if (vm.__mpxloading) {
30
+ vm.$el.removeChild(vm.__mpxloading)
31
+ vm.__mpxloading = null
34
32
  }
35
33
  }
36
34
 
@@ -1,78 +1,55 @@
1
- import { BEFORECREATE, CREATED } from '../../core/innerLifecycle'
1
+ import { BEFORECREATE } from '../../core/innerLifecycle'
2
2
  import { createSelectorQuery } from '@mpxjs/api-proxy'
3
- import { computed } from '../../observer/computed'
4
3
 
5
4
  export default function getRefsMixin () {
6
5
  return {
7
6
  [BEFORECREATE] () {
8
7
  this.__refs = {}
9
8
  this.$refs = {}
10
- },
11
- // __getRefs强依赖数据响应,需要在CREATED中执行
12
- [CREATED] () {
13
9
  this.__getRefs()
14
10
  },
15
11
  methods: {
16
12
  __getRefs () {
17
13
  const refs = this.__getRefsData() || []
18
14
  const target = this
19
- this.__selectorMap = computed(() => {
20
- const selectorMap = {}
21
- refs.forEach(({ key, type, sKeys }) => {
22
- // sKeys 是使用 wx:ref 没有值的标记场景,支持运行时的 createSelectorQuery 的使用
23
- if (sKeys) {
24
- sKeys.forEach((item = {}) => {
25
- const computedKey = item.key
26
- const prefix = item.prefix
27
- const selectors = this[computedKey] || ''
28
- selectors.trim().split(/\s+/).forEach(item => {
29
- const selector = prefix + item
30
- selectorMap[selector] = selectorMap[selector] || []
31
- selectorMap[selector].push({ type, key })
32
- })
33
- })
34
- } else {
35
- selectorMap[key] = selectorMap[key] || []
36
- selectorMap[key].push({ type, key })
15
+ refs.forEach(({ key, type, all }) => {
16
+ Object.defineProperty(this.$refs, key, {
17
+ enumerable: true,
18
+ configurable: true,
19
+ get () {
20
+ if (type === 'component') {
21
+ return all ? target.selectAllComponents(key) : target.selectComponent(key)
22
+ } else {
23
+ return createSelectorQuery().in(target).select(key, all)
24
+ }
37
25
  }
38
26
  })
39
- return selectorMap
40
- })
41
- refs.forEach(({ key, type, all, sKeys }) => {
42
- // 如果没有 sKey 说明使用的是 wx:ref="xxx" 的场景
43
- if (!sKeys) {
44
- Object.defineProperty(this.$refs, key, {
45
- enumerable: true,
46
- configurable: true,
47
- get () {
48
- const refs = target.__refs[key] || []
49
- if (type === 'component') {
50
- return all ? refs : refs[0]
51
- } else {
52
- return createSelectorQuery().in(target).select(key, all)
53
- }
54
- }
55
- })
56
- }
57
27
  })
58
28
  },
59
- __getRefVal (key) {
29
+ __getRefVal (type, selectorsConf) {
60
30
  return (instance) => {
61
31
  if (instance) {
62
- this.__refs[key] = this.__refs[key] || []
63
- this.__refs[key].push(instance)
32
+ selectorsConf.forEach((item = []) => {
33
+ const [prefix, selectors = ''] = item
34
+ if (selectors) {
35
+ selectors.trim().split(/\s+/).forEach(selector => {
36
+ const refKey = prefix + selector
37
+ this.__refs[refKey] = this.__refs[refKey] || []
38
+ this.__refs[refKey].push({ type, instance })
39
+ })
40
+ }
41
+ })
64
42
  }
65
43
  }
66
44
  },
67
45
  __selectRef (selector, refType, all = false) {
68
46
  const splitedSelector = selector.match(/(#|\.)?[^.#]+/g) || []
69
47
  const refsArr = splitedSelector.map(selector => {
70
- const selectorMap = this.__selectorMap?.value[selector] || []
48
+ const refs = this.__refs[selector] || []
71
49
  const res = []
72
- selectorMap.forEach(({ type, key }) => {
50
+ refs.forEach(({ type, instance }) => {
73
51
  if (type === refType) {
74
- const _refs = this.__refs[key] || []
75
- res.push(..._refs)
52
+ res.push(instance)
76
53
  }
77
54
  })
78
55
  return res
@@ -1,5 +1,69 @@
1
- import { isObject, isArray, dash2hump, isFunction } from '@mpxjs/utils'
2
- import { Dimensions } from 'react-native'
1
+ import { isObject, isArray, dash2hump, isFunction, cached } from '@mpxjs/utils'
2
+ import { Dimensions, StyleSheet } from 'react-native'
3
+
4
+ function rpx (value) {
5
+ const { width } = Dimensions.get('screen')
6
+ // rn 单位 dp = 1(css)px = 1 物理像素 * pixelRatio(像素比)
7
+ // px = rpx * (750 / 屏幕宽度)
8
+ return value * width / 750
9
+ }
10
+ function vw (value) {
11
+ const { width } = Dimensions.get('screen')
12
+ return value * width / 100
13
+ }
14
+ function vh (value) {
15
+ const { height } = Dimensions.get('screen')
16
+ return value * height / 100
17
+ }
18
+
19
+ const unit = {
20
+ rpx,
21
+ vw,
22
+ vh
23
+ }
24
+
25
+ function formatValue (value) {
26
+ let matched
27
+ if ((matched = numberRegExp.exec(value))) {
28
+ value = +matched[1]
29
+ } else if ((matched = unitRegExp.exec(value))) {
30
+ value = unit[matched[2]](+matched[1])
31
+ } else if (hairlineRegExp.test(value)) {
32
+ value = StyleSheet.hairlineWidth
33
+ }
34
+ return value
35
+ }
36
+
37
+ global.__formatValue = formatValue
38
+
39
+ const escapeReg = /[()[\]{}#!.:,%'"+$]/g
40
+ const escapeMap = {
41
+ '(': '_pl_',
42
+ ')': '_pr_',
43
+ '[': '_bl_',
44
+ ']': '_br_',
45
+ '{': '_cl_',
46
+ '}': '_cr_',
47
+ '#': '_h_',
48
+ '!': '_i_',
49
+ '/': '_s_',
50
+ '.': '_d_',
51
+ ':': '_c_',
52
+ ',': '_2c_',
53
+ '%': '_p_',
54
+ '\'': '_q_',
55
+ '"': '_dq_',
56
+ '+': '_a_',
57
+ $: '_si_'
58
+ }
59
+
60
+ const mpEscape = cached((str) => {
61
+ return str.replace(escapeReg, function (match) {
62
+ if (escapeMap[match]) return escapeMap[match]
63
+ // unknown escaped
64
+ return '_u_'
65
+ })
66
+ })
3
67
 
4
68
  function concat (a = '', b = '') {
5
69
  return a ? b ? (a + ' ' + b) : a : b
@@ -41,10 +105,13 @@ function stringifyDynamicClass (value) {
41
105
 
42
106
  const listDelimiter = /;(?![^(]*[)])/g
43
107
  const propertyDelimiter = /:(.+)/
44
- const rpxRegExp = /^\s*(-?\d+(\.\d+)?)rpx\s*$/
45
- const pxRegExp = /^\s*(-?\d+(\.\d+)?)(px)?\s*$/
108
+ const unitRegExp = /^\s*(-?\d+(?:\.\d+)?)(rpx|vw|vh)\s*$/
109
+ const numberRegExp = /^\s*(-?\d+(\.\d+)?)(px)?\s*$/
110
+ const hairlineRegExp = /^\s*hairlineWidth\s*$/
111
+ const varRegExp = /^--/
46
112
 
47
- function parseStyleText (cssText = '') {
113
+ const parseStyleText = cached((cssText) => {
114
+ if (typeof cssText !== 'string') return cssText
48
115
  const res = {}
49
116
  const arr = cssText.split(listDelimiter)
50
117
  for (let i = 0; i < arr.length; i++) {
@@ -52,13 +119,14 @@ function parseStyleText (cssText = '') {
52
119
  if (item) {
53
120
  const tmp = item.split(propertyDelimiter)
54
121
  if (tmp.length > 1) {
55
- const k = dash2hump(tmp[0].trim())
122
+ let k = tmp[0].trim()
123
+ k = varRegExp.test(k) ? k : dash2hump(k)
56
124
  res[k] = tmp[1].trim()
57
125
  }
58
126
  }
59
127
  }
60
128
  return res
61
- }
129
+ })
62
130
 
63
131
  function normalizeDynamicStyle (value) {
64
132
  if (!value) return {}
@@ -79,48 +147,34 @@ function mergeObjectArray (arr) {
79
147
  return res
80
148
  }
81
149
 
82
- function transformStyleObj (context, styleObj) {
83
- const keys = Object.keys(styleObj)
150
+ function transformStyleObj (styleObj) {
84
151
  const transformed = {}
85
- keys.forEach((prop) => {
86
- // todo 检测不支持的prop
87
- let value = styleObj[prop]
88
- let matched
89
- if ((matched = pxRegExp.exec(value))) {
90
- value = +matched[1]
91
- } else if ((matched = rpxRegExp.exec(value))) {
92
- value = context.__rpx(+matched[1])
93
- }
94
- // todo 检测不支持的value
95
- transformed[prop] = value
152
+ Object.keys(styleObj).forEach((prop) => {
153
+ transformed[prop] = formatValue(styleObj[prop])
96
154
  })
97
155
  return transformed
98
156
  }
99
157
 
100
- export default function styleHelperMixin (type) {
158
+ export default function styleHelperMixin () {
101
159
  return {
102
160
  methods: {
103
- __rpx (value) {
104
- const { width } = Dimensions.get('screen')
105
- // rn 单位 dp = 1(css)px = 1 物理像素 * pixelRatio(像素比)
106
- // px = rpx * (750 / 屏幕宽度)
107
- return value * width / 750
108
- },
109
161
  __getClass (staticClass, dynamicClass) {
110
162
  return concat(staticClass, stringifyDynamicClass(dynamicClass))
111
163
  },
112
- __getStyle (staticClass, dynamicClass, staticStyle, dynamicStyle, show) {
113
- // todo 每次返回新对象会导致react memo优化失效,需要考虑优化手段
164
+ __getStyle (staticClass, dynamicClass, staticStyle, dynamicStyle, hide) {
114
165
  const result = {}
115
166
  const classMap = {}
116
- if (type === 'page' && isFunction(global.__getAppClassMap)) {
117
- Object.assign(classMap, global.__getAppClassMap.call(this))
167
+ // todo 全局样式在每个页面和组件中生效,以支持全局原子类,后续支持样式模块复用后可考虑移除
168
+ if (isFunction(global.__getAppClassMap)) {
169
+ Object.assign(classMap, global.__getAppClassMap())
118
170
  }
119
171
  if (isFunction(this.__getClassMap)) {
120
172
  Object.assign(classMap, this.__getClassMap())
121
173
  }
174
+
122
175
  if (staticClass || dynamicClass) {
123
- const classString = concat(staticClass, stringifyDynamicClass(dynamicClass))
176
+ // todo 当前为了复用小程序unocss产物,暂时进行mpEscape,等后续正式支持unocss后可不进行mpEscape
177
+ const classString = mpEscape(concat(staticClass, stringifyDynamicClass(dynamicClass)))
124
178
  classString.split(/\s+/).forEach((className) => {
125
179
  if (classMap[className]) {
126
180
  Object.assign(result, classMap[className])
@@ -132,11 +186,11 @@ export default function styleHelperMixin (type) {
132
186
  }
133
187
 
134
188
  if (staticStyle || dynamicStyle) {
135
- const styleObj = Object.assign(parseStyleText(staticStyle), normalizeDynamicStyle(dynamicStyle))
136
- Object.assign(result, transformStyleObj(this, styleObj))
189
+ const styleObj = Object.assign({}, parseStyleText(staticStyle), normalizeDynamicStyle(dynamicStyle))
190
+ Object.assign(result, transformStyleObj(styleObj))
137
191
  }
138
192
 
139
- if (show === false) {
193
+ if (hide) {
140
194
  Object.assign(result, {
141
195
  display: 'none'
142
196
  })
@@ -58,7 +58,7 @@ export default function createFactory (type) {
58
58
  // 不接受mixin中的setup配置
59
59
  // 注入内建的mixins, 内建mixin是按原始平台编写的,所以合并规则和rootMixins保持一致
60
60
  // 将合并后的用户定义的rawOptions传入获取当前应该注入的内建mixins
61
- rawOptions.mixins = getBuiltInMixins(rawOptions, type)
61
+ rawOptions.mixins = getBuiltInMixins({ type, rawOptions, currentInject })
62
62
  const defaultOptions = getDefaultOptions({ type, rawOptions, currentInject })
63
63
  if (__mpx_mode__ === 'web' || __mpx_mode__ === 'ios' || __mpx_mode__ === 'android') {
64
64
  global.__mpxOptionsMap = global.__mpxOptionsMap || {}
@@ -1,9 +1,9 @@
1
- import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, useMemo, createElement, memo, forwardRef, useImperativeHandle, useContext, createContext, Fragment } from 'react'
1
+ import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, useMemo, createElement, memo, forwardRef, useImperativeHandle, useContext, createContext, Fragment, cloneElement } from 'react'
2
2
  import * as ReactNative from 'react-native'
3
3
  import { ReactiveEffect } from '../../../observer/effect'
4
4
  import { watch } from '../../../observer/watch'
5
5
  import { reactive, set, del } from '../../../observer/reactive'
6
- import { hasOwn, isFunction, noop, isObject, error, getByPath, collectDataset } from '@mpxjs/utils'
6
+ import { hasOwn, isFunction, noop, isObject, error, getByPath, collectDataset, hump2dash } 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'
@@ -40,21 +40,42 @@ function createEffect (proxy, components) {
40
40
  }
41
41
  update.id = proxy.uid
42
42
  const getComponent = (tagName) => {
43
+ if (!tagName) return null
43
44
  if (tagName === 'block') return Fragment
44
45
  return components[tagName] || getByPath(ReactNative, tagName)
45
46
  }
47
+ const innerCreateElement = (type, ...rest) => {
48
+ if (!type) return null
49
+ return createElement(type, ...rest)
50
+ }
46
51
  proxy.effect = new ReactiveEffect(() => {
47
52
  // reset instance
48
53
  proxy.target.__resetInstance()
49
- return proxy.target.__injectedRender(createElement, getComponent, proxy.target.__getRootProps())
54
+ return proxy.target.__injectedRender(innerCreateElement, getComponent)
50
55
  }, () => queueJob(update), proxy.scope)
51
56
  }
52
57
 
53
- function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components }) {
58
+ function getRootProps (props) {
59
+ const rootProps = {}
60
+ for (const key in props) {
61
+ if (hasOwn(props, key)) {
62
+ const match = /^(bind|catch|capture-bind|capture-catch|style|enable-var):?(.*?)(?:\.(.*))?$/.exec(key)
63
+ if (match) {
64
+ rootProps[key] = props[key]
65
+ }
66
+ }
67
+ }
68
+ return rootProps
69
+ }
70
+
71
+ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId }) {
54
72
  const instance = Object.create({
55
73
  setData (data, callback) {
56
74
  return this.__mpxProxy.forceUpdate(data, { sync: true }, callback)
57
75
  },
76
+ getPageId () {
77
+ return pageId
78
+ },
58
79
  __getProps () {
59
80
  const props = propsRef.current
60
81
  const propsData = {}
@@ -62,31 +83,23 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
62
83
  if (hasOwn(props, key)) {
63
84
  propsData[key] = props[key]
64
85
  } else {
65
- let field = validProps[key]
66
- if (isFunction(field) || field === null) {
67
- field = {
68
- type: field
86
+ const altKey = hump2dash(key)
87
+ if (hasOwn(props, altKey)) {
88
+ propsData[key] = props[altKey]
89
+ } else {
90
+ let field = validProps[key]
91
+ if (isFunction(field) || field === null) {
92
+ field = {
93
+ type: field
94
+ }
69
95
  }
96
+ // 处理props默认值
97
+ propsData[key] = field.value
70
98
  }
71
- // 处理props默认值
72
- propsData[key] = field.value
73
99
  }
74
100
  })
75
101
  return propsData
76
102
  },
77
- __getRootProps () {
78
- const props = propsRef.current
79
- const rootProps = {}
80
- for (const key in props) {
81
- if (hasOwn(props, key)) {
82
- const match = /^(bind|catch|capture-bind|capture-catch|style):?(.*?)(?:\.(.*))?$/.exec(key)
83
- if (match) {
84
- rootProps[key] = props[key]
85
- }
86
- }
87
- }
88
- return rootProps
89
- },
90
103
  __resetInstance () {
91
104
  this.__refs = {}
92
105
  this.__dispatchedSlotSet = new WeakSet()
@@ -97,22 +110,19 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
97
110
  const result = []
98
111
  if (Array.isArray(children)) {
99
112
  children.forEach(child => {
100
- if (child && child.props && child.props.slot === name) {
113
+ if (child?.props?.slot === name) {
101
114
  result.push(child)
102
115
  }
103
116
  })
104
117
  } else {
105
- if (children && children.props && children.props.slot === name) {
118
+ if (children?.props?.slot === name) {
106
119
  result.push(children)
107
120
  }
108
121
  }
109
122
  return result.filter(item => {
110
- if (this.__dispatchedSlotSet.has(item)) {
111
- return false
112
- } else {
113
- this.__dispatchedSlotSet.add(item)
114
- return true
115
- }
123
+ if (!isObject(item) || this.__dispatchedSlotSet.has(item)) return false
124
+ this.__dispatchedSlotSet.add(item)
125
+ return true
116
126
  })
117
127
  }
118
128
  return null
@@ -255,7 +265,7 @@ function hasPageHook (mpxProxy, hookNames) {
255
265
  })
256
266
  }
257
267
 
258
- const routeContext = createContext(null)
268
+ const RouteContext = createContext(null)
259
269
 
260
270
  const triggerPageStatusHook = (mpxProxy, event) => {
261
271
  mpxProxy.callHook(event === 'show' ? ONSHOW : ONHIDE)
@@ -279,13 +289,7 @@ const triggerResizeEvent = (mpxProxy) => {
279
289
  }
280
290
  }
281
291
 
282
- function usePageContext (mpxProxy, instance) {
283
- const { pageId } = useContext(routeContext) || {}
284
-
285
- instance.getPageId = () => {
286
- return pageId
287
- }
288
-
292
+ function usePageEffect (mpxProxy, pageId) {
289
293
  useEffect(() => {
290
294
  let unWatch
291
295
  const hasShowHook = hasPageHook(mpxProxy, [ONSHOW, 'show'])
@@ -302,7 +306,6 @@ function usePageContext (mpxProxy, instance) {
302
306
  })
303
307
  }
304
308
  }
305
-
306
309
  return () => {
307
310
  unWatch && unWatch()
308
311
  }
@@ -346,11 +349,12 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
346
349
  const defaultOptions = memo(forwardRef((props, ref) => {
347
350
  const instanceRef = useRef(null)
348
351
  const propsRef = useRef(null)
352
+ const pageId = useContext(RouteContext)
349
353
  propsRef.current = props
350
354
  let isFirst = false
351
355
  if (!instanceRef.current) {
352
356
  isFirst = true
353
- instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components })
357
+ instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId })
354
358
  }
355
359
  const instance = instanceRef.current
356
360
  useImperativeHandle(ref, () => {
@@ -364,9 +368,14 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
364
368
  useEffect(() => {
365
369
  if (!isFirst) {
366
370
  // 处理props更新
367
- Object.keys(props).forEach(key => {
368
- if (hasOwn(validProps, key)) {
371
+ Object.keys(validProps).forEach((key) => {
372
+ if (hasOwn(props, key)) {
369
373
  instance[key] = props[key]
374
+ } else {
375
+ const altKey = hump2dash(key)
376
+ if (hasOwn(props, altKey)) {
377
+ instance[key] = props[altKey]
378
+ }
370
379
  }
371
380
  })
372
381
  }
@@ -376,7 +385,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
376
385
  }
377
386
  })
378
387
 
379
- usePageContext(proxy, instance)
388
+ usePageEffect(proxy, pageId)
380
389
 
381
390
  useEffect(() => {
382
391
  if (type === 'page') {
@@ -385,6 +394,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
385
394
  proxy.mounted()
386
395
  return () => {
387
396
  proxy.unmounted()
397
+ proxy.target.__resetInstance()
388
398
  if (type === 'page') {
389
399
  delete global.__mpxPagesMap[props.route.key]
390
400
  }
@@ -393,50 +403,76 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
393
403
 
394
404
  useSyncExternalStore(proxy.subscribe, proxy.getSnapshot)
395
405
 
396
- return rawOptions.__disableMemo ? proxy.effect.run() : useMemo(() => proxy.effect.run(), [proxy.stateVersion])
406
+ const root = rawOptions.options?.disableMemo ? proxy.effect.run() : useMemo(() => proxy.effect.run(), [proxy.stateVersion])
407
+ if (root) {
408
+ const rootProps = getRootProps(props)
409
+ rootProps.style = { ...root.props.style, ...rootProps.style }
410
+ // update root props
411
+ return cloneElement(root, rootProps)
412
+ }
413
+ return root
397
414
  }))
398
415
 
399
416
  if (type === 'page') {
400
417
  const { Provider, useSafeAreaInsets, GestureHandlerRootView } = global.__navigationHelper
401
418
  const pageConfig = Object.assign({}, global.__mpxPageConfig, currentInject.pageConfig)
402
419
  const Page = ({ navigation, route }) => {
420
+ const rootRef = useRef(null)
403
421
  const currentPageId = useMemo(() => ++pageId, [])
404
422
  usePageStatus(navigation, currentPageId)
405
423
 
406
424
  useLayoutEffect(() => {
425
+ rootRef.current?.measureInWindow((x, y, width, height) => {
426
+ navigation.layout = { x, y, width, height }
427
+ })
428
+ const isCustom = pageConfig.navigationStyle === 'custom'
429
+ let opt = {}
430
+ if (__mpx_mode__ === 'android') {
431
+ opt = {
432
+ statusBarTranslucent: isCustom,
433
+ statusBarStyle: pageConfig.statusBarStyle, // 枚举值 'auto' | 'dark' | 'light' 控制statusbar字体颜色
434
+ statusBarColor: isCustom ? 'transparent' : pageConfig.statusBarColor // 控制statusbar背景颜色
435
+ }
436
+ } else if (__mpx_mode__ === 'ios') {
437
+ opt = {
438
+ headerBackTitleVisible: false
439
+ }
440
+ }
407
441
  navigation.setOptions({
408
- headerShown: pageConfig.navigationStyle !== 'custom',
442
+ headerShown: !isCustom,
443
+ headerShadowVisible: false,
409
444
  headerTitle: pageConfig.navigationBarTitleText || '',
410
445
  headerStyle: {
411
446
  backgroundColor: pageConfig.navigationBarBackgroundColor || '#000000'
412
447
  },
413
- headerTintColor: pageConfig.navigationBarTextStyle || 'white'
448
+ headerTitleAlign: 'center',
449
+ headerTintColor: pageConfig.navigationBarTextStyle || 'white',
450
+ ...opt
414
451
  })
415
452
  }, [])
416
453
 
417
454
  navigation.insets = useSafeAreaInsets()
418
455
 
419
- return createElement(Provider,
420
- null,
421
- createElement(GestureHandlerRootView,
422
- {
423
- style: {
424
- flex: 1,
425
- backgroundColor: pageConfig.backgroundColor || '#ffffff'
426
- },
427
- onLayout (e) {
428
- navigation.layout = e.nativeEvent.layout
429
- }
456
+ return createElement(GestureHandlerRootView,
457
+ {
458
+ style: {
459
+ flex: 1,
460
+ backgroundColor: pageConfig.backgroundColor || '#ffffff'
430
461
  },
431
- createElement(routeContext.Provider,
462
+ ref: rootRef
463
+ },
464
+ // todo custom portal host for active route
465
+ createElement(Provider,
466
+ null,
467
+ createElement(RouteContext.Provider,
432
468
  {
433
- value: { pageId: currentPageId }
469
+ value: currentPageId
434
470
  },
435
471
  createElement(defaultOptions,
436
472
  {
437
473
  navigation,
438
474
  route,
439
- pageConfig
475
+ id: currentPageId
440
476
  }
441
477
  )
442
478
  )