@mpxjs/core 2.9.57 → 2.9.59

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpxjs/core",
3
- "version": "2.9.57",
3
+ "version": "2.9.59",
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.50",
22
+ "@mpxjs/utils": "^2.9.59",
23
23
  "lodash": "^4.1.1",
24
24
  "miniprogram-api-typings": "^3.10.0"
25
25
  },
@@ -31,6 +31,7 @@
31
31
  "@react-navigation/native-stack": "^6.9.26",
32
32
  "react": "*",
33
33
  "react-native": "*",
34
+ "react-native-safe-area-context": "^4.10.1",
34
35
  "vue": "^2.7.10",
35
36
  "vue-demi": "^0.14.6",
36
37
  "vue-i18n": "^8.27.2",
@@ -63,6 +64,9 @@
63
64
  },
64
65
  "@ant-design/react-native": {
65
66
  "optional": true
67
+ },
68
+ "react-native-safe-area-context": {
69
+ "optional": true
66
70
  }
67
71
  },
68
72
  "publishConfig": {
@@ -81,5 +85,5 @@
81
85
  "url": "https://github.com/didi/mpx/issues"
82
86
  },
83
87
  "sideEffects": false,
84
- "gitHead": "f52627676fd798818980c2d1a6890c5ea99e2862"
88
+ "gitHead": "aa001c11cc7b21772fc6f9f5bcdd13118fc6d67c"
85
89
  }
@@ -1,7 +1,6 @@
1
1
  import * as wxLifecycle from '../platform/patch/wx/lifecycle'
2
2
  import * as aliLifecycle from '../platform/patch/ali/lifecycle'
3
3
  import { mergeLifecycle } from './mergeLifecycle'
4
- import { mergeToArray } from '../core/mergeOptions'
5
4
  import { error, hasOwn, isDev } from '@mpxjs/utils'
6
5
  import { implemented } from '../core/implement'
7
6
 
@@ -60,14 +59,6 @@ export default {
60
59
  options.props = props
61
60
  delete options.properties
62
61
  }
63
- if (options.onResize) {
64
- mergeToArray(options, {
65
- events: {
66
- onResize: options.onResize
67
- }
68
- }, 'events')
69
- delete options.onResize
70
- }
71
62
  notSupportTip(options)
72
63
  }
73
64
  }
@@ -3,7 +3,8 @@ import {
3
3
  CREATED,
4
4
  ONHIDE,
5
5
  ONSHOW,
6
- ONLOAD
6
+ ONLOAD,
7
+ ONRESIZE
7
8
  } from '../../core/innerLifecycle'
8
9
  import { isFunction, isBrowser } from '@mpxjs/utils'
9
10
 
@@ -35,11 +36,11 @@ function onResize () {
35
36
  }
36
37
  }
37
38
 
38
- const _t = getCurrentPageInstance()
39
+ const pageInstance = getCurrentPageInstance()
39
40
 
40
- if (_t) {
41
- _t.mpxPageStatus = `resize${count++}`
42
- isFunction(_t.onResize) && _t.onResize(systemInfo)
41
+ if (pageInstance) {
42
+ pageInstance.mpxPageStatus = `resize${count++}`
43
+ pageInstance.__mpxProxy.callHook(ONRESIZE, [systemInfo])
43
44
  }
44
45
  }
45
46
 
