@mpxjs/core 2.9.65 → 2.9.67

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.
@@ -4,6 +4,8 @@ declare let __mpx_mode__: 'wx' | 'ali' | 'swan' | 'qq' | 'tt' | 'web' | 'dd' | '
4
4
  // declaration for mpx env
5
5
  declare let __mpx_env__: string
6
6
 
7
+ declare const Mixin: WechatMiniprogram.Behavior.Constructor
8
+
7
9
  // Wildcard module declarations for ?resolve case
8
10
  declare module '*?resolve' {
9
11
  const resourcePath: string
package/@types/index.d.ts CHANGED
@@ -137,6 +137,11 @@ interface ComponentOpt<D extends Data, P extends Properties, C, M extends Method
137
137
 
138
138
  initData?: Record<string, any>
139
139
 
140
+ provide?: Record<string, any> | (() => Record<string, any>)
141
+ inject?:
142
+ | { [key: string]: string | Symbol | { from?: string | Symbol; default?: any } }
143
+ | Array<string>
144
+
140
145
  [index: string]: any
141
146
  }
142
147
 
@@ -259,13 +264,15 @@ interface MpxConfig {
259
264
  ignoreWarning: boolean | string | RegExp | ((msg: string, location: string, e: Error) => boolean)
260
265
  ignoreProxyWhiteList: Array<string>
261
266
  observeClassInstance: boolean | Array<AnyConstructor>
262
- errorHandler: (e: Error, target: ComponentIns<{}, {}, {}, {}, []>, hookName: string) => any | null
267
+ errorHandler: (msg: String, location: String, e: Error) => any | null
268
+ warnHandler: (msg: String, location: String, e: Error) => any | null
263
269
  proxyEventHandler: (e: WechatMiniprogram.CustomEvent) => any | null
264
270
  setDataHandler: (data: object, target: ComponentIns<{}, {}, {}, {}, []>) => any | null
265
271
  forceFlushSync: boolean,
266
272
  webRouteConfig: object,
267
273
  webConfig: object,
268
- webviewConfig?: WebviewConfig
274
+ webviewConfig: WebviewConfig,
275
+ rnConfig: object,
269
276
  }
270
277
 
271
278
  type SupportedMode = 'wx' | 'ali' | 'qq' | 'swan' | 'tt' | 'web' | 'qa'
@@ -306,6 +313,8 @@ export interface Mpx {
306
313
 
307
314
  delete: typeof del
308
315
 
316
+ provide: typeof provide
317
+
309
318
  config: MpxConfig
310
319
 
311
320
  i18n: {
@@ -570,6 +579,13 @@ export function onScopeDispose (fn: () => void): void
570
579
  export function set<T extends object> (target: T, key: string | number, value: any): void
571
580
  export function del<T extends object> (target: T, key: keyof T): void
572
581
 
582
+ // provide & inject
583
+ export declare function provide<T>(key: InjectionKey<T> | string | number, value: T): void;
584
+ export declare function inject<T>(key: InjectionKey<T> | string): T | undefined;
585
+ export declare function inject<T>(key: InjectionKey<T> | string, defaultValue: T, treatDefaultAsFactory?: false): T;
586
+ export declare function inject<T>(key: InjectionKey<T> | string, defaultValue: T | (() => T), treatDefaultAsFactory: true): T;
587
+ export declare interface InjectionKey<T> extends Symbol {}
588
+
573
589
  // nextTick
574
590
  export function nextTick (fn: () => any): void
575
591
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpxjs/core",
3
- "version": "2.9.65",
3
+ "version": "2.9.67",
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.65",
22
+ "@mpxjs/utils": "^2.9.67",
23
23
  "lodash": "^4.1.1",
24
24
  "miniprogram-api-typings": "^3.10.0"
25
25
  },
@@ -97,5 +97,5 @@
97
97
  "url": "https://github.com/didi/mpx/issues"
98
98
  },
99
99
  "sideEffects": false,
100
- "gitHead": "24efa90e90b4d42c285ca61739cb9e4d0696976c"
100
+ "gitHead": "b23d3850c16543c5998811b8d1d8e6ee7988c0f8"
101
101
  }
