@mpxjs/core 2.7.52 → 2.8.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/@types/index.d.ts +342 -27
- package/package.json +10 -5
- package/src/convertor/convertor.js +2 -2
- package/src/convertor/mergeLifecycle.js +4 -4
- package/src/convertor/wxToAli.js +3 -4
- package/src/convertor/wxToSwan.js +2 -2
- package/src/convertor/wxToTt.js +1 -10
- package/src/convertor/wxToWeb.js +14 -7
- package/src/core/implement.js +2 -2
- package/src/core/injectMixins.js +1 -1
- package/src/core/innerLifecycle.js +15 -2
- package/src/core/mergeOptions.js +11 -5
- package/src/core/proxy.js +343 -229
- package/src/core/transferOptions.js +5 -2
- package/src/helper/const.js +10 -0
- package/src/index.js +73 -147
- package/src/observer/array.js +12 -17
- package/src/observer/computed.js +27 -56
- package/src/observer/dep.js +1 -1
- package/src/observer/effect.js +113 -0
- package/src/observer/effectScope.js +109 -0
- package/src/observer/{index.js → reactive.js} +74 -70
- package/src/observer/ref.js +97 -0
- package/src/observer/scheduler.js +171 -56
- package/src/observer/watch.js +163 -39
- package/src/platform/builtInMixins/i18nMixin.js +238 -31
- package/src/platform/builtInMixins/pageScrollMixin.web.js +4 -5
- package/src/platform/builtInMixins/pageStatusMixin.js +76 -54
- package/src/platform/builtInMixins/pageStatusMixin.web.js +35 -22
- package/src/platform/builtInMixins/proxyEventMixin.js +40 -22
- package/src/platform/builtInMixins/proxyEventMixin.web.js +16 -24
- package/src/platform/builtInMixins/refsMixin.js +82 -73
- package/src/platform/builtInMixins/refsMixin.web.js +0 -47
- package/src/platform/builtInMixins/relationsMixin.js +10 -9
- package/src/platform/builtInMixins/renderHelperMixin.js +1 -1
- package/src/platform/builtInMixins/showMixin.js +1 -1
- package/src/platform/createApp.js +5 -5
- package/src/platform/export/api.js +23 -0
- package/src/platform/export/api.web.js +26 -0
- package/src/platform/export/index.js +45 -0
- package/src/platform/export/index.web.js +36 -0
- package/src/platform/index.js +1 -5
- package/src/platform/patch/ali/getDefaultOptions.js +33 -31
- package/src/platform/patch/ali/lifecycle.js +21 -13
- package/src/platform/patch/builtInKeysMap.js +2 -1
- package/src/platform/patch/index.js +4 -9
- package/src/platform/patch/swan/getDefaultOptions.js +3 -3
- package/src/platform/patch/swan/lifecycle.js +17 -14
- package/src/platform/patch/web/getDefaultOptions.js +40 -16
- package/src/platform/patch/web/lifecycle.js +6 -3
- package/src/platform/patch/wx/getDefaultOptions.js +38 -31
- package/src/platform/patch/wx/lifecycle.js +18 -11
- package/src/runtime/createFactory.js +6 -2
- package/src/vue.web.js +3 -0
- package/src/vuePlugin.js +31 -0
- package/src/core/createStore.js +0 -241
- package/src/core/mapStore.js +0 -94
- package/src/helper/env.js +0 -20
- package/src/helper/getByPath.js +0 -127
- package/src/helper/log.js +0 -31
- package/src/helper/utils.js +0 -652
- package/src/observer/watcher.js +0 -244
package/src/core/proxy.js
CHANGED
|
@@ -1,111 +1,157 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { reactive } from '../observer/reactive'
|
|
2
|
+
import { ReactiveEffect } from '../observer/effect'
|
|
3
|
+
import { effectScope } from '../platform/export/index'
|
|
3
4
|
import { watch } from '../observer/watch'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { computed } from '../observer/computed'
|
|
6
|
+
import { queueJob, nextTick } from '../observer/scheduler'
|
|
7
|
+
import Mpx from '../index'
|
|
7
8
|
import {
|
|
8
9
|
noop,
|
|
9
|
-
|
|
10
|
+
type,
|
|
11
|
+
isFunction,
|
|
12
|
+
isObject,
|
|
10
13
|
isEmptyObject,
|
|
11
14
|
isPlainObject,
|
|
12
|
-
|
|
13
|
-
setByPath,
|
|
15
|
+
doGetByPath,
|
|
14
16
|
getByPath,
|
|
15
|
-
|
|
17
|
+
setByPath,
|
|
16
18
|
diffAndCloneA,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
hasOwn,
|
|
20
|
+
proxy,
|
|
21
|
+
makeMap,
|
|
22
|
+
isString,
|
|
19
23
|
aIsSubPathOfB,
|
|
24
|
+
mergeData,
|
|
25
|
+
processUndefined,
|
|
20
26
|
getFirstKey,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
import { getRenderCallBack } from '../platform/patch'
|
|
27
|
+
callWithErrorHandling,
|
|
28
|
+
warn,
|
|
29
|
+
error
|
|
30
|
+
} from '@mpxjs/utils'
|
|
26
31
|
import {
|
|
27
32
|
BEFORECREATE,
|
|
28
33
|
CREATED,
|
|
29
34
|
BEFOREMOUNT,
|
|
30
35
|
MOUNTED,
|
|
36
|
+
BEFOREUPDATE,
|
|
31
37
|
UPDATED,
|
|
32
|
-
|
|
38
|
+
BEFOREUNMOUNT,
|
|
39
|
+
UNMOUNTED,
|
|
40
|
+
ONLOAD,
|
|
41
|
+
ONSHOW,
|
|
42
|
+
ONHIDE,
|
|
43
|
+
ONRESIZE
|
|
33
44
|
} from './innerLifecycle'
|
|
34
|
-
import { warn, error } from '../helper/log'
|
|
35
45
|
|
|
36
46
|
let uid = 0
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
class RenderTask {
|
|
49
|
+
resolved = false
|
|
50
|
+
|
|
51
|
+
constructor (instance) {
|
|
52
|
+
instance.currentRenderTask = this
|
|
53
|
+
this.promise = new Promise((resolve) => {
|
|
54
|
+
this.resolve = resolve
|
|
55
|
+
}).then(() => {
|
|
56
|
+
this.resolved = true
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* process renderData, remove sub node if visit parent node already
|
|
63
|
+
* @param {Object} renderData
|
|
64
|
+
* @return {Object} processedRenderData
|
|
65
|
+
*/
|
|
66
|
+
function preProcessRenderData (renderData) {
|
|
67
|
+
// method for get key path array
|
|
68
|
+
const processKeyPathMap = (keyPathMap) => {
|
|
69
|
+
const keyPath = Object.keys(keyPathMap)
|
|
70
|
+
return keyPath.filter((keyA) => {
|
|
71
|
+
return keyPath.every((keyB) => {
|
|
72
|
+
if (keyA.startsWith(keyB) && keyA !== keyB) {
|
|
73
|
+
const nextChar = keyA[keyB.length]
|
|
74
|
+
if (nextChar === '.' || nextChar === '[') {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return true
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const processedRenderData = {}
|
|
84
|
+
const renderDataFinalKey = processKeyPathMap(renderData)
|
|
85
|
+
Object.keys(renderData).forEach(item => {
|
|
86
|
+
if (renderDataFinalKey.indexOf(item) > -1) {
|
|
87
|
+
processedRenderData[item] = renderData[item]
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
return processedRenderData
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default class MpxProxy {
|
|
94
|
+
constructor (options, target, reCreated) {
|
|
40
95
|
this.target = target
|
|
96
|
+
this.reCreated = reCreated
|
|
41
97
|
this.uid = uid++
|
|
42
98
|
this.name = options.name || ''
|
|
43
99
|
this.options = options
|
|
44
|
-
// beforeCreate -> created -> mounted ->
|
|
100
|
+
// beforeCreate -> created -> mounted -> unmounted
|
|
45
101
|
this.state = BEFORECREATE
|
|
46
|
-
this.
|
|
47
|
-
|
|
102
|
+
this.ignoreProxyMap = makeMap(Mpx.config.ignoreProxyWhiteList)
|
|
103
|
+
// 收集setup中动态注册的hooks,小程序与web环境都需要
|
|
104
|
+
this.hooks = {}
|
|
48
105
|
if (__mpx_mode__ !== 'web') {
|
|
49
|
-
this.
|
|
50
|
-
|
|
51
|
-
this.
|
|
52
|
-
|
|
53
|
-
this.
|
|
54
|
-
|
|
106
|
+
this.scope = effectScope(true)
|
|
107
|
+
// props响应式数据代理
|
|
108
|
+
this.props = {}
|
|
109
|
+
// data响应式数据代理
|
|
110
|
+
this.data = {}
|
|
111
|
+
// 非props key
|
|
112
|
+
this.localKeysMap = {}
|
|
113
|
+
// 渲染函数中收集的数据
|
|
114
|
+
this.renderData = {}
|
|
115
|
+
// 最小渲染数据
|
|
55
116
|
this.miniRenderData = {}
|
|
56
|
-
|
|
57
|
-
this.
|
|
58
|
-
|
|
117
|
+
// 强制更新的数据
|
|
118
|
+
this.forceUpdateData = {}
|
|
119
|
+
// 下次是否需要强制更新全部渲染数据
|
|
120
|
+
this.forceUpdateAll = false
|
|
121
|
+
this.currentRenderTask = null
|
|
59
122
|
}
|
|
123
|
+
this.initApi()
|
|
124
|
+
this.callHook(BEFORECREATE)
|
|
60
125
|
}
|
|
61
126
|
|
|
62
|
-
created (
|
|
63
|
-
this.initApi()
|
|
64
|
-
this.callUserHook(BEFORECREATE)
|
|
127
|
+
created () {
|
|
65
128
|
if (__mpx_mode__ !== 'web') {
|
|
66
|
-
this
|
|
129
|
+
setCurrentInstance(this)
|
|
130
|
+
this.initProps()
|
|
131
|
+
this.initSetup()
|
|
132
|
+
this.initData()
|
|
133
|
+
this.initComputed()
|
|
134
|
+
this.initWatch()
|
|
135
|
+
unsetCurrentInstance()
|
|
67
136
|
}
|
|
137
|
+
|
|
68
138
|
this.state = CREATED
|
|
69
|
-
this.
|
|
139
|
+
this.callHook(CREATED)
|
|
140
|
+
|
|
70
141
|
if (__mpx_mode__ !== 'web') {
|
|
71
142
|
this.initRender()
|
|
72
143
|
}
|
|
73
|
-
}
|
|
74
144
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.state = BEFORECREATE
|
|
78
|
-
this.callUserHook(BEFORECREATE)
|
|
79
|
-
if (__mpx_mode__ !== 'web') {
|
|
80
|
-
this.initComputed(options.computed, true)
|
|
81
|
-
this.initWatch(options.watch)
|
|
82
|
-
}
|
|
83
|
-
this.state = CREATED
|
|
84
|
-
this.callUserHook(CREATED, params)
|
|
85
|
-
if (__mpx_mode__ !== 'web') {
|
|
86
|
-
this.initRender()
|
|
145
|
+
if (this.reCreated) {
|
|
146
|
+
nextTick(this.mounted.bind(this))
|
|
87
147
|
}
|
|
88
|
-
this.nextTick(() => {
|
|
89
|
-
this.mounted()
|
|
90
|
-
})
|
|
91
148
|
}
|
|
92
149
|
|
|
93
|
-
|
|
94
|
-
if ((!this.isMounted() && this.
|
|
150
|
+
createRenderTask (isEmptyRender) {
|
|
151
|
+
if ((!this.isMounted() && this.currentRenderTask) || (this.isMounted() && isEmptyRender)) {
|
|
95
152
|
return
|
|
96
153
|
}
|
|
97
|
-
|
|
98
|
-
state: 'pending'
|
|
99
|
-
}
|
|
100
|
-
const promise = new Promise(resolve => {
|
|
101
|
-
this.curRenderTask.resolve = (res) => {
|
|
102
|
-
this.curRenderTask.state = 'finished'
|
|
103
|
-
resolve(res)
|
|
104
|
-
}
|
|
105
|
-
})
|
|
106
|
-
this.curRenderTask.promise = promise
|
|
107
|
-
// isMounted之前基于mounted触发,isMounted之后基于setData回调触发
|
|
108
|
-
return this.isMounted() && this.curRenderTask.resolve
|
|
154
|
+
return new RenderTask(this)
|
|
109
155
|
}
|
|
110
156
|
|
|
111
157
|
isMounted () {
|
|
@@ -116,131 +162,132 @@ export default class MPXProxy {
|
|
|
116
162
|
if (this.state === CREATED) {
|
|
117
163
|
this.state = MOUNTED
|
|
118
164
|
// 用于处理refs等前置工作
|
|
119
|
-
this.
|
|
120
|
-
this.
|
|
121
|
-
this.
|
|
165
|
+
this.callHook(BEFOREMOUNT)
|
|
166
|
+
this.callHook(MOUNTED)
|
|
167
|
+
this.currentRenderTask && this.currentRenderTask.resolve()
|
|
122
168
|
}
|
|
123
169
|
}
|
|
124
170
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
171
|
+
propsUpdated () {
|
|
172
|
+
const updateJob = this.updateJob || (this.updateJob = () => {
|
|
173
|
+
// 只有当前没有渲染任务时,属性更新才需要单独触发updated,否则可以由渲染任务触发updated
|
|
174
|
+
if (this.currentRenderTask?.resolved && this.isMounted()) {
|
|
175
|
+
this.callHook(BEFOREUPDATE)
|
|
176
|
+
this.callHook(UPDATED)
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
nextTick(updateJob)
|
|
129
180
|
}
|
|
130
181
|
|
|
131
|
-
|
|
132
|
-
this.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
this.callUserHook(DESTROYED)
|
|
182
|
+
unmounted () {
|
|
183
|
+
this.callHook(BEFOREUNMOUNT)
|
|
184
|
+
this.scope?.stop()
|
|
185
|
+
this.callHook(UNMOUNTED)
|
|
186
|
+
this.state = UNMOUNTED
|
|
137
187
|
}
|
|
138
188
|
|
|
139
|
-
|
|
140
|
-
return this.state ===
|
|
189
|
+
isUnmounted () {
|
|
190
|
+
return this.state === UNMOUNTED
|
|
141
191
|
}
|
|
142
192
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
proxy(this.target, this.options.proto, Object.keys(this.options.proto), true, (key) => {
|
|
193
|
+
createProxyConflictHandler (owner) {
|
|
194
|
+
return (key) => {
|
|
146
195
|
if (this.ignoreProxyMap[key]) {
|
|
147
|
-
error(`The key [${key}]
|
|
196
|
+
!this.reCreated && error(`The ${owner} key [${key}] is a reserved keyword of miniprogram, please check and rename it.`, this.options.mpxFileResource)
|
|
148
197
|
return false
|
|
149
198
|
}
|
|
150
|
-
error(`The key [${key}]
|
|
151
|
-
}
|
|
199
|
+
!this.reCreated && error(`The ${owner} key [${key}] exist in the current instance already, please check and rename it.`, this.options.mpxFileResource)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
initApi () {
|
|
204
|
+
// 挂载扩展属性到实例上
|
|
205
|
+
proxy(this.target, Mpx.prototype, undefined, true, this.createProxyConflictHandler('mpx.prototype'))
|
|
152
206
|
// 挂载混合模式下createPage中的自定义属性,模拟原生Page构造器的表现
|
|
153
207
|
if (this.options.__type__ === 'page' && !this.options.__pageCtor__) {
|
|
154
|
-
proxy(this.target, this.options, this.options.mpxCustomKeysForBlend,
|
|
155
|
-
if (this.ignoreProxyMap[key]) {
|
|
156
|
-
error(`The key [${key}] of page options is a reserved keyword of miniprogram, please check and rename it!`, this.options.mpxFileResource)
|
|
157
|
-
return false
|
|
158
|
-
}
|
|
159
|
-
error(`The key [${key}] of page options exist in the page instance already, please check your page options!`, this.options.mpxFileResource)
|
|
160
|
-
})
|
|
208
|
+
proxy(this.target, this.options, this.options.mpxCustomKeysForBlend, false, this.createProxyConflictHandler('page options'))
|
|
161
209
|
}
|
|
162
210
|
if (__mpx_mode__ !== 'web') {
|
|
163
211
|
// 挂载$watch
|
|
164
|
-
this.target.$watch =
|
|
212
|
+
this.target.$watch = this.watch.bind(this)
|
|
165
213
|
// 强制执行render
|
|
166
|
-
this.target.$forceUpdate =
|
|
167
|
-
this.target.$nextTick =
|
|
168
|
-
|
|
169
|
-
this.target.$
|
|
170
|
-
if (!this._namedWatchers) return null
|
|
171
|
-
return this._namedWatchers[name] || null
|
|
172
|
-
}
|
|
173
|
-
this.target.$getRenderWatcher = () => this._watcher
|
|
214
|
+
this.target.$forceUpdate = this.forceUpdate.bind(this)
|
|
215
|
+
this.target.$nextTick = nextTick
|
|
216
|
+
// 挂载$refs对象
|
|
217
|
+
this.target.$refs = {}
|
|
174
218
|
}
|
|
175
219
|
}
|
|
176
220
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.initComputed(options.computed)
|
|
182
|
-
// target的数据访问代理到将proxy的data
|
|
183
|
-
proxy(this.target, this.data, undefined, undefined, (key) => {
|
|
184
|
-
if (this.ignoreProxyMap[key]) {
|
|
185
|
-
error(`The data/props/computed key [${key}] is a reserved keyword of miniprogram, please check and rename it!`, this.options.mpxFileResource)
|
|
186
|
-
return false
|
|
187
|
-
}
|
|
188
|
-
if (!proxyedKeysMap[key]) error(`The data/props/computed key [${key}] exist in the component/page instance already, please check and rename it!`, this.options.mpxFileResource)
|
|
189
|
-
})
|
|
190
|
-
this.initWatch(options.watch)
|
|
221
|
+
initProps () {
|
|
222
|
+
this.props = diffAndCloneA(this.target.__getProps(this.options)).clone
|
|
223
|
+
reactive(this.props)
|
|
224
|
+
proxy(this.target, this.props, undefined, false, this.createProxyConflictHandler('props'))
|
|
191
225
|
}
|
|
192
226
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
227
|
+
initSetup () {
|
|
228
|
+
const setup = this.options.setup
|
|
229
|
+
if (setup) {
|
|
230
|
+
const setupResult = callWithErrorHandling(setup, this, 'setup function', [
|
|
231
|
+
this.props,
|
|
232
|
+
{
|
|
233
|
+
triggerEvent: this.target.triggerEvent.bind(this.target),
|
|
234
|
+
refs: this.target.$refs,
|
|
235
|
+
forceUpdate: this.forceUpdate.bind(this),
|
|
236
|
+
selectComponent: this.target.selectComponent.bind(this.target),
|
|
237
|
+
selectAllComponents: this.target.selectAllComponents.bind(this.target),
|
|
238
|
+
createSelectorQuery: this.target.createSelectorQuery.bind(this.target),
|
|
239
|
+
createIntersectionObserver: this.target.createIntersectionObserver.bind(this.target)
|
|
240
|
+
}
|
|
241
|
+
])
|
|
242
|
+
if (!isObject(setupResult)) {
|
|
243
|
+
error(`Setup() should return a object, received: ${type(setupResult)}.`, this.options.mpxFileResource)
|
|
244
|
+
return
|
|
201
245
|
}
|
|
246
|
+
proxy(this.target, setupResult, undefined, false, this.createProxyConflictHandler('setup result'))
|
|
247
|
+
this.collectLocalKeys(setupResult, (key, val) => !isFunction(val))
|
|
202
248
|
}
|
|
203
249
|
}
|
|
204
250
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// 获取包含data/props在内的初始数据,包含初始原生微信转换支付宝时合并props进入data的逻辑
|
|
209
|
-
const initialData = this.target.__getInitialData(this.options) || {}
|
|
251
|
+
initData () {
|
|
252
|
+
const data = this.options.data
|
|
253
|
+
const dataFn = this.options.dataFn
|
|
210
254
|
// 之所以没有直接使用initialData,而是通过对原始dataOpt进行深clone获取初始数据对象,主要是为了避免小程序自身序列化时错误地转换数据对象,比如将promise转为普通object
|
|
211
255
|
this.data = diffAndCloneA(data || {}).clone
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
proxy(this.target, initialData, proxyedKeys, undefined, (key) => {
|
|
216
|
-
if (this.ignoreProxyMap[key]) {
|
|
217
|
-
error(`The props/data key [${key}] is a reserved keyword of miniprogram, please check and rename it!`, this.options.mpxFileResource)
|
|
218
|
-
return false
|
|
219
|
-
}
|
|
220
|
-
error(`The props/data key [${key}] exist in the component instance already, please check and rename it!`, this.options.mpxFileResource)
|
|
221
|
-
})
|
|
222
|
-
Object.assign(this.data, dataFn.call(this.target))
|
|
256
|
+
// 执行dataFn
|
|
257
|
+
if (isFunction(dataFn)) {
|
|
258
|
+
Object.assign(this.data, callWithErrorHandling(dataFn.bind(this.target), this, 'data function'))
|
|
223
259
|
}
|
|
224
|
-
|
|
260
|
+
reactive(this.data)
|
|
261
|
+
proxy(this.target, this.data, undefined, false, this.createProxyConflictHandler('data'))
|
|
225
262
|
this.collectLocalKeys(this.data)
|
|
226
|
-
// 将props数据合并到data中
|
|
227
|
-
Object.keys(initialData).forEach((key) => {
|
|
228
|
-
if (!hasOwn(this.data, key)) {
|
|
229
|
-
// 除了data函数返回的数据外深拷贝切断引用关系,避免后续watch由于小程序内部对data赋值重复触发watch
|
|
230
|
-
this.data[key] = diffAndCloneA(initialData[key]).clone
|
|
231
|
-
}
|
|
232
|
-
})
|
|
233
|
-
// mpxCid 解决支付宝环境selector为全局问题
|
|
234
|
-
this.data.mpxCid = this.uid
|
|
235
|
-
this.localKeysMap.mpxCid = true
|
|
236
|
-
observe(this.data, true)
|
|
237
|
-
return proxyedKeys
|
|
238
263
|
}
|
|
239
264
|
|
|
240
|
-
|
|
265
|
+
initComputed () {
|
|
266
|
+
const computedOpt = this.options.computed
|
|
267
|
+
if (computedOpt) {
|
|
268
|
+
const computedObj = {}
|
|
269
|
+
Object.entries(computedOpt).forEach(([key, opt]) => {
|
|
270
|
+
const get = isFunction(opt)
|
|
271
|
+
? opt.bind(this.target)
|
|
272
|
+
: isFunction(opt.get)
|
|
273
|
+
? opt.get.bind(this.target)
|
|
274
|
+
: noop
|
|
275
|
+
|
|
276
|
+
const set = !isFunction(opt) && isFunction(opt.set)
|
|
277
|
+
? opt.set.bind(this.target)
|
|
278
|
+
: () => warn(`Write operation failed: computed property "${key}" is readonly.`, this.options.mpxFileResource)
|
|
279
|
+
|
|
280
|
+
computedObj[key] = computed({ get, set })
|
|
281
|
+
})
|
|
282
|
+
this.collectLocalKeys(computedObj)
|
|
283
|
+
proxy(this.target, computedObj, undefined, false, this.createProxyConflictHandler('computed'))
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
initWatch () {
|
|
288
|
+
const watch = this.options.watch
|
|
241
289
|
if (watch) {
|
|
242
|
-
|
|
243
|
-
const handler = watch[key]
|
|
290
|
+
Object.entries(watch).forEach(([key, handler]) => {
|
|
244
291
|
if (Array.isArray(handler)) {
|
|
245
292
|
for (let i = 0; i < handler.length; i++) {
|
|
246
293
|
this.watch(key, handler[i])
|
|
@@ -248,55 +295,66 @@ export default class MPXProxy {
|
|
|
248
295
|
} else {
|
|
249
296
|
this.watch(key, handler)
|
|
250
297
|
}
|
|
251
|
-
}
|
|
298
|
+
})
|
|
252
299
|
}
|
|
253
300
|
}
|
|
254
301
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
}
|
|
302
|
+
watch (source, cb, options) {
|
|
303
|
+
const target = this.target
|
|
304
|
+
const getter = isString(source)
|
|
305
|
+
? () => getByPath(target, source)
|
|
306
|
+
: source.bind(target)
|
|
262
307
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
this.curRenderTask ? this.curRenderTask.promise.then(fn) : fn()
|
|
267
|
-
})
|
|
308
|
+
if (isObject(cb)) {
|
|
309
|
+
options = cb
|
|
310
|
+
cb = cb.handler
|
|
268
311
|
}
|
|
269
|
-
}
|
|
270
312
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (typeof hook === 'function') {
|
|
274
|
-
try {
|
|
275
|
-
hook.apply(this.target, params)
|
|
276
|
-
} catch (e) {
|
|
277
|
-
if (typeof EXPORT_MPX.config.hookErrorHandler === 'function') {
|
|
278
|
-
EXPORT_MPX.config.hookErrorHandler(e, this.target, hookName)
|
|
279
|
-
} else {
|
|
280
|
-
throw e
|
|
281
|
-
}
|
|
282
|
-
}
|
|
313
|
+
if (isString(cb) && target[cb]) {
|
|
314
|
+
cb = target[cb]
|
|
283
315
|
}
|
|
316
|
+
|
|
317
|
+
cb = cb || noop
|
|
318
|
+
|
|
319
|
+
const cur = currentInstance
|
|
320
|
+
setCurrentInstance(this)
|
|
321
|
+
|
|
322
|
+
const res = watch(getter, cb.bind(target), options)
|
|
323
|
+
|
|
324
|
+
if (cur) setCurrentInstance(cur)
|
|
325
|
+
else unsetCurrentInstance()
|
|
326
|
+
|
|
327
|
+
return res
|
|
284
328
|
}
|
|
285
329
|
|
|
286
|
-
|
|
287
|
-
|
|
330
|
+
collectLocalKeys (data, filter = () => true) {
|
|
331
|
+
Object.keys(data).filter((key) => filter(key, data[key])).forEach((key) => {
|
|
332
|
+
this.localKeysMap[key] = true
|
|
333
|
+
})
|
|
288
334
|
}
|
|
289
335
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
336
|
+
callHook (hookName, params, hooksOnly) {
|
|
337
|
+
const hook = this.options[hookName]
|
|
338
|
+
const hooks = this.hooks[hookName] || []
|
|
339
|
+
let result
|
|
340
|
+
if (isFunction(hook) && !hooksOnly) {
|
|
341
|
+
result = callWithErrorHandling(hook.bind(this.target), this, `${hookName} hook`, params)
|
|
294
342
|
}
|
|
295
|
-
|
|
343
|
+
hooks.forEach((hook) => {
|
|
344
|
+
result = params ? hook(...params) : hook()
|
|
345
|
+
})
|
|
346
|
+
return result
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
hasHook (hookName) {
|
|
350
|
+
return !!(this.options[hookName] || this.hooks[hookName])
|
|
296
351
|
}
|
|
297
352
|
|
|
298
353
|
render () {
|
|
299
|
-
const renderData =
|
|
354
|
+
const renderData = {}
|
|
355
|
+
Object.keys(this.localKeysMap).forEach((key) => {
|
|
356
|
+
renderData[key] = this.target[key]
|
|
357
|
+
})
|
|
300
358
|
this.doRender(this.processRenderDataWithStrictDiff(renderData))
|
|
301
359
|
}
|
|
302
360
|
|
|
@@ -315,7 +373,7 @@ export default class MPXProxy {
|
|
|
315
373
|
|
|
316
374
|
processRenderDataWithStrictDiff (renderData) {
|
|
317
375
|
const result = {}
|
|
318
|
-
for (
|
|
376
|
+
for (const key in renderData) {
|
|
319
377
|
if (hasOwn(renderData, key)) {
|
|
320
378
|
const data = renderData[key]
|
|
321
379
|
const firstKey = getFirstKey(key)
|
|
@@ -329,7 +387,7 @@ export default class MPXProxy {
|
|
|
329
387
|
clone = localClone
|
|
330
388
|
if (diff) {
|
|
331
389
|
this.miniRenderData[key] = clone
|
|
332
|
-
if (diffData &&
|
|
390
|
+
if (diffData && Mpx.config.useStrictDiff) {
|
|
333
391
|
this.processRenderDataWithDiffData(result, key, diffData)
|
|
334
392
|
} else {
|
|
335
393
|
result[key] = clone
|
|
@@ -350,12 +408,12 @@ export default class MPXProxy {
|
|
|
350
408
|
const subPath = aIsSubPathOfB(key, tarKey)
|
|
351
409
|
if (subPath) {
|
|
352
410
|
// setByPath 更新miniRenderData中的子数据
|
|
353
|
-
|
|
411
|
+
doGetByPath(this.miniRenderData[tarKey], subPath, (current, subKey, meta) => {
|
|
354
412
|
if (meta.isEnd) {
|
|
355
413
|
const { clone, diff, diffData } = diffAndCloneA(data, current[subKey])
|
|
356
414
|
if (diff) {
|
|
357
415
|
current[subKey] = clone
|
|
358
|
-
if (diffData &&
|
|
416
|
+
if (diffData && Mpx.config.useStrictDiff) {
|
|
359
417
|
this.processRenderDataWithDiffData(result, key, diffData)
|
|
360
418
|
} else {
|
|
361
419
|
result[key] = clone
|
|
@@ -377,7 +435,7 @@ export default class MPXProxy {
|
|
|
377
435
|
const { clone, diff, diffData } = diffAndCloneA(data, localInitialData)
|
|
378
436
|
this.miniRenderData[key] = clone
|
|
379
437
|
if (diff) {
|
|
380
|
-
if (diffData &&
|
|
438
|
+
if (diffData && Mpx.config.useStrictDiff) {
|
|
381
439
|
this.processRenderDataWithDiffData(result, key, diffData)
|
|
382
440
|
} else {
|
|
383
441
|
result[key] = clone
|
|
@@ -408,7 +466,7 @@ export default class MPXProxy {
|
|
|
408
466
|
}
|
|
409
467
|
|
|
410
468
|
const isEmpty = isEmptyObject(data) && isEmptyObject(this.forceUpdateData)
|
|
411
|
-
const
|
|
469
|
+
const renderTask = this.createRenderTask(isEmpty)
|
|
412
470
|
|
|
413
471
|
if (isEmpty) {
|
|
414
472
|
cb && cb()
|
|
@@ -427,16 +485,17 @@ export default class MPXProxy {
|
|
|
427
485
|
*/
|
|
428
486
|
let callback = cb
|
|
429
487
|
if (this.isMounted()) {
|
|
488
|
+
this.callHook(BEFOREUPDATE)
|
|
430
489
|
callback = () => {
|
|
431
|
-
getRenderCallBack(this)()
|
|
432
490
|
cb && cb()
|
|
433
|
-
|
|
491
|
+
this.callHook(UPDATED)
|
|
492
|
+
renderTask && renderTask.resolve()
|
|
434
493
|
}
|
|
435
494
|
}
|
|
436
495
|
data = processUndefined(data)
|
|
437
|
-
if (typeof
|
|
496
|
+
if (typeof Mpx.config.setDataHandler === 'function') {
|
|
438
497
|
try {
|
|
439
|
-
|
|
498
|
+
Mpx.config.setDataHandler(data, this.target)
|
|
440
499
|
} catch (e) {
|
|
441
500
|
}
|
|
442
501
|
}
|
|
@@ -446,64 +505,119 @@ export default class MPXProxy {
|
|
|
446
505
|
initRender () {
|
|
447
506
|
if (this.options.__nativeRender__) return this.doRender()
|
|
448
507
|
|
|
449
|
-
|
|
450
|
-
|
|
508
|
+
this.effect = new ReactiveEffect(() => {
|
|
509
|
+
if (this.target.__injectedRender) {
|
|
451
510
|
try {
|
|
452
511
|
return this.target.__injectedRender()
|
|
453
512
|
} catch (e) {
|
|
454
|
-
warn(
|
|
513
|
+
warn('Failed to execute render function, degrade to full-set-data mode.', this.options.mpxFileResource, e)
|
|
455
514
|
this.render()
|
|
456
515
|
}
|
|
457
|
-
}
|
|
458
|
-
} else {
|
|
459
|
-
this._watcher = new Watcher(this, () => {
|
|
516
|
+
} else {
|
|
460
517
|
this.render()
|
|
461
|
-
}
|
|
462
|
-
}
|
|
518
|
+
}
|
|
519
|
+
}, () => queueJob(update), this.scope)
|
|
520
|
+
|
|
521
|
+
const update = this.effect.run.bind(this.effect)
|
|
522
|
+
update.id = this.uid
|
|
523
|
+
update()
|
|
463
524
|
}
|
|
464
525
|
|
|
465
526
|
forceUpdate (data, options, callback) {
|
|
466
|
-
if (
|
|
527
|
+
if (this.isUnmounted()) return
|
|
528
|
+
if (isFunction(data)) {
|
|
467
529
|
callback = data
|
|
468
530
|
data = undefined
|
|
469
531
|
}
|
|
470
532
|
|
|
471
533
|
options = options || {}
|
|
472
534
|
|
|
473
|
-
if (
|
|
535
|
+
if (isFunction(options)) {
|
|
474
536
|
callback = options
|
|
475
537
|
options = {}
|
|
476
538
|
}
|
|
477
539
|
|
|
478
540
|
if (isPlainObject(data)) {
|
|
479
|
-
|
|
480
|
-
Object.keys(this.forceUpdateData).forEach(key => {
|
|
541
|
+
Object.keys(data).forEach(key => {
|
|
481
542
|
if (!this.options.__nativeRender__ && !this.localKeysMap[getFirstKey(key)]) {
|
|
482
|
-
warn(`ForceUpdate data includes a props
|
|
543
|
+
warn(`ForceUpdate data includes a props key [${key}], which may yield a unexpected result.`, this.options.mpxFileResource)
|
|
483
544
|
}
|
|
484
|
-
setByPath(this.
|
|
545
|
+
setByPath(this.target, key, data[key])
|
|
485
546
|
})
|
|
547
|
+
this.forceUpdateData = data
|
|
486
548
|
} else {
|
|
487
549
|
this.forceUpdateAll = true
|
|
488
550
|
}
|
|
489
551
|
|
|
490
|
-
if (
|
|
491
|
-
|
|
492
|
-
this.nextTick(callback)
|
|
493
|
-
}
|
|
494
|
-
if (this._watcher) {
|
|
495
|
-
this._watcher.update(options.sync)
|
|
552
|
+
if (this.effect) {
|
|
553
|
+
options.sync ? this.effect.run() : this.effect.update()
|
|
496
554
|
} else {
|
|
497
555
|
if (this.forceUpdateAll) {
|
|
498
|
-
Object.keys(this.
|
|
499
|
-
|
|
500
|
-
this.forceUpdateData[key] = diffAndCloneA(this.data[key]).clone
|
|
501
|
-
}
|
|
556
|
+
Object.keys(this.localKeysMap).forEach((key) => {
|
|
557
|
+
this.forceUpdateData[key] = diffAndCloneA(this.target[key]).clone
|
|
502
558
|
})
|
|
503
559
|
}
|
|
504
|
-
options.sync ? this.doRender() :
|
|
505
|
-
|
|
506
|
-
|
|
560
|
+
options.sync ? this.doRender() : queueJob(this.doRender.bind(this))
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (callback) {
|
|
564
|
+
callback = callback.bind(this.target)
|
|
565
|
+
const doCallback = () => {
|
|
566
|
+
if (this.currentRenderTask?.resolved === false) {
|
|
567
|
+
this.currentRenderTask.promise.then(callback)
|
|
568
|
+
} else {
|
|
569
|
+
callback()
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
options.sync ? doCallback() : nextTick(doCallback)
|
|
507
573
|
}
|
|
508
574
|
}
|
|
509
575
|
}
|
|
576
|
+
|
|
577
|
+
export let currentInstance = null
|
|
578
|
+
|
|
579
|
+
export const getCurrentInstance = () => currentInstance
|
|
580
|
+
|
|
581
|
+
export const setCurrentInstance = (instance) => {
|
|
582
|
+
currentInstance = instance
|
|
583
|
+
instance?.scope?.on()
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export const unsetCurrentInstance = () => {
|
|
587
|
+
currentInstance?.scope?.off()
|
|
588
|
+
currentInstance = null
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export const injectHook = (hookName, hook, instance = currentInstance) => {
|
|
592
|
+
if (instance) {
|
|
593
|
+
const wrappedHook = (...args) => {
|
|
594
|
+
if (instance.isUnmounted()) return
|
|
595
|
+
setCurrentInstance(instance)
|
|
596
|
+
const res = callWithErrorHandling(hook, instance, `${hookName} hook`, args)
|
|
597
|
+
unsetCurrentInstance()
|
|
598
|
+
return res
|
|
599
|
+
}
|
|
600
|
+
if (isFunction(hook)) (instance.hooks[hookName] || (instance.hooks[hookName] = [])).push(wrappedHook)
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export const createHook = (hookName) => (hook, instance) => injectHook(hookName, hook, instance)
|
|
605
|
+
// 在代码中调用以下生命周期钩子时, 将生命周期钩子注入到mpxProxy实例上
|
|
606
|
+
export const onBeforeMount = createHook(BEFOREMOUNT)
|
|
607
|
+
export const onMounted = createHook(MOUNTED)
|
|
608
|
+
export const onBeforeUpdate = createHook(BEFOREUPDATE)
|
|
609
|
+
export const onUpdated = createHook(UPDATED)
|
|
610
|
+
export const onBeforeUnmount = createHook(BEFOREUNMOUNT)
|
|
611
|
+
export const onUnmounted = createHook(UNMOUNTED)
|
|
612
|
+
export const onLoad = createHook(ONLOAD)
|
|
613
|
+
export const onShow = createHook(ONSHOW)
|
|
614
|
+
export const onHide = createHook(ONHIDE)
|
|
615
|
+
export const onResize = createHook(ONRESIZE)
|
|
616
|
+
export const onPullDownRefresh = createHook('__onPullDownRefresh__')
|
|
617
|
+
export const onReachBottom = createHook('__onReachBottom__')
|
|
618
|
+
export const onShareAppMessage = createHook('__onShareAppMessage__')
|
|
619
|
+
export const onShareTimeline = createHook('__onShareTimeline__')
|
|
620
|
+
export const onAddToFavorites = createHook('__onAddToFavorites__')
|
|
621
|
+
export const onPageScroll = createHook('__onPageScroll__')
|
|
622
|
+
export const onTabItemTap = createHook('__onTabItemTap__')
|
|
623
|
+
export const onSaveExitState = createHook('__onSaveExitState__')
|