@@ -1,303 +1,59 @@
1
- import { BEFORECREATE } from '../../core/innerLifecycle'
2
- import { noop, isBoolean, dash2hump, warn, collectDataset, hump2dash } from '@mpxjs/utils'
3
- import { StyleSheet } from 'react-native'
4
-
5
- const _createSelectorQuery = (runCb) => {
6
- return {
7
- exec: (cb = noop) => {
8
- runCb().then(res => {
9
- cb(res)
10
- })
11
- },
12
- in: () => {
13
- warn('please use wx:ref to get NodesRef')
14
- },
15
- select: () => {
16
- warn('please use wx:ref to get NodesRef')
17
- },
18
- selectAll: () => {
19
- warn('please use wx:ref to get NodesRef')
20
- },
21
- selectViewport: () => { // 有点难实现,dimension 目前没有暴露相关 api
22
- warn('please use wx:ref')
23
- }
24
- }
25
- }
26
-
27
- const flushRefFns = (nodeInstances, fns) => {
28
- const mountedNodeInstance = nodeInstances
29
- .map(instance => instance.getNodeInstance())
30
- .filter(({ nodeRef }) => nodeRef.current) // 如果有 nodeRef,表明目前组件处于挂载中
31
- if (mountedNodeInstance.length) {
32
- return Promise.all(mountedNodeInstance.map(instance => flushFns(instance, fns)))
33
- } else {
34
- return Promise.resolve(null)
35
- }
36
- }
37
-
38
- const flushFns = (nodeInstance, fns) => {
39
- return Promise.all(fns.map(fn => fn(nodeInstance))).then((res) => {
40
- return res.reduce((preVal, curVal) => {
41
- return Object.assign(preVal, curVal)
42
- }, {})
43
- })
44
- }
45
-
46
- const wrapFn = (fn) => {
47
- return (nodeRef) => {
48
- return new Promise((resolve) => {
49
- fn(nodeRef, resolve)
50
- })
51
- }
52
- }
53
-
54
- const getMeasureProps = (measureProps = []) => {
55
- return wrapFn((nodeInstance, resolve) => {
56
- const nodeRef = nodeInstance.nodeRef.current
57
- setTimeout(() => {
58
- nodeRef.measure(function (x, y, width, height, pageX, pageY) {
59
- const rectAndSize = {
60
- width,
61
- height,
62
- left: pageX,
63
- top: pageY,
64
- right: pageX + width,
65
- bottom: pageY + height
66
- }
67
- const result = measureProps.reduce((preVal, key) => {
68
- return Object.assign(preVal, { [key]: rectAndSize[key] || 0 })
69
- }, {})
70
- resolve(result)
71
- })
72
- }, 30) // 延迟,等待组件在rn视图上真正渲染出来
73
- })
74
- }
75
-
76
- const getDataset = (props) => {
77
- return wrapFn((nodeRef, resolve) => {
78
- props = nodeRef.props.current
79
- resolve({
80
- dataset: collectDataset(props)
81
- })
82
- })
83
- }
84
-
85
- const getPlainProps = (config) => {
86
- return wrapFn((nodeRef, resolve) => {
87
- const res = {}
88
- const props = nodeRef.props.current
89
- config.forEach((key) => {
90
- // props 属性默认不转驼峰,用户写什么格式不会变化,取值做兼容
91
- res[key] = props[key] || props[hump2dash(key)] || ''
92
- })
93
- resolve(res)
94
- })
95
- }
96
-
97
- const getComputedStyle = (config = []) => {
98
- return wrapFn((nodeRef, resolve) => {
99
- config = new Set(config)
100
- const res = {}
101
- const styles = nodeRef.props.current.style || []
102
- const defaultStyle = nodeRef.instance.defaultStyle || {}
103
- const computedStyle = StyleSheet.flatten([defaultStyle, ...styles])
104
- config.forEach((key) => {
105
- const humpKey = dash2hump(key)
106
- // 取 style 的 key 是根据传入的 key 来设置,传什么设置什么 key,只不过取值需要做兼容
107
- res[key] = computedStyle[key] || computedStyle[humpKey] || ''
108
- })
109
-
110
- resolve(res)
111
- })
112
- }
113
-
114
- const getInstanceConfig = (config) => {
115
- return wrapFn((nodeRef, resolve) => {
116
- const instance = nodeRef.instance
117
- resolve({ [config]: instance[config] || {} })
118
- })
119
- }
120
-
121
- const defaultScrollOffset = {
122
- scrollLeft: 0,
123
- scrollTop: 0,
124
- scrollHeight: 0,
125
- scrollWidth: 0
126
- }
127
-
128
- const getScrollOffset = () => {
129
- return wrapFn((nodeRef, resolve) => {
130
- const instance = nodeRef.instance
131
- resolve((instance.scrollOffset && instance.scrollOffset.current) || defaultScrollOffset)
132
- })
133
- }
134
-
135
- // const getScrollOffsetFallback = (cb) => {
136
- // const res = {
137
- // scrollLeft: 0,
138
- // scrollTop: 0,
139
- // scrollHeight: 0,
140
- // scrollWidth: 0
141
- // }
142
- // cb(res)
143
- // }
144
-
145
- const RECT = ['left', 'top', 'right', 'bottom']
146
- const SIZE = ['width', 'height']
147
-
148
- function _createNodesRef (nodeRefs = []) {
149
- const fields = (config, cb = noop) => {
150
- const plainProps = []
151
- const measureProps = []
152
- const computedStyle = []
153
- const fns = []
154
-
155
- for (const key in config) {
156
- const value = config[key]
157
- if (Array.isArray(value) && value.length) {
158
- if (key === 'properties') {
159
- // wx 最终输出的 properties 字段都会转化为驼峰,所以在这里提前处理为最终的字段格式
160
- plainProps.push(...value.map(v => dash2hump(v)))
161
- } else if (key === 'computedStyle') {
162
- const _computedStyle = config.computedStyle
163
- for (let i = _computedStyle.length - 1; i >= 0; i--) {
164
- const style = _computedStyle[i]
165
- if (RECT.includes(style) || SIZE.includes(style)) {
166
- measureProps.push(style)
167
- _computedStyle.splice(i, 1)
168
- }
169
- }
170
- if (_computedStyle.length) {
171
- computedStyle.push(..._computedStyle)
172
- }
173
- }
174
- } else if (isBoolean(value) && value) {
175
- switch (key) {
176
- case 'rect':
177
- measureProps.push(...RECT)
178
- break
179
- case 'size':
180
- measureProps.push(...SIZE)
181
- break
182
- case 'scrollOffset':
183
- fns.push(getScrollOffset())
184
- break
185
- case 'dataset':
186
- fns.push(getDataset())
187
- break
188
- case 'node':
189
- case 'context':
190
- case 'ref':
191
- fns.push(getInstanceConfig(key))
192
- break
193
- default:
194
- plainProps.push(key)
195
- break
196
- }
197
- }
198
- }
199
-
200
- if (plainProps.length) {
201
- fns.push(getPlainProps(plainProps))
202
- }
203
- if (measureProps.length) {
204
- const nodeInstance = nodeRefs[0] && nodeRefs[0].getNodeInstance()
205
- const hasMeasureFn = nodeInstance && nodeInstance.nodeRef.current && nodeInstance.nodeRef.current.measure
206
- if (hasMeasureFn) {
207
- fns.push(getMeasureProps(measureProps))
208
- } else {
209
- computedStyle.push(...measureProps)
210
- }
211
- }
212
- if (computedStyle.length) {
213
- fns.push(getComputedStyle(computedStyle))
214
- }
215
-
216
- const runCb = () => {
217
- return flushRefFns(nodeRefs, fns).then((result) => {
218
- // wx的数据格式:对于具体方法接受到的回调传参,如果获取的 nodeRef 只有一个,那么只需要返回一条数据而不是数组,但是 exec 里面统一都是数组
219
- cb(result && result.length === 1 ? result[0] : result)
220
- return result
221
- })
222
- }
223
-
224
- return _createSelectorQuery(runCb)
225
- }
226
-
227
- const boundingClientRect = (cb = noop) => {
228
- const config = {
229
- id: true,
230
- dataset: true,
231
- rect: true,
232
- size: true
233
- }
234
- return fields(config, cb)
235
- }
236
-
237
- const context = (cb = noop) => {
238
- const config = {
239
- context: true
240
- }
241
- return fields(config, cb)
242
- }
243
-
244
- const node = (cb = noop) => {
245
- const config = {
246
- node: true
247
- }
248
- return fields(config, cb)
249
- }
250
-
251
- const ref = (cb = noop) => {
252
- const config = {
253
- ref: true
254
- }
255
- return fields(config, cb)
256
- }
257
-
258
- const scrollOffset = (cb = noop) => {
259
- const config = {
260
- id: true,
261
- dataset: true,
262
- scrollOffset: true
263
- }
264
- return fields(config, cb)
265
- }
266
-
267
- return {
268
- fields,
269
- boundingClientRect,
270
- context,
271
- node,
272
- ref,
273
- scrollOffset
274
- }
275
- }
1
+ import { BEFORECREATE, CREATED } from '../../core/innerLifecycle'
2
+ import { createSelectorQuery } from '@mpxjs/api-proxy'
3
+ import { computed } from '../../observer/computed'
276
4
 