@@ -354,7 +354,7 @@ function transformHOOKS (options) {
354
354
  const componentHooksMap = makeMap(convertRule.lifecycle.component)
355
355
  for (const key in options) {
356
356
  // 使用Component创建page实例,页面专属生命周期&自定义方法需写在methods内部
357
- if (typeof options[key] === 'function' && key !== 'dataFn' && key !== 'setup' && !componentHooksMap[key]) {
357
+ if (typeof options[key] === 'function' && key !== 'dataFn' && key !== 'setup' && key !== 'provide' && !componentHooksMap[key]) {
358
358
  if (!options.methods) options.methods = {}
359
359
  options.methods[key] = options[key]
360
360
  delete options[key]
package/src/core/proxy.js CHANGED
@@ -8,10 +8,12 @@ import Mpx from '../index'
8
8
  import {
9
9
  noop,
10
10
  type,
11
+ isArray,
11
12
  isFunction,
12
13
  isObject,
13
14
  isEmptyObject,
14
15
  isPlainObject,
16
+ isWeb,
15
17
  doGetByPath,
16
18
  getByPath,
17
19
  setByPath,
@@ -25,6 +27,7 @@ import {
25
27
  processUndefined,
26
28
  getFirstKey,
27
29
  callWithErrorHandling,
30
+ wrapMethodsWithErrorHandling,
28
31
  warn,
29
32
  error,
30
33
  getEnvObj
@@ -47,6 +50,7 @@ import {
47
50
  } from './innerLifecycle'
48
51
  import contextMap from '../dynamic/vnode/context'
49
52
  import { getAst } from '../dynamic/astCache'
53
+ import { inject, provide } from '../platform/export/apiInject'
50
54
 
51
55
  let uid = 0
52
56
 
@@ -106,6 +110,7 @@ export default class MpxProxy {
106
110
  this.uid = uid++
107
111
  this.name = options.name || ''
108
112
  this.options = options
113
+ this.ignoreReactivePattern = this.options.options?.ignoreReactivePattern
109
114
  // beforeCreate -> created -> mounted -> unmounted
110
115
  this.state = BEFORECREATE
111
116
  this.ignoreProxyMap = makeMap(Mpx.config.ignoreProxyWhiteList)
@@ -135,6 +140,21 @@ export default class MpxProxy {
135
140
  this.initApi()
136
141
  }
137
142
 
143
+ processIgnoreReactive (obj) {
144
+ if (this.ignoreReactivePattern && isObject(obj)) {
145
+ Object.keys(obj).forEach((key) => {
146
+ if (this.ignoreReactivePattern.test(key)) {
147
+ Object.defineProperty(obj, key, {
148
+ enumerable: true,
149
+ // set configurable to false to skip defineReactive
150
+ configurable: false
151
+ })
152
+ }
153
+ })
154
+ }
155
+ return obj
156
+ }
157
+
138
158
  created () {
139
159
  if (__mpx_dynamic_runtime__) {
140
160
  // 缓存上下文,在 destoryed 阶段删除
@@ -144,11 +164,15 @@ export default class MpxProxy {
144
164
  // web中BEFORECREATE钩子通过vue的beforeCreate钩子单独驱动
145
165
  this.callHook(BEFORECREATE)
146
166
  setCurrentInstance(this)
167
+ // 在 props/data 初始化之前初始化 inject
168
+ this.initInject()
147
169
  this.initProps()
148
170
  this.initSetup()
149
171
  this.initData()
150
172
  this.initComputed()
151
173
  this.initWatch()
174
+ // 在 props/data 初始化之后初始化 provide
175
+ this.initProvide()
152
176
  unsetCurrentInstance()
153
177
  }
154
178
 
@@ -203,11 +227,26 @@ export default class MpxProxy {
203
227
  // 页面/组件销毁清除上下文的缓存
204
228
  contextMap.remove(this.uid)
205
229
  }
230
+ if (!isWeb && this.options.__type__ === 'page') {
231
+ // 小程序页面销毁时移除对应的 provide
232
+ if (isFunction(this.target.getPageId)) {
233
+ const pageId = this.target.getPageId()
234
+ const providesMap = global.__mpxProvidesMap
235
+ if (providesMap.__pages[pageId]) {
236
+ delete providesMap.__pages[pageId]
237
+ }
238
+ }
239
+ }
206
240
  this.callHook(BEFOREUNMOUNT)
207
241
  this.scope?.stop()
208
242
  if (this.update) this.update.active = false
209
243
  this.callHook(UNMOUNTED)
210
244
  this.state = UNMOUNTED
245
+ if (this._intersectionObservers) {
246
+ this._intersectionObservers.forEach((observer) => {
247
+ observer.disconnect()
248
+ })
249
+ }
211
250
  }
212
251
 
213
252
  isUnmounted () {
@@ -249,14 +288,14 @@ export default class MpxProxy {
249
288
  } else {
250
289
  this.props = diffAndCloneA(this.target.__getProps(this.options)).clone
251
290
  }
252
- reactive(this.props)
291
+ reactive(this.processIgnoreReactive(this.props))
253
292
  proxy(this.target, this.props, undefined, false, this.createProxyConflictHandler('props'))
254
293
  }
255
294
 
256
295
  initSetup () {
257
296
  const setup = this.options.setup
258
297
  if (setup) {
259
- const setupResult = callWithErrorHandling(setup, this, 'setup function', [
298
+ let setupResult = callWithErrorHandling(setup, this, 'setup function', [
260
299
  this.props,
261
300
  {
262
301
  triggerEvent: this.target.triggerEvent ? this.target.triggerEvent.bind(this.target) : noop,
@@ -273,6 +312,7 @@ export default class MpxProxy {
273
312
  error(`Setup() should return a object, received: ${type(setupResult)}.`, this.options.mpxFileResource)
274
313
  return
275
314
  }
315
+ setupResult = wrapMethodsWithErrorHandling(setupResult, this)
276
316
  proxy(this.target, setupResult, undefined, false, this.createProxyConflictHandler('setup result'))
277
317
  this.collectLocalKeys(setupResult, (key, val) => !isFunction(val))
278
318
  }
@@ -287,7 +327,7 @@ export default class MpxProxy {
287
327
  if (isFunction(dataFn)) {
288
328
  Object.assign(this.data, callWithErrorHandling(dataFn.bind(this.target), this, 'data function'))
289
329
  }
290
- reactive(this.data)
330
+ reactive(this.processIgnoreReactive(this.data))
291
331
  proxy(this.target, this.data, undefined, false, this.createProxyConflictHandler('data'))
292
332
  this.collectLocalKeys(this.data)
293
333
  }
@@ -329,6 +369,55 @@ export default class MpxProxy {
329
369
  }
330
370
  }
331
371
 
372
+ initProvide () {
373
+ const provideOpt = this.options.provide
374
+ if (provideOpt) {
375
+ const provided = isFunction(provideOpt)
376
+ ? callWithErrorHandling(provideOpt.bind(this.target), this, 'provide function')
377
+ : provideOpt
378
+ if (!isObject(provided)) {
379
+ return
380
+ }
381
+ Object.keys(provided).forEach(key => {
382
+ provide(key, provided[key])
383
+ })
384
+ }
385
+ }
386
+
387
+ initInject () {
388
+ const injectOpt = this.options.inject
389
+ if (injectOpt) {
390
+ this.resolveInject(injectOpt)
391
+ }
392
+ }
393
+
394
+ resolveInject (injectOpt) {
395
+ if (isArray(injectOpt)) {
396
+ const normalized = {}
397
+ for (let i = 0; i < injectOpt.length; i++) {
398
+ normalized[injectOpt[i]] = injectOpt[i]
399
+ }
400
+ injectOpt = normalized
401
+ }
402
+ const injectObj = {}
403
+ for (const key in injectOpt) {
404
+ const opt = injectOpt[key]
405
+ let injected
406
+ if (isObject(opt)) {
407
+ if ('default' in opt) {
408
+ injected = inject(opt.from || key, opt.default, true)
409
+ } else {
410
+ injected = inject(opt.from || key)
411
+ }
412
+ } else {
413
+ injected = inject(opt)
414
+ }
415
+ injectObj[key] = injected
416
+ }
417
+ proxy(this.target, injectObj, undefined, false, this.createProxyConflictHandler('inject'))
418
+ this.collectLocalKeys(injectObj)
419
+ }
420
+
332
421
  watch (source, cb, options) {
333
422
  const target = this.target
334
423
  const getter = isString(source)
@@ -419,7 +508,7 @@ export default class MpxProxy {
419
508
  if (hasOwn(renderData, key)) {
420
509
  const data = renderData[key]
421
510
  const firstKey = getFirstKey(key)
422
- if (!this.localKeysMap[firstKey]) {
511
+ if (!this.localKeysMap[firstKey] || (this.ignoreReactivePattern && this.ignoreReactivePattern.test(firstKey))) {
423
512
  continue
424
513
  }
425
514
  // 外部clone,用于只需要clone的场景
@@ -1,4 +1,4 @@
1
- import { isObject, isArray, dash2hump, isFunction, cached } from '@mpxjs/utils'
1
+ import { isObject, isArray, dash2hump, isFunction, cached, getFocusedNavigation } from '@mpxjs/utils'
2
2
  import { Dimensions, StyleSheet } from 'react-native'
3
3
 
4
4
  function rpx (value) {
@@ -12,7 +12,8 @@ function vw (value) {
12
12
  return value * width / 100
13
13
  }
14
14
  function vh (value) {
15
- const { height } = Dimensions.get('screen')
15
+ const navigation = getFocusedNavigation()
16
+ const height = navigation?.layout?.height || Dimensions.get('screen').height
16
17
  return value * height / 100
17
18
  }
18
19
 
@@ -1,6 +1,6 @@
1
1
  import transferOptions from '../core/transferOptions'
2
2
  import builtInKeysMap from './patch/builtInKeysMap'
3
- import { makeMap, spreadProp } from '@mpxjs/utils'
3
+ import { makeMap, spreadProp, parseUrlQuery } from '@mpxjs/utils'
4
4
  import { mergeLifecycle } from '../convertor/mergeLifecycle'
5
5
  import * as wxLifecycle from '../platform/patch/wx/lifecycle'
6
6
  import Mpx from '../index'
@@ -40,7 +40,7 @@ function createAppInstance (appData) {
40
40
  export default function createApp (option, config = {}) {
41
41
  const appData = {}
42
42
 
43
- const { NavigationContainer, createNavigationContainerRef, createNativeStackNavigator, SafeAreaProvider } = global.__navigationHelper
43
+ const { NavigationContainer, createStackNavigator, SafeAreaProvider } = global.__navigationHelper
44
44
  // app选项目前不需要进行转换
45
45
  const { rawOptions, currentInject } = transferOptions(option, 'app', false)
46
46
  const defaultOptions = filterOptions(spreadProp(rawOptions, 'methods'), appData)
@@ -51,16 +51,25 @@ export default function createApp (option, config = {}) {
51
51
  }
52
52
  const pages = currentInject.getPages() || {}
53
53
  const firstPage = currentInject.firstPage
54
- const Stack = createNativeStackNavigator()
55
- const navigationRef = createNavigationContainerRef()
56
- const pageScreens = Object.entries(pages).map(([key, item]) => {
57
- return createElement(Stack.Screen, {
58
- name: key,
59
- component: item
54
+ const Stack = createStackNavigator()
55
+ const getPageScreens = (initialRouteName, initialParams) => {
56
+ return Object.entries(pages).map(([key, item]) => {
57
+ if (key === initialRouteName) {
58
+ return createElement(Stack.Screen, {
59
+ name: key,
60
+ component: item,
61
+ initialParams
62
+ })
63
+ }
64
+ return createElement(Stack.Screen, {
65
+ name: key,
66
+ component: item
67
+ })
60
68
  })
61
- })
69
+ }
62
70
  global.__mpxOptionsMap = global.__mpxOptionsMap || {}
63
- const onStateChange = () => {
71
+ const onStateChange = (state) => {
72
+ Mpx.config.rnConfig.onStateChange?.(state)
64
73
  if (global.__navigationHelper.lastSuccessCallback) {
65
74
  global.__navigationHelper.lastSuccessCallback()
66
75
  global.__navigationHelper.lastSuccessCallback = null
@@ -81,26 +90,48 @@ export default function createApp (option, config = {}) {
81
90
  error: []
82
91
  }
83
92
 
93
+ global.__mpxAppLaunched = false
94
+
84
95
  global.__mpxAppFocusedState = ref('show')
85
- global.__mpxOptionsMap[currentInject.moduleId] = memo(() => {
96
+ global.__mpxOptionsMap[currentInject.moduleId] = memo((props) => {
86
97
  const instanceRef = useRef(null)
87
98
  if (!instanceRef.current) {
88
99
  instanceRef.current = createAppInstance(appData)
89
100
  }
90
101
  const instance = instanceRef.current
91
- useEffect(() => {
92
- const current = navigationRef.isReady() ? navigationRef.getCurrentRoute() : {}
93
- const options = {
94
- path: current.name,
95
- query: current.params,
96
- scene: 0,
97
- shareTicket: '',
98
- referrerInfo: {}
102
+ const initialRouteRef = useRef({
103
+ initialRouteName: firstPage,
104
+ initialParams: {}
105
+ })
106
+
107
+ if (!global.__mpxAppLaunched) {
108
+ const parsed = Mpx.config.rnConfig.parseAppProps?.(props) || {}
109
+ if (parsed.url) {
110
+ const { path, queryObj } = parseUrlQuery(parsed.url)
111
+ Object.assign(initialRouteRef.current, {
112
+ initialRouteName: path.startsWith('/') ? path.slice(1) : path,
113
+ initialParams: queryObj
114
+ })
115
+ }
116
+ global.__mpxAppOnLaunch = (navigation) => {
117
+ global.__mpxAppLaunched = true
118
+ const state = navigation.getState()
119
+ Mpx.config.rnConfig.onStateChange?.(state)
120
+ const current = state.routes[state.index]
121
+ global.__mpxEnterOptions = {
122
+ path: current.name,
123
+ query: current.params,
124
+ scene: 0,
125
+ shareTicket: '',
126
+ referrerInfo: {}
127
+ }
128
+ defaultOptions.onLaunch && defaultOptions.onLaunch.call(instance, global.__mpxEnterOptions)
129
+ defaultOptions.onShow && defaultOptions.onShow.call(instance, global.__mpxEnterOptions)
99
130
  }
100
- global.__mpxEnterOptions = options
101
- defaultOptions.onLaunch && defaultOptions.onLaunch.call(instance, options)
131
+ }
132
+
133
+ useEffect(() => {
102
134
  if (defaultOptions.onShow) {
103
- defaultOptions.onShow.call(instance, options)
104
135
  global.__mpxAppCbs.show.push(defaultOptions.onShow.bind(instance))
105
136
  }
106
137
  if (defaultOptions.onHide) {
@@ -113,7 +144,7 @@ export default function createApp (option, config = {}) {
113
144
  const changeSubscription = ReactNative.AppState.addEventListener('change', (currentState) => {
114
145
  if (currentState === 'active') {
115
146
  global.__mpxAppCbs.show.forEach((cb) => {
116
- cb(options)
147
+ cb(global.__mpxEnterOptions)
117
148
  })
118
149
  global.__mpxAppFocusedState.value = 'show'
119
150
  } else if (currentState === 'inactive') {
@@ -138,19 +169,22 @@ export default function createApp (option, config = {}) {
138
169
  }
139
170
  }, [])
140
171
 
172
+ const { initialRouteName, initialParams } = initialRouteRef.current
141
173
  return createElement(SafeAreaProvider,
142
174
  null,
143
175
  createElement(NavigationContainer,
144
176
  {
145
- ref: navigationRef,
146
177
  onStateChange,
147
178
  onUnhandledAction
148
179
  },
149
180
  createElement(Stack.Navigator,
150
181
  {
151
- initialRouteName: firstPage
182
+ initialRouteName,
183
+ headerBackButtonDisplayMode: 'minimal',
184
+ headerMode: 'float',
185
+ gestureEnabled: true
152
186
  },
153
- ...pageScreens
187
+ ...getPageScreens(initialRouteName, initialParams)
154
188
  )
155
189
  )
156
190
  )
@@ -5,6 +5,7 @@ import { makeMap, spreadProp, isBrowser } from '@mpxjs/utils'
5
5
  import { mergeLifecycle } from '../convertor/mergeLifecycle'
6
6
  import * as webLifecycle from '../platform/patch/web/lifecycle'
7
7
  import Mpx from '../index'
8
+ import { initAppProvides } from './export/apiInject'
8
9
 
9
10
  const webAppHooksMap = makeMap(mergeLifecycle(webLifecycle.LIFECYCLE).app)
10
11
 
@@ -14,7 +15,7 @@ function filterOptions (options, appData) {
14
15
  if (builtInKeysMap[key]) {
15
16
  return
16
17
  }
17
- if (__mpx_mode__ === 'web' && !webAppHooksMap[key]) {
18
+ if (__mpx_mode__ === 'web' && !webAppHooksMap[key] && key !== 'provide') {
18
19
  appData[key] = options[key]
19
20
  } else {
20
21
  newOptions[key] = options[key]
@@ -87,6 +88,7 @@ export default function createApp (option, config = {}) {
87
88
  return appData
88
89
  }
89
90
  } else {
91
+ initAppProvides(rawOptions)
90
92
  defaultOptions.onAppInit && defaultOptions.onAppInit()
91
93
  const ctor = config.customCtor || global.currentCtor || App
92
94
  ctor(defaultOptions)
@@ -0,0 +1,68 @@
1
+ import { callWithErrorHandling, isFunction, isObject, warn } from '@mpxjs/utils'
2
+ import { currentInstance } from '../../core/proxy'
3
+
4
+ const providesMap = {
5
+ /** 全局 scope */
6
+ __app: Object.create(null),
7
+ /** 页面 scope */
8
+ __pages: Object.create(null)
9
+ }
10
+
11
+ global.__mpxProvidesMap = providesMap
12
+
13
+ /** @internal createApp() 初始化应用层 scope provide */
14
+ export function initAppProvides (appOptions) {
15
+ const provideOpt = appOptions.provide
16
+ if (provideOpt) {
17
+ const provided = isFunction(provideOpt)
18
+ ? callWithErrorHandling(provideOpt.bind(appOptions), appOptions, 'createApp provide function')
19
+ : provideOpt
20
+ if (isObject(provided)) {
21
+ providesMap.__app = provided
22
+ } else {
23
+ warn('App provides must be an object or a function that returns an object.')
24
+ }
25
+ }
26
+ }
27
+
28
+ function resolvePageId (context) {
29
+ if (context && isFunction(context.getPageId)) {
30
+ return context.getPageId()
31
+ }
32
+ }
33
+
34
+ function resolvePageProvides (context) {
35
+ const pageId = resolvePageId(context)
36
+ return providesMap.__pages[pageId] || (providesMap.__pages[pageId] = Object.create(null))
37
+ }
38
+
39
+ export function provide (key, value) {
40
+ const instance = currentInstance
41
+ if (!instance) {
42
+ warn('provide() can only be used inside setup().')
43
+ return
44
+ }
45
+ // 小程序无法实现组件父级引用,所以 provide scope 设置为组件所在页面
46
+ const provides = resolvePageProvides(instance.target)
47
+ provides[key] = value
48
+ }
49
+
50
+ export function inject (key, defaultValue, treatDefaultAsFactory = false) {
51
+ const instance = currentInstance
52
+ if (!instance) {
53
+ warn('inject() can only be used inside setup()')
54
+ return
55
+ }
56
+ const provides = resolvePageProvides(instance.target)
57
+ if (key in provides) {
58
+ return provides[key]
59
+ } else if (key in providesMap.__app) {
60
+ return providesMap.__app[key]
61
+ } else if (arguments.length > 1) {
62
+ return treatDefaultAsFactory && isFunction(defaultValue)
63
+ ? defaultValue.call(instance && instance.target)
64
+ : defaultValue
65
+ } else {
66
+ warn(`injection "${String(key)}" not found.`)
67
+ }
68
+ }
@@ -0,0 +1 @@
1
+ export { provide, inject } from 'vue'
@@ -1,4 +1,3 @@
1
-
2
1
  export {
3
2
  watchEffect,
4
3
  watchSyncEffect,
@@ -43,3 +42,8 @@ export {
43
42
  export {
44
43
  useI18n
45
44
  } from '../../platform/builtInMixins/i18nMixin'
45
+
46
+ export {
47
+ provide,
48
+ inject
49
+ } from './apiInject'
@@ -27,7 +27,9 @@ export {
27
27
  // effectScope
28
28
  effectScope,
29
29
  getCurrentScope,
30
- onScopeDispose
30
+ onScopeDispose,
31
+ provide,
32
+ inject
31
33
  } from 'vue'
32
34
 
33
35
  export {
@@ -1,7 +1,7 @@
1
1
  import MpxProxy from '../../../core/proxy'
2
2
  import builtInKeysMap from '../builtInKeysMap'
3
3
  import mergeOptions from '../../../core/mergeOptions'
4
- import { error, diffAndCloneA, hasOwn, noop } from '@mpxjs/utils'
4
+ import { error, diffAndCloneA, hasOwn, noop, wrapMethodsWithErrorHandling } from '@mpxjs/utils'
5
5
 
6
6
  function transformApiForProxy (context, currentInject) {
7
7
  const rawSetData = context.setData.bind(context)
@@ -100,12 +100,25 @@ function filterOptions (options, type) {
100
100
  if (!hasOwn(newOptions, 'props')) {
101
101
  newOptions.props = Object.assign({}, options.props, options.properties)
102
102
  }
103
- } else if (key === 'methods' && type === 'page') {
104
- Object.assign(newOptions, options[key])
103
+ } else if (key === 'methods') {
104
+ const newMethods = wrapMethodsWithErrorHandling(options[key])
105
+ if (type === 'page') {
106
+ // 构造器为Page时抽取所有methods方法到顶层
107
+ Object.assign(newOptions, newMethods)
108
+ } else {
109
+ newOptions[key] = newMethods
110
+ }
111
+ } else if (key === 'behaviors') {
112
+ newOptions.mixins = options[key]
105
113
  } else {
106
114
  newOptions[key] = options[key]
107
115
  }
108
116
  })
117
+ if (newOptions.relations) {
118
+ // ali relations 需要设置 options.relations = true
119
+ newOptions.options = newOptions.options || {}
120
+ newOptions.options.relations = true
121
+ }
109
122
  return newOptions
110
123
  }
111
124
 
@@ -7,6 +7,7 @@ if (__mpx_mode__ === 'web') {
7
7
  builtInKeys = [
8
8
  'proto',
9
9
  'mixins',
10
+ 'initData',
10
11
  'mpxCustomKeysForBlend',
11
12
  'mpxConvertMode',
12
13
  'mpxFileResource',
@@ -20,8 +21,11 @@ if (__mpx_mode__ === 'web') {
20
21
  'dataFn',
21
22
  'proto',
22
23
  'mixins',
24
+ 'initData',
23
25
  'watch',
24
26
  'computed',
27
+ 'provide',
28
+ 'inject',
25
29
  'mpxCustomKeysForBlend',
26
30
  'mpxConvertMode',
27
31
  'mpxFileResource',
@@ -1,14 +1,15 @@
1
- import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, useMemo, createElement, memo, forwardRef, useImperativeHandle, useContext, createContext, Fragment, cloneElement } from 'react'
1
+ import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, useMemo, useCallback, 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, hump2dash } from '@mpxjs/utils'
6
+ import { hasOwn, isFunction, noop, isObject, getByPath, collectDataset, hump2dash, wrapMethodsWithErrorHandling } 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 } from '../../../observer/scheduler'
11
- import { createSelectorQuery } from '@mpxjs/api-proxy'
11
+ import { createSelectorQuery, createIntersectionObserver } from '@mpxjs/api-proxy'
12
+ import { IntersectionObserverContext } from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/context'
12
13
 
13
14
  function getSystemInfo () {
14
15
  const window = ReactNative.Dimensions.get('window')
@@ -68,7 +69,7 @@ function getRootProps (props) {
68
69
  return rootProps
69
70
  }
70
71
 
71
- function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId }) {
72
+ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx }) {
72
73
  const instance = Object.create({
73
74
  setData (data, callback) {
74
75
  return this.__mpxProxy.forceUpdate(data, { sync: true }, callback)
@@ -183,8 +184,8 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
183
184
  createSelectorQuery () {
184
185
  return createSelectorQuery().in(this)
185
186
  },
186
- createIntersectionObserver () {
187
- error('createIntersectionObserver is not supported in react native, please use ref instead')
187
+ createIntersectionObserver (opt) {
188
+ return createIntersectionObserver(this, opt, intersectionCtx)
188
189
  },
189
190
  ...rawOptions.methods
190
191
  }, {
@@ -346,15 +347,17 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
346
347
  rawOptions = mergeOptions(rawOptions, type, false)
347
348
  const components = Object.assign({}, rawOptions.components, currentInject.getComponents())
348
349
  const validProps = Object.assign({}, rawOptions.props, rawOptions.properties)
350
+ if (rawOptions.methods) rawOptions.methods = wrapMethodsWithErrorHandling(rawOptions.methods)
349
351
  const defaultOptions = memo(forwardRef((props, ref) => {
350
352
  const instanceRef = useRef(null)
351
353
  const propsRef = useRef(null)
354
+ const intersectionCtx = useContext(IntersectionObserverContext)
352
355
  const pageId = useContext(RouteContext)
353
356
  propsRef.current = props
354
357
  let isFirst = false
355
358
  if (!instanceRef.current) {
356
359
  isFirst = true
357
- instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId })
360
+ instanceRef.current = createInstance({ propsRef, type, rawOptions, currentInject, validProps, components, pageId, intersectionCtx })
358
361
  }
359
362
  const instance = instanceRef.current
360
363
  useImperativeHandle(ref, () => {
@@ -363,7 +366,21 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
363
366
 
364
367
  const proxy = instance.__mpxProxy
365
368
 
366
- proxy.callHook(REACTHOOKSEXEC)
369
+ let hooksResult = proxy.callHook(REACTHOOKSEXEC, [props])
370
+ if (isObject(hooksResult)) {
371
+ hooksResult = wrapMethodsWithErrorHandling(hooksResult, proxy)
372
+ if (isFirst) {
373
+ const onConflict = proxy.createProxyConflictHandler('react hooks result')
374
+ Object.keys(hooksResult).forEach((key) => {
375
+ if (key in proxy.target) {
376
+ onConflict(key)
377
+ }
378
+ proxy.target[key] = hooksResult[key]
379
+ })
380
+ } else {
381
+ Object.assign(proxy.target, hooksResult)
382
+ }
383
+ }
367
384
 
368
385
  useEffect(() => {
369
386
  if (!isFirst) {
@@ -389,6 +406,9 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
389
406
 
390
407
  useEffect(() => {
391
408
  if (type === 'page') {
409
+ if (!global.__mpxAppLaunched && global.__mpxAppOnLaunch) {
410
+ global.__mpxAppOnLaunch(props.navigation)
411
+ }
392
412
  proxy.callHook(ONLOAD, [props.route.params || {}])
393
413
  }
394
414
  proxy.mounted()
@@ -413,74 +433,88 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
413
433
  return root
414
434
  }))
415
435
 
436
+ if (rawOptions.options?.isCustomText) {
437
+ defaultOptions.isCustomText = true
438
+ }
439
+
416
440
  if (type === 'page') {
417
441
  const { Provider, useSafeAreaInsets, GestureHandlerRootView } = global.__navigationHelper
418
442
  const pageConfig = Object.assign({}, global.__mpxPageConfig, currentInject.pageConfig)
419
443
  const Page = ({ navigation, route }) => {
420
- const rootRef = useRef(null)
421
444
  const currentPageId = useMemo(() => ++pageId, [])
445
+ const intersectionObservers = useRef({})
422
446
  usePageStatus(navigation, currentPageId)
423
447
 
424
448
  useLayoutEffect(() => {
425
- rootRef.current?.measureInWindow((x, y, width, height) => {
426
- navigation.layout = { x, y, width, height }
427
- })
428
449
  const isCustom = pageConfig.navigationStyle === 'custom'
429
- let opt = {}
450
+ const opt = {}
430
451
  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
- }
452
+ // opt = {
453
+ // statusBarTranslucent: isCustom,
454
+ // statusBarStyle: pageConfig.statusBarStyle, // 枚举值 'auto' | 'dark' | 'light' 控制statusbar字体颜色
455
+ // statusBarColor: isCustom ? 'transparent' : pageConfig.statusBarColor // 控制statusbar背景颜色
456
+ // }
440
457
  }
441
458
  navigation.setOptions({
442
459
  headerShown: !isCustom,
443
- headerShadowVisible: false,
444
- headerTitle: pageConfig.navigationBarTitleText || '',
460
+ title: pageConfig.navigationBarTitleText || '',
445
461
  headerStyle: {
446
462
  backgroundColor: pageConfig.navigationBarBackgroundColor || '#000000'
447
463
  },
448
- headerTitleAlign: 'center',
464
+ // headerTitleAlign: 'center',
449
465
  headerTintColor: pageConfig.navigationBarTextStyle || 'white',
450
466
  ...opt
451
467
  })
452
468
  }, [])
453
469
 
470
+ const rootRef = useRef(null)
471
+ const onLayout = useCallback(() => {
472
+ rootRef.current?.measureInWindow((x, y, width, height) => {
473
+ navigation.layout = { x, y, width, height }
474
+ })
475
+ }, [])
476
+
454
477
  navigation.insets = useSafeAreaInsets()
455
478
 
456
479
  return createElement(GestureHandlerRootView,
457
480
  {
481
+ style: {
482
+ flex: 1
483
+ }
484
+ },
485
+ createElement(ReactNative.View, {
458
486
  style: {
459
487
  flex: 1,
460
488
  backgroundColor: pageConfig.backgroundColor || '#ffffff'
461
489
  },
462
- ref: rootRef
490
+ ref: rootRef,
491
+ onLayout
463
492
  },
464
- // todo custom portal host for active route
465
- createElement(Provider,
466
- null,
467
- createElement(RouteContext.Provider,
468
- {
469
- value: currentPageId
470
- },
471
- createElement(defaultOptions,
493
+ createElement(Provider,
494
+ null,
495
+ createElement(RouteContext.Provider,
472
496
  {
473
- navigation,
474
- route,
475
- id: currentPageId
476
- }
497
+ value: currentPageId
498
+ },
499
+ createElement(IntersectionObserverContext.Provider,
500
+ {
501
+ value: intersectionObservers.current
502
+ },
503
+ createElement(defaultOptions,
504
+ {
505
+ navigation,
506
+ route,
507
+ id: currentPageId
508
+ }
509
+ )
510
+ )
477
511
  )
478
512
  )
479
513
  )
514
+ // todo custom portal host for active route
480
515
  )
481
516
  }
482
517
  return Page
483
518
  }
484
-
485
519
  return defaultOptions
486
520
  }
@@ -1,6 +1,6 @@
1
1
  import builtInKeysMap from '../builtInKeysMap'
2
2
  import mergeOptions from '../../../core/mergeOptions'
3
- import { diffAndCloneA, hasOwn } from '@mpxjs/utils'
3
+ import { diffAndCloneA, hasOwn, wrapMethodsWithErrorHandling } from '@mpxjs/utils'
4
4
  import { getCurrentInstance as getCurrentVueInstance } from '../../export/index'
5
5
  import MpxProxy, { setCurrentInstance, unsetCurrentInstance } from '../../../core/proxy'
6
6
  import {
@@ -27,6 +27,8 @@ function filterOptions (options) {
27
27
  )
28
28
  }
29
29
  }
30
+ } else if (key === 'methods') {
31
+ newOptions[key] = wrapMethodsWithErrorHandling(options[key])
30
32
  } else {
31
33
  newOptions[key] = options[key]
32
34
  }
@@ -62,7 +64,7 @@ export function getDefaultOptions ({ type, rawOptions = {} }) {
62
64
  }
63
65
  const setupRes = rawSetup(props, newContext)
64
66
  unsetCurrentInstance(instance.__mpxProxy)
65
- return setupRes
67
+ return wrapMethodsWithErrorHandling(setupRes, instance.__mpxProxy)
66
68
  }
67
69
  }
68
70
  const rootMixins = [{
@@ -1,4 +1,4 @@
1
- import { hasOwn, noop, isFunction } from '@mpxjs/utils'
1
+ import { hasOwn, noop, isFunction, wrapMethodsWithErrorHandling } from '@mpxjs/utils'
2
2
  import MpxProxy from '../../../core/proxy'
3
3
  import builtInKeysMap from '../builtInKeysMap'
4
4
  import mergeOptions from '../../../core/mergeOptions'
@@ -23,11 +23,13 @@ function transformProperties (properties) {
23
23
  } else {
24
24
  newFiled = Object.assign({}, rawFiled)
25
25
  }
26
- newFiled.observer = function (value) {
26
+ const rawObserver = rawFiled?.observer
27
+ newFiled.observer = function (value, oldValue) {
27
28
  if (this.__mpxProxy) {
28
29
  this[key] = value
29
30
  this.__mpxProxy.propsUpdated()
30
31
  }
32
+ rawObserver && rawObserver.call(this, value, oldValue)
31
33
  }
32
34
  newProps[key] = newFiled
33
35
  })
@@ -138,9 +140,14 @@ export function filterOptions (options) {
138
140
  if (!hasOwn(newOptions, 'properties')) {
139
141
  newOptions.properties = transformProperties(Object.assign({}, options.props, options.properties))
140
142
  }
141
- } else if (key === 'methods' && options.__pageCtor__) {
142
- // 构造器为Page时抽取所有methods方法到顶层
143
- Object.assign(newOptions, options[key])
143
+ } else if (key === 'methods') {
144
+ const newMethods = wrapMethodsWithErrorHandling(options[key])
145
+ if (options.__pageCtor__) {
146
+ // 构造器为Page时抽取所有methods方法到顶层
147
+ Object.assign(newOptions, newMethods)
148
+ } else {
149
+ newOptions[key] = newMethods
150
+ }
144
151
  } else {
145
152
  newOptions[key] = options[key]
146
153
  }
@@ -6,6 +6,9 @@ const factoryMap = {
6
6
 
7
7
  module.exports = (type) => (...args) => {
8
8
  if (type === 'Behavior') {
9
+ if (__mpx_mode__ === 'ali') {
10
+ return Mixin.apply(null, args)
11
+ }
9
12
  if (args[0]) {
10
13
  Object.defineProperty(args[0], '__mpx_behaviors_to_mixins__', {
11
14
  configurable: true,
@@ -1,2 +0,0 @@
1
- import directiveHelperMixin from './directiveHelperMixin.ios'
2
- export default directiveHelperMixin
@@ -1,2 +0,0 @@
1
- import proxyEventMixin from './proxyEventMixin.ios'
2
- export default proxyEventMixin
@@ -1,2 +0,0 @@
1
- import refsMixin from './refsMixin.ios'
2
- export default refsMixin
@@ -1,2 +0,0 @@
1
- import styleHelperMixin from './styleHelperMixin.ios'
2
- export default styleHelperMixin
@@ -1,2 +0,0 @@
1
- import createApp from './createApp.ios'
2
- export default createApp
@@ -1 +0,0 @@
1
- export { getDefaultOptions } from './getDefaultOptions.ios'