277
5
  export default function getRefsMixin () {
278
6
  return {
279
7
  [BEFORECREATE] () {
280
8
  this.__refs = {}
281
9
  this.$refs = {}
10
+ },
11
+ // __getRefs强依赖数据响应,需要在CREATED中执行
12
+ [CREATED] () {
282
13
  this.__getRefs()
283
14
  },
284
15
  methods: {
285
16
  __getRefs () {
286
17
  const refs = this.__getRefsData() || []
287
18
  const target = this
288
- refs.forEach(({ key, type, all }) => {
289
- Object.defineProperty(this.$refs, key, {
290
- enumerable: true,
291
- configurable: true,
292
- get () {
293
- const refs = target.__refs[key] || []
294
- if (type === 'component') {
295
- return all ? refs : refs[0]
296
- } else {
297
- return _createNodesRef(refs)
298
- }
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 })
299
37
  }
300
38
  })
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
+ }
301
57
  })
302
58
  },
303
59
  __getRefVal (key) {
@@ -305,6 +61,28 @@ export default function getRefsMixin () {
305
61
  this.__refs[key] = []
306
62
  }
307
63
  return (instance) => instance && this.__refs[key].push(instance)
64
+ },
65
+ __selectRef (selector, refType, all = false) {
66
+ const splitedSelector = selector.match(/(#|\.)?\w+/g) || []
67
+ const refsArr = splitedSelector.map(selector => {
68
+ const selectorMap = this.__selectorMap?.value[selector] || []
69
+ const res = []
70
+ selectorMap.forEach(({ type, key }) => {
71
+ if (type === refType) {
72
+ const _refs = this.__refs[key] || []
73
+ res.push(..._refs)
74
+ }
75
+ })
76
+ return res
77
+ })
78
+
79
+ const refs = refsArr.reduce((preRefs, curRefs, curIndex) => {
80
+ if (curIndex === 0) return curRefs
81
+ curRefs = new Set(curRefs)
82
+ return preRefs.filter(p => curRefs.has(p))
83
+ }, [])
84
+
85
+ return all ? refs : refs[0]
308
86
  }
309
87
  }
310
88
  }
@@ -1,4 +1,4 @@
1
- import { isObject, isArray, dash2hump, isFunction, isEmptyObject } from '@mpxjs/utils'
1
+ import { isObject, isArray, dash2hump, isFunction } from '@mpxjs/utils'
2
2
  import { Dimensions } from 'react-native'
3
3
 
4
4
  function concat (a, b) {
@@ -41,8 +41,8 @@ function stringifyDynamicClass (value) {
41
41
 
42
42
  const listDelimiter = /;(?![^(]*[)])/g
43
43
  const propertyDelimiter = /:(.+)/
44
- const rpxRegExp = /^\s*(\d+(\.\d+)?)rpx\s*$/
45
- const pxRegExp = /^\s*(\d+(\.\d+)?)(px)?\s*$/
44
+ const rpxRegExp = /^\s*(-?\d+(\.\d+)?)rpx\s*$/
45
+ const pxRegExp = /^\s*(-?\d+(\.\d+)?)(px)?\s*$/
46
46
 
47
47
  function parseStyleText (cssText) {
48
48
  const res = {}
@@ -106,7 +106,11 @@ export default function styleHelperMixin (type) {
106
106
  // px = rpx * (750 / 屏幕宽度)
107
107
  return value * width / 750
108
108
  },
109
+ __getClass (staticClass, dynamicClass) {
110
+ return concat(staticClass, stringifyDynamicClass(dynamicClass))
111
+ },
109
112
  __getStyle (staticClass, dynamicClass, staticStyle, dynamicStyle, show) {
113
+ // todo 每次返回新对象会导致react memo优化失效,需要考虑优化手段
110
114
  const result = []
111
115
  const classMap = {}
112
116
  if (type === 'page' && isFunction(global.__getAppClassMap)) {
@@ -115,11 +119,14 @@ export default function styleHelperMixin (type) {
115
119
  if (isFunction(this.__getClassMap)) {
116
120
  Object.assign(classMap, this.__getClassMap())
117
121
  }
118
- if ((staticClass || dynamicClass) && !isEmptyObject(classMap)) {
122
+ if (staticClass || dynamicClass) {
119
123
  const classString = concat(staticClass, stringifyDynamicClass(dynamicClass))
120
- classString.split(' ').forEach((className) => {
124
+ classString.split(/\s+/).forEach((className) => {
121
125
  if (classMap[className]) {
122
126
  result.push(classMap[className])
127
+ } else if (this.props[className]) {
128
+ // externalClasses必定以数组形式传递下来
129
+ result.push(...this.props[className])
123
130
  }
124
131
  })
125
132
  }
@@ -5,9 +5,15 @@ import { mergeLifecycle } from '../convertor/mergeLifecycle'
5
5
  import * as wxLifecycle from '../platform/patch/wx/lifecycle'
6
6
  import Mpx from '../index'
7
7
  import { createElement, memo, useRef, useEffect } from 'react'
8
+ import * as ReactNative from 'react-native'
9
+ import { ref } from '../observer/ref'
8
10
 
9
11
  const appHooksMap = makeMap(mergeLifecycle(wxLifecycle.LIFECYCLE).app)
10
12
 
13
+ function getOrientation (window = ReactNative.Dimensions.get('window')) {
14
+ return window.width > window.height ? 'landscape' : 'portrait'
15
+ }
16
+
11
17
  function filterOptions (options, appData) {
12
18
  const newOptions = {}
13
19
  Object.keys(options).forEach(key => {
@@ -34,11 +40,15 @@ function createAppInstance (appData) {
34
40
  export default function createApp (option, config = {}) {
35
41
  const appData = {}
36
42
 
37
- const { NavigationContainer, createNavigationContainerRef, createNativeStackNavigator } = global.__navigationHelper
43
+ const { NavigationContainer, createNavigationContainerRef, createNativeStackNavigator, SafeAreaProvider } = global.__navigationHelper
38
44
  // app选项目前不需要进行转换
39
45
  const { rawOptions, currentInject } = transferOptions(option, 'app', false)
40
46
  const defaultOptions = filterOptions(spreadProp(rawOptions, 'methods'), appData)
41
47
  defaultOptions.onAppInit && defaultOptions.onAppInit()
48
+ // 在页面script执行前填充getApp()
49
+ global.getApp = function () {
50
+ return appData
51
+ }
42
52
  const pages = currentInject.getPages() || {}
43
53
  const firstPage = currentInject.firstPage
44
54
  const Stack = createNativeStackNavigator()
@@ -64,6 +74,14 @@ export default function createApp (option, config = {}) {
64
74
  global.__navigationHelper.lastFailCallback = null
65
75
  }
66
76
  }
77
+
78
+ global.__mpxAppCbs = global.__mpxAppCbs || {
79
+ show: [],
80
+ hide: [],
81
+ error: []
82
+ }
83
+
84
+ global.__mpxAppFocusedState = ref('show')
67
85
  global.__mpxOptionsMap[currentInject.moduleId] = memo(() => {
68
86
  const instanceRef = useRef(null)
69
87
  if (!instanceRef.current) {
@@ -81,24 +99,63 @@ export default function createApp (option, config = {}) {
81
99
  }
82
100
  global.__mpxEnterOptions = options
83
101
  defaultOptions.onLaunch && defaultOptions.onLaunch.call(instance, options)
102
+ if (defaultOptions.onShow) {
103
+ defaultOptions.onShow.call(instance, options)
104
+ global.__mpxAppCbs.show.push(defaultOptions.onShow.bind(instance))
105
+ }
106
+ if (defaultOptions.onHide) {
107
+ global.__mpxAppCbs.hide.push(defaultOptions.onHide.bind(instance))
108
+ }
109
+ if (defaultOptions.onError) {
110
+ global.__mpxAppCbs.error.push(defaultOptions.onError.bind(instance))
111
+ }
112
+
113
+ const changeSubscription = ReactNative.AppState.addEventListener('change', (currentState) => {
114
+ if (currentState === 'active') {
115
+ global.__mpxAppCbs.show.forEach((cb) => {
116
+ cb(options)
117
+ })
118
+ global.__mpxAppFocusedState.value = 'show'
119
+ } else if (currentState === 'inactive') {
120
+ global.__mpxAppCbs.hide.forEach((cb) => {
121
+ cb()
122
+ })
123
+ global.__mpxAppFocusedState.value = 'hide'
124
+ }
125
+ })
126
+
127
+ let count = 0
128
+ let lastOrientation = getOrientation()
129
+ const resizeSubScription = ReactNative.Dimensions.addEventListener('change', ({ window }) => {
130
+ const orientation = getOrientation(window)
131
+ if (orientation === lastOrientation) return
132
+ lastOrientation = orientation
133
+ global.__mpxAppFocusedState.value = `resize${count++}`
134
+ })
135
+ return () => {
136
+ changeSubscription()
137
+ resizeSubScription && resizeSubScription.remove()
138
+ }
84
139
  }, [])
85
- return createElement(NavigationContainer,
86
- {
87
- ref: navigationRef,
88
- onStateChange,
89
- onUnhandledAction
90
- },
91
- createElement(Stack.Navigator,
140
+
141
+ return createElement(SafeAreaProvider,
142
+ null,
143
+ createElement(NavigationContainer,
92
144
  {
93
- initialRouteName: firstPage
145
+ ref: navigationRef,
146
+ onStateChange,
147
+ onUnhandledAction
94
148
  },
95
- ...pageScreens
149
+ createElement(Stack.Navigator,
150
+ {
151
+ initialRouteName: firstPage
152
+ },
153
+ ...pageScreens
154
+ )
96
155
  )
97
156
  )
98
157
  })
99
- global.getApp = function () {
100
- return appData
101
- }
158
+
102
159
  global.getCurrentPages = function () {
103
160
  const navigation = Object.values(global.__mpxPagesMap || {})[0]?.[1]
104
161
  if (navigation) {
@@ -1,4 +1,5 @@
1
- import { set, del, reactive } from '../../observer/reactive'
1
+ import { set, del, reactive, isReactive } from '../../observer/reactive'
2
+ import { isRef } from '../../observer/ref'
2
3
  import { watch } from '../../observer/watch'
3
4
  import { injectMixins } from '../../core/injectMixins'
4
5
 
@@ -8,7 +9,9 @@ const APIs = {
8
9
  observable: reactive,
9
10
  watch,
10
11
  set,
11
- delete: del
12
+ delete: del,
13
+ isReactive,
14
+ isRef
12
15
  }
13
16
 
14
17
  const InstanceAPIs = {
@@ -1,19 +1,22 @@
1
- import Vue from 'vue'
1
+ import {
2
+ watch,
3
+ reactive,
4
+ isReactive,
5
+ set,
6
+ del,
7
+ isRef
8
+ } from 'vue'
2
9
  import { injectMixins } from '../../core/injectMixins'
3
10
 
4
- const vm = new Vue()
5
- const observable = Vue.observable.bind(Vue)
6
- const watch = vm.$watch.bind(vm)
7
- const set = Vue.set.bind(Vue)
8
- const del = Vue.delete.bind(Vue)
9
-
10
11
  const APIs = {
11
12
  injectMixins,
12
13
  mixin: injectMixins,
13
- observable,
14
+ observable: reactive,
14
15
  watch,
15
16
  set,
16
- delete: del
17
+ delete: del,
18
+ isReactive,
19
+ isRef
17
20
  }
18
21
 
19
22
  const InstanceAPIs = {}
@@ -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 { isFunction, error, diffAndCloneA, hasOwn, noop } from '@mpxjs/utils'
4
+ import { error, diffAndCloneA, hasOwn, noop } from '@mpxjs/utils'
5
5
 
6
6
  function transformApiForProxy (context, currentInject) {
7
7
  const rawSetData = context.setData.bind(context)
@@ -23,7 +23,7 @@ function transformApiForProxy (context, currentInject) {
23
23
  const validProps = Object.assign({}, options.properties, options.props)
24
24
  if (context.props) {
25
25
  Object.keys(context.props).forEach((key) => {
26
- if (hasOwn(validProps, key) && !isFunction(context.props[key])) {
26
+ if (hasOwn(validProps, key)) {
27
27
  props[key] = context.props[key]
28
28
  }
29
29
  })
@@ -129,7 +129,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
129
129
  if (rawOptions.__nativeRender__ && this.props) {
130
130
  const validProps = Object.assign({}, rawOptions.props, rawOptions.properties)
131
131
  Object.keys(this.props).forEach((key) => {
132
- if (hasOwn(validProps, key) && !isFunction(this.props[key])) {
132
+ if (hasOwn(validProps, key)) {
133
133
  this.data[key] = this.props[key]
134
134
  }
135
135
  })
@@ -143,7 +143,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
143
143
  const newData = {}
144
144
  // 微信原生转换支付宝时,每次props更新将其设置进data模拟微信表现
145
145
  Object.keys(nextProps).forEach((key) => {
146
- if (hasOwn(validProps, key) && !isFunction(nextProps[key])) {
146
+ if (hasOwn(validProps, key)) {
147
147
  const { diff, clone } = diffAndCloneA(nextProps[key], this.props[key])
148
148
  if (diff) newData[key] = clone
149
149
  }
@@ -153,7 +153,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
153
153
  // 由于支付宝中props透传父级setData的值,此处发生变化的属性实例一定不同,只需浅比较即可确定发生变化的属性
154
154
  // 支付宝appx2.0版本后props传递发生变化,此处获取到的nextProps和this.props以及父组件setData的数据引用都不一致,进行了两次深克隆,此处this.props和nextProps的比对需要用deep diff
155
155
  Object.keys(nextProps).forEach(key => {
156
- if (hasOwn(validProps, key) && !isFunction(nextProps[key])) {
156
+ if (hasOwn(validProps, key)) {
157
157
  const { diff, clone } = diffAndCloneA(nextProps[key], this.props[key])
158
158
  // 由于支付宝中透传父级setData的值,此处进行深clone后赋值避免父级存储的miniRenderData部分数据在此处被响应化,在子组件对props赋值时触发父组件的render
159
159
  if (diff) this[key] = clone
@@ -1,11 +1,28 @@
1
- import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, createElement, memo, forwardRef, useImperativeHandle } from 'react'
1
+ import { useEffect, useLayoutEffect, useSyncExternalStore, useRef, useMemo, createElement, memo, forwardRef, useImperativeHandle, useContext, createContext, Fragment } from 'react'
2
2
  import * as ReactNative from 'react-native'
3
3
  import { ReactiveEffect } from '../../../observer/effect'
4
+ import { watch } from '../../../observer/watch'
5
+ import { reactive, set, del } from '../../../observer/reactive'
4
6
  import { hasOwn, isFunction, noop, isObject, error, getByPath, collectDataset } from '@mpxjs/utils'
5
7
  import MpxProxy from '../../../core/proxy'
6
- import { BEFOREUPDATE, UPDATED, ONLOAD } from '../../../core/innerLifecycle'
8
+ import { BEFOREUPDATE, ONLOAD, UPDATED, ONSHOW, ONHIDE, ONRESIZE } from '../../../core/innerLifecycle'
7
9
  import mergeOptions from '../../../core/mergeOptions'
8
10
  import { queueJob } from '../../../observer/scheduler'
11
+ import { createSelectorQuery } from '@mpxjs/api-proxy'
12
+
13
+ function getSystemInfo () {
14
+ const window = ReactNative.Dimensions.get('window')
15
+ const screen = ReactNative.Dimensions.get('screen')
16
+ return {
17
+ deviceOrientation: window.width > window.height ? 'landscape' : 'portrait',
18
+ size: {
19
+ screenWidth: screen.width,
20
+ screenHeight: screen.height,
21
+ windowWidth: window.width,
22
+ windowHeight: window.height
23
+ }
24
+ }
25
+ }
9
26
 
10
27
  function getRootProps (props) {
11
28
  const rootProps = {}
@@ -36,6 +53,7 @@ function createEffect (proxy, components, props) {
36
53
  }
37
54
  update.id = proxy.uid
38
55
  const getComponent = (tagName) => {
56
+ if (tagName === 'block') return Fragment
39
57
  return components[tagName] || getByPath(ReactNative, tagName)
40
58
  }
41
59
  proxy.effect = new ReactiveEffect(() => {
@@ -51,13 +69,20 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
51
69
  __getProps () {
52
70
  const propsData = {}
53
71
  const props = propsRef.current
54
- if (props) {
55
- Object.keys(props).forEach((key) => {
56
- if (hasOwn(validProps, key) && !isFunction(props[key])) {
57
- propsData[key] = props[key]
72
+ Object.keys(validProps).forEach((key) => {
73
+ if (hasOwn(props, key)) {
74
+ propsData[key] = props[key]
75
+ } else {
76
+ let field = validProps[key]
77
+ if (isFunction(field) || field === null) {
78
+ field = {
79
+ type: field
80
+ }
58
81
  }
59
- })
60
- }
82
+ // 处理props默认值
83
+ propsData[key] = field.value
84
+ }
85
+ })
61
86
  return propsData
62
87
  },
63
88
  __getSlot (name) {
@@ -111,8 +136,7 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
111
136
  },
112
137
  triggerEvent (eventName, eventDetail) {
113
138
  const props = propsRef.current
114
- const handlerName = eventName.replace(/^./, matched => matched.toUpperCase()).replace(/-([a-z])/g, (match, p1) => p1.toUpperCase())
115
- const handler = props && (props['bind' + handlerName] || props['catch' + handlerName] || props['capture-bind' + handlerName] || props['capture-catch' + handlerName])
139
+ const handler = props && (props['bind' + eventName] || props['catch' + eventName] || props['capture-bind' + eventName] || props['capture-catch' + eventName])
116
140
  if (handler && typeof handler === 'function') {
117
141
  const timeStamp = +new Date()
118
142
  const dataset = collectDataset(props)
@@ -134,14 +158,14 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
134
158
  handler.call(this, eventObj)
135
159
  }
136
160
  },
137
- selectComponent () {
138
- error('selectComponent is not supported in react native, please use ref instead')
161
+ selectComponent (selector) {
162
+ return this.__selectRef(selector, 'component')
139
163
  },
140
- selectAllComponents () {
141
- error('selectAllComponents is not supported in react native, please use ref instead')
164
+ selectAllComponents (selector) {
165
+ return this.__selectRef(selector, 'component', true)
142
166
  },
143
167
  createSelectorQuery () {
144
- error('createSelectorQuery is not supported in react native, please use ref instead')
168
+ return createSelectorQuery().in(this)
145
169
  },
146
170
  createIntersectionObserver () {
147
171
  error('createIntersectionObserver is not supported in react native, please use ref instead')
@@ -161,6 +185,12 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
161
185
  return props.id
162
186
  },
163
187
  enumerable: true
188
+ },
189
+ props: {
190
+ get () {
191
+ return propsRef.current
192
+ },
193
+ enumerable: true
164
194
  }
165
195
  })
166
196
 
@@ -175,7 +205,7 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
175
205
  proxy.created()
176
206
 
177
207
  if (type === 'page') {
178
- proxy.callHook(ONLOAD, [props.route.params])
208
+ proxy.callHook(ONLOAD, [props.route.params || {}])
179
209
  }
180
210
 
181
211
  Object.assign(proxy, {
@@ -207,6 +237,106 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
207
237
  return instance
208
238
  }
209
239
 
240
+ function hasPageHook (mpxProxy, hookNames) {
241
+ const options = mpxProxy.options
242
+ const type = options.__type__
243
+ return hookNames.some(h => {
244
+ if (mpxProxy.hasHook(h)) {
245
+ return true
246
+ }
247
+ if (type === 'page') {
248
+ return isFunction(options.methods && options.methods[h])
249
+ } else if (type === 'component') {
250
+ return options.pageLifetimes && isFunction(options.pageLifetimes[h])
251
+ }
252
+ return false
253
+ })
254
+ }
255
+
256
+ const routeContext = createContext(null)
257
+
258
+ const triggerPageStatusHook = (mpxProxy, event) => {
259
+ mpxProxy.callHook(event === 'show' ? ONSHOW : ONHIDE)
260
+ const pageLifetimes = mpxProxy.options.pageLifetimes
261
+ if (pageLifetimes) {
262
+ const instance = mpxProxy.target
263
+ isFunction(pageLifetimes[event]) && pageLifetimes[event].call(instance)
264
+ }
265
+ }
266
+
267
+ const triggerResizeEvent = (mpxProxy) => {
268
+ const type = mpxProxy.options.__type__
269
+ const systemInfo = getSystemInfo()
270
+ const target = mpxProxy.target
271
+ mpxProxy.callHook(ONRESIZE, [systemInfo])
272
+ if (type === 'page') {
273
+ target.onResize && target.onResize(systemInfo)
274
+ } else {
275
+ const pageLifetimes = mpxProxy.options.pageLifetimes
276
+ pageLifetimes && isFunction(pageLifetimes.resize) && pageLifetimes.resize.call(target, systemInfo)
277
+ }
278
+ }
279
+
280
+ function usePageContext (mpxProxy, instance) {
281
+ const { pageId } = useContext(routeContext) || {}
282
+
283
+ instance.getPageId = () => {
284
+ return pageId
285
+ }
286
+
287
+ useEffect(() => {
288
+ let unWatch
289
+ const hasShowHook = hasPageHook(mpxProxy, [ONSHOW, 'show'])
290
+ const hasHideHook = hasPageHook(mpxProxy, [ONHIDE, 'hide'])
291
+ const hasResizeHook = hasPageHook(mpxProxy, [ONRESIZE, 'resize'])
292
+ if (hasShowHook || hasHideHook || hasResizeHook) {
293
+ if (hasOwn(pageStatusContext, pageId)) {
294
+ unWatch = watch(() => pageStatusContext[pageId], (newVal) => {
295
+ if (newVal === 'show' || newVal === 'hide') {
296
+ triggerPageStatusHook(mpxProxy, newVal)
297
+ } else if (/^resize/.test(newVal)) {
298
+ triggerResizeEvent(mpxProxy)
299
+ }
300
+ })
301
+ }
302
+ }
303
+
304
+ return () => {
305
+ unWatch && unWatch()
306
+ }
307
+ }, [])
308
+ }
309
+
310
+ const pageStatusContext = reactive({})
311
+ let pageId = 0
312
+
313
+ function usePageStatus (navigation, pageId) {
314
+ let isFocused = true
315
+ set(pageStatusContext, pageId, '')
316
+ useEffect(() => {
317
+ const focusSubscription = navigation.addListener('focus', () => {
318
+ pageStatusContext[pageId] = 'show'
319
+ isFocused = true
320
+ })
321
+ const blurSubscription = navigation.addListener('blur', () => {
322
+ pageStatusContext[pageId] = 'hide'
323
+ isFocused = false
324
+ })
325
+ const unWatchAppFocusedState = watch(global.__mpxAppFocusedState, (value) => {
326
+ if (isFocused) {
327
+ pageStatusContext[pageId] = value
328
+ }
329
+ })
330
+
331
+ return () => {
332
+ focusSubscription()
333
+ blurSubscription()
334
+ unWatchAppFocusedState()
335
+ del(pageStatusContext, pageId)
336
+ }
337
+ }, [navigation])
338
+ }
339
+
210
340
  export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
211
341
  rawOptions = mergeOptions(rawOptions, type, false)
212
342
  const components = Object.assign({}, rawOptions.components, currentInject.getComponents())
@@ -232,13 +362,15 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
232
362
  // 处理props更新
233
363
  propsRef.current = props
234
364
  Object.keys(props).forEach(key => {
235
- if (hasOwn(validProps, key) && !isFunction(props[key])) {
365
+ if (hasOwn(validProps, key)) {
236
366
  instance[key] = props[key]
237
367
  }
238
368
  })
239
369
  proxy.propsUpdated()
240
370
  }
241
371
 
372
+ usePageContext(proxy, instance)
373
+
242
374
  useEffect(() => {
243
375
  if (proxy.pendingUpdatedFlag) {
244
376
  proxy.pendingUpdatedFlag = false
@@ -262,11 +394,15 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
262
394
  }))
263
395
 
264
396
  if (type === 'page') {
265
- const { Provider } = global.__navigationHelper
397
+ const { Provider, useSafeAreaInsets } = global.__navigationHelper
266
398
  const pageConfig = Object.assign({}, global.__mpxPageConfig, currentInject.pageConfig)
267
399
  const Page = ({ navigation, route }) => {
400
+ const currentPageId = useMemo(() => ++pageId, [])
401
+ usePageStatus(navigation, currentPageId)
402
+
268
403
  useLayoutEffect(() => {
269
404
  navigation.setOptions({
405
+ headerShown: pageConfig.navigationStyle !== 'custom',
270
406
  headerTitle: pageConfig.navigationBarTitleText || '',
271
407
  headerStyle: {
272
408
  backgroundColor: pageConfig.navigationBarBackgroundColor || '#000000'
@@ -275,16 +411,34 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) {
275
411
  })
276
412
  }, [])
277
413
 
414
+ const insets = useSafeAreaInsets()
415
+ const safeAreaPadding = {
416
+ paddingTop: insets.top,
417
+ paddingLeft: insets.left
418
+ }
419
+
278
420
  return createElement(Provider,
279
421
  null,
280
422
  createElement(ReactNative.View,
281
423
  {
282
424
  style: {
425
+ ...pageConfig.navigationStyle === 'custom' && safeAreaPadding,
283
426
  ...ReactNative.StyleSheet.absoluteFillObject,
284
427
  backgroundColor: pageConfig.backgroundColor || '#ffffff'
285
428
  }
286
429
  },
287
- createElement(defaultOptions, { navigation, route, pageConfig })
430
+ createElement(routeContext.Provider,
431
+ {
432
+ value: { pageId: currentPageId }
433
+ },
434
+ createElement(defaultOptions,
435
+ {
436
+ navigation,
437
+ route,
438
+ pageConfig
439
+ }
440
+ )
441
+ )
288
442
  )
289
443
  )
290
444
  }
@@ -4,7 +4,8 @@ import {
4
4
  MOUNTED,
5
5
  ONSHOW,
6
6
  ONHIDE,
7
- ONLOAD
7
+ ONLOAD,
8
+ ONRESIZE
8
9
  } from '../../../core/innerLifecycle'
9
10
 
10
11
  const APP_HOOKS = [
@@ -47,7 +48,8 @@ export const lifecycleProxyMap = {
47
48
  [UNMOUNTED]: ['detached', 'onUnload'],
48
49
  [ONSHOW]: ['pageShow', 'onShow'],
49
50
  [ONHIDE]: ['pageHide', 'onHide'],
50
- [ONLOAD]: ['onLoad']
51
+ [ONLOAD]: ['onLoad'],
52
+ [ONRESIZE]: ['onResize']
51
53
  }
52
54
 
53
55
  export const LIFECYCLE = {
@@ -1,4 +1,4 @@
1
- import { hasOwn, noop } from '@mpxjs/utils'
1
+ import { hasOwn, noop, isFunction } from '@mpxjs/utils'
2
2
  import MpxProxy from '../../../core/proxy'
3
3
  import builtInKeysMap from '../builtInKeysMap'
4
4
  import mergeOptions from '../../../core/mergeOptions'
@@ -16,7 +16,7 @@ function transformProperties (properties) {
16
16
  type: null
17
17
  }
18
18
  }
19
- if (typeof rawFiled === 'function') {
19
+ if (isFunction(rawFiled)) {
20
20
  newFiled = {
21
21
  type: rawFiled
22
22
  }
@@ -4,7 +4,8 @@ import {
4
4
  MOUNTED,
5
5
  ONSHOW,
6
6
  ONHIDE,
7
- ONLOAD
7
+ ONLOAD,
8
+ ONRESIZE
8
9
  } from '../../../core/innerLifecycle'
9
10
 
10
11
  const APP_HOOKS = [
@@ -52,7 +53,8 @@ export const lifecycleProxyMap = {
52
53
  [UNMOUNTED]: ['detached', 'onUnload'],
53
54
  [ONSHOW]: ['pageShow', 'onShow'],
54
55
  [ONHIDE]: ['pageHide', 'onHide'],
55
- [ONLOAD]: ['onLoad']
56
+ [ONLOAD]: ['onLoad'],
57
+ [ONRESIZE]: ['onResize']
56
58
  }
57
59
 
58
60
  export const LIFECYCLE = {