annil 1.2.2-dev.202312100 → 1.3.0
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/CHANGELOG.md +14 -0
- package/dist/api/DefineComponent/collectOptionsForComponent.d.ts +11 -6
- package/dist/api/DefineComponent/collectOptionsForComponent.js +30 -35
- package/dist/api/DefineComponent/collectOptionsForComponent.js.map +1 -1
- package/dist/api/DefineComponent/index.js.map +1 -1
- package/dist/api/RootComponent/PageLifetimes/PageLifetimesOption.d.ts +1 -1
- package/dist/api/RootComponent/index.js +1 -1
- package/dist/api/RootComponent/index.js.map +1 -1
- package/dist/behaviors/BComputedAndWatch/computedUpdater.js +2 -11
- package/dist/behaviors/BComputedAndWatch/computedUpdater.js.map +1 -1
- package/dist/behaviors/BComputedAndWatch/data-tracer.js.map +1 -1
- package/dist/behaviors/BComputedAndWatch/getPathsValue.d.ts +1 -2
- package/dist/behaviors/BComputedAndWatch/getPathsValue.js +14 -9
- package/dist/behaviors/BComputedAndWatch/getPathsValue.js.map +1 -1
- package/dist/behaviors/BComputedAndWatch/getPropertiesValue.d.ts +2 -0
- package/dist/behaviors/BComputedAndWatch/getPropertiesValue.js +41 -0
- package/dist/behaviors/BComputedAndWatch/getPropertiesValue.js.map +1 -0
- package/dist/behaviors/BComputedAndWatch/index.js +39 -42
- package/dist/behaviors/BComputedAndWatch/index.js.map +1 -1
- package/dist/behaviors/BComputedAndWatch/initComputed.d.ts +4 -8
- package/dist/behaviors/BComputedAndWatch/initComputed.js +16 -40
- package/dist/behaviors/BComputedAndWatch/initComputed.js.map +1 -1
- package/dist/behaviors/BComputedAndWatch/types.d.ts +9 -6
- package/dist/behaviors/BStore.js +1 -4
- package/dist/behaviors/BStore.js.map +1 -1
- package/dist/behaviors/BbeforeCreated.js.map +1 -1
- package/dist/utils/InnerMarker.js.map +1 -1
- package/dist/utils/isEmptyObject.d.ts +1 -1
- package/dist/utils/isEmptyObject.js.map +1 -1
- package/package.json +1 -1
- package/src/api/DefineComponent/collectOptionsForComponent.ts +49 -58
- package/src/api/DefineComponent/index.ts +1 -1
- package/src/api/RootComponent/PageLifetimes/PageLifetimesOption.ts +1 -1
- package/src/api/RootComponent/Watch/test/WatchProperties.test.ts +1 -1
- package/src/api/RootComponent/index.ts +1 -1
- package/src/behaviors/BComputedAndWatch/computedUpdater.ts +3 -20
- package/src/behaviors/BComputedAndWatch/data-tracer.ts +2 -0
- package/src/behaviors/BComputedAndWatch/getPathsValue.ts +24 -10
- package/src/behaviors/BComputedAndWatch/getPropertiesValue.ts +54 -0
- package/src/behaviors/BComputedAndWatch/index.ts +58 -63
- package/src/behaviors/BComputedAndWatch/initComputed.ts +35 -90
- package/src/behaviors/BComputedAndWatch/types.ts +14 -12
- package/src/behaviors/BStore.ts +3 -7
- package/src/behaviors/BbeforeCreated.ts +5 -2
- package/src/utils/InnerMarker.ts +1 -2
- package/src/utils/isEmptyObject.ts +1 -1
- package/dist/behaviors/BComputedAndWatch/IsPage.d.ts +0 -2
- package/dist/behaviors/BComputedAndWatch/IsPage.js +0 -4
- package/dist/behaviors/BComputedAndWatch/IsPage.js.map +0 -1
- package/src/behaviors/BComputedAndWatch/IsPage.ts +0 -5
|
@@ -1,33 +1,21 @@
|
|
|
1
|
-
import { deleteProtoField } from "../../utils/deleteProtoField";
|
|
2
|
-
import { computedUpdater } from "./computedUpdater";
|
|
3
1
|
import { deepProxy, unwrap } from "./data-tracer";
|
|
4
2
|
function isValidDependences(dependences, computedKeys) {
|
|
5
3
|
for (const { paths: path, val } of dependences) {
|
|
6
|
-
if (val === undefined && computedKeys.includes(path[0])) {
|
|
4
|
+
if ((val === undefined) && computedKeys.includes(path[0])) {
|
|
7
5
|
return false;
|
|
8
6
|
}
|
|
9
7
|
}
|
|
10
8
|
return true;
|
|
11
9
|
}
|
|
12
|
-
export function
|
|
10
|
+
export function getItemCache(initAllData, computedKeys, computedFunc) {
|
|
13
11
|
const dependences = [];
|
|
14
|
-
|
|
15
|
-
const _this = this;
|
|
16
|
-
const computedThis = new Proxy({ data: deepProxy(this.data, dependences) }, {
|
|
17
|
-
get(target, key) {
|
|
18
|
-
if (key === "data") {
|
|
19
|
-
return Reflect.get(target, key);
|
|
20
|
-
}
|
|
21
|
-
return Reflect.get(_this, key);
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
initValue = computedFunc.call(computedThis);
|
|
12
|
+
const initValue = computedFunc.call({ data: deepProxy(initAllData, dependences) });
|
|
25
13
|
if (isValidDependences(dependences, computedKeys)) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
return {
|
|
15
|
+
dependences: uniqueDependences(dependences),
|
|
16
|
+
method: computedFunc,
|
|
17
|
+
value: unwrap(initValue),
|
|
18
|
+
};
|
|
31
19
|
}
|
|
32
20
|
else {
|
|
33
21
|
return false;
|
|
@@ -52,35 +40,23 @@ function uniqueDependences(dependences) {
|
|
|
52
40
|
}
|
|
53
41
|
return dependences;
|
|
54
42
|
}
|
|
55
|
-
function
|
|
43
|
+
export function initComputed(options, computedConfig, initAllData, uninitedkeys = Object.keys(computedConfig), computedCache = {}) {
|
|
56
44
|
for (const key of uninitedkeys) {
|
|
57
|
-
const
|
|
45
|
+
const itemCache = getItemCache(initAllData, uninitedkeys, computedConfig[key]);
|
|
58
46
|
uninitedkeys = uninitedkeys.filter(ele => ele !== key);
|
|
59
|
-
if (
|
|
47
|
+
if (itemCache === false) {
|
|
60
48
|
uninitedkeys.push(key);
|
|
61
49
|
}
|
|
62
50
|
else {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
};
|
|
51
|
+
const dataOpt = options.data || (options.data = {});
|
|
52
|
+
dataOpt[key] = itemCache.value;
|
|
53
|
+
initAllData[key] = itemCache.value;
|
|
54
|
+
computedCache[key] = itemCache;
|
|
68
55
|
}
|
|
69
56
|
}
|
|
70
57
|
if (uninitedkeys.length === 0) {
|
|
71
58
|
return computedCache;
|
|
72
59
|
}
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
export function initComputed() {
|
|
76
|
-
var _a;
|
|
77
|
-
const computedConfig = (_a = this.__computedConfig__) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
78
|
-
deleteProtoField(this, "__computedConfig__");
|
|
79
|
-
if (computedConfig) {
|
|
80
|
-
this.__computedStatus__ = "初始化中";
|
|
81
|
-
this.__computedCache__ = _initComputed.call(this, computedConfig, Object.keys(computedConfig));
|
|
82
|
-
this.__computedUpdater__ = computedUpdater.bind(this);
|
|
83
|
-
this.__computedStatus__ = "待更新";
|
|
84
|
-
}
|
|
60
|
+
return initComputed(options, computedConfig, initAllData, uninitedkeys, computedCache);
|
|
85
61
|
}
|
|
86
62
|
//# sourceMappingURL=initComputed.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initComputed.js","sourceRoot":"","sources":["../../../src/behaviors/BComputedAndWatch/initComputed.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"initComputed.js","sourceRoot":"","sources":["../../../src/behaviors/BComputedAndWatch/initComputed.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,19 +4,22 @@ import type { ComputedDependence } from "./initComputed";
|
|
|
4
4
|
export type InstanceCustomFields = {
|
|
5
5
|
__pendingSetData__?: object | null;
|
|
6
6
|
__storeConfig__?: () => Record<string, () => unknown>;
|
|
7
|
-
__computedStatus__?: "
|
|
7
|
+
__computedStatus__?: "待更新" | "更新完毕";
|
|
8
8
|
__computedUpdater__: Func;
|
|
9
|
-
|
|
10
|
-
__computedCache__
|
|
9
|
+
__computedInitCache__?: () => ComputedCache;
|
|
10
|
+
__computedCache__: ComputedCache | undefined;
|
|
11
11
|
__watchOldValue__?: WatchOldValue;
|
|
12
12
|
__watchConfig__?: () => Record<string, Func>;
|
|
13
13
|
__compLoadList__?: Func[];
|
|
14
|
+
__prePropertiesValue__: object | undefined;
|
|
15
|
+
__setDataFromInner__?: boolean;
|
|
14
16
|
disposer: Record<string, Func>;
|
|
15
17
|
};
|
|
16
18
|
export type Instance = (ComponentInstance | PageInstance) & InstanceCustomFields;
|
|
17
|
-
export type
|
|
19
|
+
export type ItemCache = {
|
|
18
20
|
dependences: ComputedDependence[];
|
|
19
21
|
method: Func;
|
|
20
22
|
value: unknown;
|
|
21
|
-
}
|
|
22
|
-
export type
|
|
23
|
+
};
|
|
24
|
+
export type ComputedCache = Record<string, ItemCache>;
|
|
25
|
+
export type WatchOldValue = Record<string, unknown[]>;
|
package/dist/behaviors/BStore.js
CHANGED
|
@@ -6,11 +6,8 @@ export const BStore = Behavior({
|
|
|
6
6
|
return;
|
|
7
7
|
const { toJS } = require("mobx");
|
|
8
8
|
for (const key in storeConfig) {
|
|
9
|
-
options.data || (options.data = {});
|
|
10
9
|
options.data[key] = toJS(storeConfig[key]());
|
|
11
|
-
options.methods || (options.methods = {});
|
|
12
10
|
options.methods.__storeConfig__ = () => storeConfig;
|
|
13
|
-
delete options.store;
|
|
14
11
|
}
|
|
15
12
|
},
|
|
16
13
|
lifetimes: {
|
|
@@ -19,6 +16,7 @@ export const BStore = Behavior({
|
|
|
19
16
|
const storeConfig = (_a = this.__storeConfig__) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
20
17
|
if (!storeConfig)
|
|
21
18
|
return;
|
|
19
|
+
deleteProtoField(this, "__storeConfig__");
|
|
22
20
|
const { comparer, reaction, toJS } = require("mobx");
|
|
23
21
|
this.disposer = {};
|
|
24
22
|
for (const key in storeConfig) {
|
|
@@ -30,7 +28,6 @@ export const BStore = Behavior({
|
|
|
30
28
|
equals: comparer.structural,
|
|
31
29
|
});
|
|
32
30
|
}
|
|
33
|
-
deleteProtoField(this, "__storeConfig__");
|
|
34
31
|
},
|
|
35
32
|
detached() {
|
|
36
33
|
for (const key in this.disposer) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BStore.js","sourceRoot":"","sources":["../../src/behaviors/BStore.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAG7D,MAAM,CAAC,MAAM,MAAM,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"BStore.js","sourceRoot":"","sources":["../../src/behaviors/BStore.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAG7D,MAAM,CAAC,MAAM,MAAM,GAAG,QAAQ,CAAC;IAE7B,gBAAgB,CAAC,OAAgC;QAE/C,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC;QAClC,IAAI,CAAC,WAAW;YAAE,OAAO;QACzB,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAgB,CAAC;QAChD,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAG7C,OAAO,CAAC,OAAO,CAAC,eAAe,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC;QACtD,CAAC;IACH,CAAC;IACD,SAAS,EAAE;QACT,OAAO;;YAEL,MAAM,WAAW,GAAG,MAAA,IAAI,CAAC,eAAe,oDAAI,CAAC;YAC7C,IAAI,CAAC,WAAW;gBAAE,OAAO;YACzB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;YAE1C,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAgB,CAAC;YAEpE,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;YAEnB,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;gBAE9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,QAAQ,CAC3B,WAAW,CAAC,GAAG,CAAC,EAChB,CAAC,KAAc,EAAE,EAAE;oBAEjB,IAAI,CAAC,OAAO,CAAC;wBACX,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC;qBACnB,CAAC,CAAC;gBACL,CAAC,EACD;oBACE,MAAM,EAAE,QAAQ,CAAC,UAAU;iBAC5B,CACF,CAAC;YACJ,CAAC;QACH,CAAC;QAED,QAAQ;YAEN,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAChC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,CAAC;QACH,CAAC;KACF;CACF,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BbeforeCreated.js","sourceRoot":"","sources":["../../src/behaviors/BbeforeCreated.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"BbeforeCreated.js","sourceRoot":"","sources":["../../src/behaviors/BbeforeCreated.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAG,QAAQ,CAAC;IAEpC,gBAAgB,CACd,OAAwF;QAGxF,MAAM,gBAAgB,GAAG,OAAO,CAAC,SAAU,CAAC,YAAY,CAAC;QAEzD,gBAAgB,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InnerMarker.js","sourceRoot":"","sources":["../../src/utils/InnerMarker.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"InnerMarker.js","sourceRoot":"","sources":["../../src/utils/InnerMarker.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,GAAG,EAAE,WAAW;CACjB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function isEmptyObject(obj:
|
|
1
|
+
export declare function isEmptyObject(obj: object): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isEmptyObject.js","sourceRoot":"","sources":["../../src/utils/isEmptyObject.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"isEmptyObject.js","sourceRoot":"","sources":["../../src/utils/isEmptyObject.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,IACE,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,YAAY,GAAG,IAAI,GAAG,YAAY,GAAG;WACtG,GAAG,YAAY,OAAO,IAAI,GAAG,YAAY,OAAO,EACnD,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AAC3C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { Func } from "hry-types/src/Misc/_api";
|
|
2
2
|
import { BBeforeCreate } from "../../behaviors/BbeforeCreated";
|
|
3
3
|
import { BComputedAndWatch } from "../../behaviors/BComputedAndWatch";
|
|
4
|
-
import {
|
|
5
|
-
import type { Instance } from "../../behaviors/BComputedAndWatch/types";
|
|
4
|
+
import type { ComputedCache, Instance } from "../../behaviors/BComputedAndWatch/types";
|
|
6
5
|
import { BStore } from "../../behaviors/BStore";
|
|
7
6
|
import type { WMComponent } from "../../types/OfficialTypeAlias";
|
|
8
7
|
import { INNERMARKER } from "../../utils/InnerMarker";
|
|
@@ -20,6 +19,7 @@ import type { PageInstance } from "../RootComponent/Instance/RootComponentInstan
|
|
|
20
19
|
import type { LifetimesConstraint } from "../RootComponent/Lifetimes/LifetimesConstraint";
|
|
21
20
|
import type { MethodsConstraint } from "../RootComponent/Methods/MethodsConstraint";
|
|
22
21
|
import type { PageLifetimesOption } from "../RootComponent/PageLifetimes/PageLifetimesOption";
|
|
22
|
+
import type { PropertiesConstraint } from "../RootComponent/Properties/PropertiesConstraint";
|
|
23
23
|
import type { StoreConstraint } from "../RootComponent/Store/StoreConstraint";
|
|
24
24
|
import type { SubComponentTrueOptions } from "../SubComponent";
|
|
25
25
|
import type { DefineComponentOption } from ".";
|
|
@@ -33,18 +33,21 @@ export type FuncOptions = Record<"pageLifetimes" | "lifetimes" | "watch", Record
|
|
|
33
33
|
* 最终传入原生Component的配置项
|
|
34
34
|
*/
|
|
35
35
|
export type FinalOptionsOfComponent = {
|
|
36
|
+
data: DataConstraint;
|
|
37
|
+
observers: Record<string, Func>;
|
|
38
|
+
behaviors: string[];
|
|
39
|
+
methods: MethodsConstraint & {
|
|
40
|
+
__computedInitCache__?: () => ComputedCache;
|
|
41
|
+
};
|
|
36
42
|
isPage?: boolean;
|
|
37
43
|
options?: WMComponent.Options;
|
|
38
|
-
properties?:
|
|
39
|
-
data?: DataConstraint;
|
|
44
|
+
properties?: PropertiesConstraint;
|
|
40
45
|
store?: StoreConstraint;
|
|
41
46
|
computed?: ComputedConstraint;
|
|
42
|
-
observers?: Record<string, Func>;
|
|
43
|
-
behaviors?: string[];
|
|
44
|
-
methods?: MethodsConstraint;
|
|
45
47
|
watch?: Record<string, Func>;
|
|
46
48
|
lifetimes?: LifetimesConstraint;
|
|
47
|
-
|
|
49
|
+
pageLifetimes?: PageLifetimesOption<false, object>["pageLifetimes"];
|
|
50
|
+
};
|
|
48
51
|
|
|
49
52
|
/**
|
|
50
53
|
* 原生Component会对传入的对象字段匹配的properties字段setData赋值。不符合字段或Page时不会赋值。
|
|
@@ -60,7 +63,7 @@ function onLoadReceivedDataHandle(
|
|
|
60
63
|
) {
|
|
61
64
|
const innerData: string | undefined = option[INNERMARKER.url];
|
|
62
65
|
|
|
63
|
-
// 情况1为undefined,2为INNERMARKER.url
|
|
66
|
+
// 情况1为undefined,2为INNERMARKER.url有值但不是本身,说明是老框架。3所以innerData等于 INNERMARKER.url即有组件配置了load(新框架在pageLifetimes.load中提前解析了)
|
|
64
67
|
if (innerData === undefined) return;
|
|
65
68
|
if (innerData !== INNERMARKER.url) {
|
|
66
69
|
// 需要情况2 需要解析
|
|
@@ -105,15 +108,15 @@ function loadReceivedDataHandle(
|
|
|
105
108
|
option: Record<typeof INNERMARKER.url, string>,
|
|
106
109
|
) {
|
|
107
110
|
const innerData: string | undefined = option[INNERMARKER.url];
|
|
108
|
-
// 未使用自定义的navigateTo
|
|
109
|
-
if (innerData === undefined
|
|
111
|
+
// 未使用自定义的navigateTo
|
|
112
|
+
if (innerData === undefined) return;
|
|
113
|
+
// 使用navigateTo API
|
|
110
114
|
const decodeOption = JSON.parse(decodeURIComponent(innerData));
|
|
111
115
|
|
|
112
|
-
// 使用navigateTo API
|
|
113
116
|
for (const key in decodeOption) {
|
|
114
117
|
option[key] = decodeOption[key];
|
|
115
118
|
}
|
|
116
|
-
// 给onLoad
|
|
119
|
+
// 给onLoad劫持函数一个标记,判断在新框架下已经被解析过了
|
|
117
120
|
option[INNERMARKER.url] = INNERMARKER.url;
|
|
118
121
|
}
|
|
119
122
|
/**
|
|
@@ -150,9 +153,9 @@ function InternalFieldProtection(config: object | undefined, keys: string[]) {
|
|
|
150
153
|
/**
|
|
151
154
|
* 把函数配置放入一个配置中依次运行
|
|
152
155
|
*/
|
|
153
|
-
function
|
|
156
|
+
function _funcOptionsHandle(config: object, configList: Record<string, Func[]>) {
|
|
154
157
|
for (const key in configList) {
|
|
155
|
-
|
|
158
|
+
config[key] = function(...args: unknown[]) {
|
|
156
159
|
configList[key].forEach(ele => ele.call(this, ...args));
|
|
157
160
|
};
|
|
158
161
|
}
|
|
@@ -160,27 +163,27 @@ function _funcConfigHandle(methodsConfig: object, configList: Record<string, Fun
|
|
|
160
163
|
/**
|
|
161
164
|
* 把函数列表配置放入一个配置中循环一次运行
|
|
162
165
|
*/
|
|
163
|
-
function
|
|
166
|
+
function funcOptionsHandle(
|
|
164
167
|
finalOptionsForComponent: FinalOptionsOfComponent,
|
|
165
168
|
isPage: boolean | undefined,
|
|
166
169
|
funcOptions: FuncOptions,
|
|
167
170
|
) {
|
|
168
|
-
// 测试框架无法测试page情形
|
|
169
171
|
/* istanbul ignore next: miniprogram-simulate(当前版本 1.6.1) 无法测试页面 */
|
|
170
172
|
if (isPage) {
|
|
171
173
|
// 页面时 生命周期方法(即 on 开头的方法),(https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/component.html)
|
|
172
|
-
|
|
174
|
+
!(isEmptyObject(funcOptions.pageLifetimes))
|
|
175
|
+
&& _funcOptionsHandle(finalOptionsForComponent.methods, funcOptions.pageLifetimes);
|
|
173
176
|
} else {
|
|
174
177
|
// 组件时
|
|
175
|
-
funcOptions.pageLifetimes
|
|
176
|
-
&&
|
|
178
|
+
!(isEmptyObject(funcOptions.pageLifetimes))
|
|
179
|
+
&& _funcOptionsHandle(finalOptionsForComponent.pageLifetimes ||= {}, funcOptions.pageLifetimes);
|
|
177
180
|
}
|
|
178
|
-
funcOptions.lifetimes &&
|
|
181
|
+
funcOptions.lifetimes && _funcOptionsHandle(finalOptionsForComponent.lifetimes ||= {}, funcOptions.lifetimes);
|
|
179
182
|
|
|
180
|
-
funcOptions.watch &&
|
|
183
|
+
funcOptions.watch && _funcOptionsHandle(finalOptionsForComponent.watch ||= {}, funcOptions.watch);
|
|
181
184
|
}
|
|
182
185
|
/**
|
|
183
|
-
* 把配置为函数的字段方法收集到
|
|
186
|
+
* 把配置为函数的字段方法收集到funcOptions中
|
|
184
187
|
*/
|
|
185
188
|
function funcFieldsCollect(
|
|
186
189
|
options: SubComponentTrueOptions | RootComponentTrueOptions,
|
|
@@ -193,8 +196,6 @@ function funcFieldsCollect(
|
|
|
193
196
|
(funcOptions[key][_key] ||= []).push(options[key][_key]);
|
|
194
197
|
}
|
|
195
198
|
}
|
|
196
|
-
|
|
197
|
-
Reflect.deleteProperty(options, key);
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
201
|
|
|
@@ -207,10 +208,9 @@ function otherFieldsHandle(
|
|
|
207
208
|
) {
|
|
208
209
|
for (const key in rootComponentOptions) {
|
|
209
210
|
const config = rootComponentOptions[key];
|
|
210
|
-
if (
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
(finalOptions[key] ||= []).push(...config);
|
|
211
|
+
if (key === "behaviors") {
|
|
212
|
+
// 是不是只有behaviors是数组
|
|
213
|
+
finalOptions[key].push(...config);
|
|
214
214
|
} else {
|
|
215
215
|
Object.assign(finalOptions[key] ||= {}, config);
|
|
216
216
|
}
|
|
@@ -219,11 +219,8 @@ function otherFieldsHandle(
|
|
|
219
219
|
/**
|
|
220
220
|
* 把events字段放入到componentOptions.methods中
|
|
221
221
|
*/
|
|
222
|
-
function eventsHandle(
|
|
223
|
-
|
|
224
|
-
componentOptions.methods ||= {};
|
|
225
|
-
|
|
226
|
-
Object.assign(componentOptions.methods, eventsConfig);
|
|
222
|
+
function eventsHandle(methods: FinalOptionsOfComponent["methods"], eventsConfig: EventsConstraint) {
|
|
223
|
+
Object.assign(methods, eventsConfig);
|
|
227
224
|
}
|
|
228
225
|
function subComponentsHandle(
|
|
229
226
|
componentOptions: FinalOptionsOfComponent,
|
|
@@ -231,7 +228,7 @@ function subComponentsHandle(
|
|
|
231
228
|
funcOptions: FuncOptions,
|
|
232
229
|
) {
|
|
233
230
|
subComponents.forEach((subOption) => {
|
|
234
|
-
subOption.events && eventsHandle(componentOptions, subOption.events);
|
|
231
|
+
subOption.events && eventsHandle(componentOptions.methods, subOption.events);
|
|
235
232
|
|
|
236
233
|
funcFieldsCollect(subOption, funcOptions);
|
|
237
234
|
|
|
@@ -250,21 +247,18 @@ function IsFullCustomEvents(
|
|
|
250
247
|
* 把customEvents字段配置变成函数放入到componentOptions.methods中
|
|
251
248
|
*/
|
|
252
249
|
function customEventsHandle(
|
|
253
|
-
|
|
250
|
+
methods: FinalOptionsOfComponent["methods"],
|
|
254
251
|
customEventsConfig: CustomEventConstraint,
|
|
255
252
|
) {
|
|
256
|
-
/* istanbul ignore next: miniprogram-simulate(当前版本 1.6.1) 无法测试页面 */
|
|
257
|
-
componentOptions.methods ||= {};
|
|
258
|
-
|
|
259
253
|
for (const key in customEventsConfig) {
|
|
260
254
|
const customEventOptions = customEventsConfig[key];
|
|
261
255
|
|
|
262
256
|
if (IsFullCustomEvents(customEventOptions)) {
|
|
263
|
-
|
|
257
|
+
methods[key] = function(this: Instance, detail: unknown) {
|
|
264
258
|
this.triggerEvent(key, detail, customEventOptions.options);
|
|
265
259
|
};
|
|
266
260
|
} else {
|
|
267
|
-
|
|
261
|
+
methods[key] = function(this: Instance, detail: unknown) {
|
|
268
262
|
this.triggerEvent(key, detail);
|
|
269
263
|
};
|
|
270
264
|
}
|
|
@@ -281,13 +275,9 @@ function collectRootComponentOption(
|
|
|
281
275
|
funcOptions: FuncOptions,
|
|
282
276
|
rootComponentOptions: RootComponentTrueOptions,
|
|
283
277
|
) {
|
|
284
|
-
rootComponentOptions.customEvents && customEventsHandle(finalOptions, rootComponentOptions.customEvents);
|
|
285
|
-
|
|
286
|
-
delete rootComponentOptions.customEvents;
|
|
278
|
+
rootComponentOptions.customEvents && customEventsHandle(finalOptions.methods, rootComponentOptions.customEvents);
|
|
287
279
|
|
|
288
|
-
rootComponentOptions.events && eventsHandle(finalOptions, rootComponentOptions.events);
|
|
289
|
-
|
|
290
|
-
delete rootComponentOptions.events;
|
|
280
|
+
rootComponentOptions.events && eventsHandle(finalOptions.methods, rootComponentOptions.events);
|
|
291
281
|
|
|
292
282
|
funcFieldsCollect(rootComponentOptions, funcOptions);
|
|
293
283
|
|
|
@@ -306,15 +296,16 @@ export function collectOptionsForComponent(
|
|
|
306
296
|
const rootComponentOption = defineComponentOption.rootComponent;
|
|
307
297
|
const subComponentsList = defineComponentOption.subComponents;
|
|
308
298
|
const finalOptionsForComponent: FinalOptionsOfComponent = {
|
|
309
|
-
// default options
|
|
310
299
|
options: {
|
|
311
300
|
addGlobalClass: true, // "styleIsolation": "apply-shared"
|
|
312
301
|
multipleSlots: true,
|
|
313
302
|
pureDataPattern: /^_/,
|
|
314
303
|
virtualHost: true,
|
|
315
304
|
},
|
|
316
|
-
// default behaviors
|
|
317
305
|
behaviors: [BStore, BComputedAndWatch],
|
|
306
|
+
methods: {},
|
|
307
|
+
observers: {},
|
|
308
|
+
data: {},
|
|
318
309
|
};
|
|
319
310
|
/**
|
|
320
311
|
* 有些字段配置同时存在根组件和子组件当中(如pageLifetimes,lifetimes,watch字段), 且key相同值类型为函数。funcConfig对象用于收集这些配置为数组形式,最终再一起整合进finalOptionsForComponent配置中。即funcConfig是一个临时中介对象。
|
|
@@ -334,25 +325,25 @@ export function collectOptionsForComponent(
|
|
|
334
325
|
}
|
|
335
326
|
// miniprogram-simulate(当前版本 1.6.1) 无法测试页面
|
|
336
327
|
/* istanbul ignore next: miniprogram-simulate(当前版本 1.6.1) 无法测试页面 */
|
|
337
|
-
|
|
328
|
+
funcOptionsHandle(finalOptionsForComponent, rootComponentOption?.isPage, funcOptions);
|
|
338
329
|
|
|
339
|
-
|
|
330
|
+
InternalFieldProtection(finalOptionsForComponent.methods, ["disposer"]);
|
|
340
331
|
|
|
332
|
+
// 对页面传入参数进行处理 老框架劫持页面methods.onLoad,新框架劫持页面pageLifetimes.load
|
|
341
333
|
/* istanbul ignore next: miniprogram-simulate(当前版本 1.6.1) 无法测试页面 */
|
|
342
|
-
finalOptionsForComponent.
|
|
343
|
-
&& hijack(finalOptionsForComponent.
|
|
334
|
+
finalOptionsForComponent.isPage
|
|
335
|
+
&& hijack(finalOptionsForComponent.pageLifetimes ||= {}, "load", [loadReceivedDataHandle], []);
|
|
344
336
|
|
|
345
337
|
/* istanbul ignore next: miniprogram-simulate(当前版本 1.6.1) 无法测试页面 */
|
|
346
|
-
finalOptionsForComponent.
|
|
347
|
-
&& hijack(finalOptionsForComponent.pageLifetimes, "load", [loadReceivedDataHandle], []);
|
|
338
|
+
hijack(finalOptionsForComponent.methods, "onLoad", [onLoadReceivedDataHandle], []);
|
|
348
339
|
|
|
349
340
|
// hijack(finalOptionsForComponent.lifetimes!, "attached", [pathCheck(defineComponentOption.path)], []);
|
|
350
341
|
|
|
351
342
|
/* istanbul ignore next: miniprogram-simulate(当前版本 1.6.1) 无法测试页面 */
|
|
352
|
-
|
|
343
|
+
finalOptionsForComponent.isPage
|
|
353
344
|
// 页面时删除预设的虚拟组件字段
|
|
354
|
-
Reflect.deleteProperty(finalOptionsForComponent.options!, "virtualHost");
|
|
355
|
-
|
|
345
|
+
&& Reflect.deleteProperty(finalOptionsForComponent.options!, "virtualHost");
|
|
346
|
+
|
|
356
347
|
// BBeforeCreate在最后面,让BeforeCreate生命周期运行在最终建立组件时。
|
|
357
348
|
finalOptionsForComponent.behaviors!.push(BBeforeCreate);
|
|
358
349
|
|
|
@@ -55,6 +55,6 @@ export type DefineComponentOption = {
|
|
|
55
55
|
export const DefineComponent: DefineComponentConstructor = function(options): any {
|
|
56
56
|
// console.log("---------------------------");
|
|
57
57
|
Component(
|
|
58
|
-
collectOptionsForComponent(options as DefineComponentOption),
|
|
58
|
+
collectOptionsForComponent(options as DefineComponentOption) as any,
|
|
59
59
|
);
|
|
60
60
|
};
|
|
@@ -134,7 +134,7 @@ RootComponent()({
|
|
|
134
134
|
optional_obj(newValue, oldValue) {
|
|
135
135
|
Checking<ReadonlyDeep<Mock_User>, typeof newValue, Test.Pass>;
|
|
136
136
|
|
|
137
|
-
Checking<ReadonlyDeep<Mock_User
|
|
137
|
+
Checking<ReadonlyDeep<Mock_User | null>, typeof oldValue, Test.Pass>;
|
|
138
138
|
},
|
|
139
139
|
// 对象的二段key
|
|
140
140
|
"optional_obj.age"(newValue, oldValue) {
|
|
@@ -128,7 +128,7 @@ export function RootComponent<
|
|
|
128
128
|
>(): RootComponentConstructor<
|
|
129
129
|
TComponentDocList
|
|
130
130
|
> {
|
|
131
|
-
return (
|
|
131
|
+
return (options: any) => options;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
export type RootComponentTrueOptions = {
|
|
@@ -6,17 +6,12 @@ import { isEqual } from "./isEqual";
|
|
|
6
6
|
import type { Instance } from "./types";
|
|
7
7
|
|
|
8
8
|
export function computedUpdater(this: Instance, isUpdated = false): boolean {
|
|
9
|
-
// console.log("computedUpdater开始");
|
|
10
|
-
|
|
11
|
-
// console.log("循环判断缓存中每个计算属性依赖是否有变化");
|
|
12
|
-
|
|
13
9
|
for (const key in this.__computedCache__) {
|
|
14
|
-
// console.log("当前计算属性为:", key);
|
|
15
|
-
|
|
16
10
|
const itemCache = this.__computedCache__[key];
|
|
17
11
|
let changed = false;
|
|
18
12
|
for (const dep of itemCache.dependences) {
|
|
19
|
-
|
|
13
|
+
// getPathsValue返回的是数组
|
|
14
|
+
const curVal = getPathsValue(this.data, dep.paths.join("."))[0];
|
|
20
15
|
|
|
21
16
|
// 检查依赖是否更新
|
|
22
17
|
if (!isEqual(curVal, dep.val)) {
|
|
@@ -27,19 +22,7 @@ export function computedUpdater(this: Instance, isUpdated = false): boolean {
|
|
|
27
22
|
}
|
|
28
23
|
if (changed) {
|
|
29
24
|
const newDependences: ComputedDependence[] = [];
|
|
30
|
-
|
|
31
|
-
const _this = this;
|
|
32
|
-
const computedThis = new Proxy({ data: deepProxy(this.data, newDependences) }, {
|
|
33
|
-
get(target, key) {
|
|
34
|
-
if (key === "data") {
|
|
35
|
-
return Reflect.get(target, key);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return Reflect.get(_this, key);
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const newValue = itemCache.method.call(computedThis);
|
|
25
|
+
const newValue = itemCache.method.call({ data: deepProxy(this.data, newDependences) });
|
|
43
26
|
|
|
44
27
|
// 更新值不会立即再次进入**函数,而是当前**函数运行完毕后触发**函数,
|
|
45
28
|
this.setData({
|
|
@@ -1,12 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 获取指定paths的值
|
|
3
|
+
* @param this - 组件实例 Instance
|
|
4
|
+
* @param paths - 支持多字段(用`,`分开) 例如 'obj.xxx,a,b' 监控了3个字段
|
|
5
|
+
* @returns unknown[] 每项对应paths每项的值
|
|
6
|
+
*/
|
|
7
|
+
export function getPathsValue(data: object, paths: string) {
|
|
8
|
+
const valueList: unknown[] = [];
|
|
2
9
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// ['obj.xxx','a','b'].forEach
|
|
11
|
+
paths.split(",").forEach(path => {
|
|
12
|
+
// path : 'obj.xxx' | 'a' | 'b'
|
|
13
|
+
const value = path.split(".").reduce((pre: any, path) => {
|
|
14
|
+
// pre有可能为undefined|null,比如obj是未初始化的计算属性(undefined),还有可能是properties的对象类型(默认为null)
|
|
15
|
+
try {
|
|
16
|
+
return pre[path];
|
|
17
|
+
} catch (error) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}, data);
|
|
21
|
+
|
|
22
|
+
valueList.push(value);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return valueList;
|
|
12
26
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PropertiesConstraint,
|
|
3
|
+
PropertiesTypes,
|
|
4
|
+
RequiredSingle,
|
|
5
|
+
RequiredUnion,
|
|
6
|
+
} from "../../api/RootComponent/Properties/PropertiesConstraint";
|
|
7
|
+
|
|
8
|
+
type PropertiesDefaultValue = "" | 0 | [] | null | false;
|
|
9
|
+
|
|
10
|
+
/* istanbul ignore next */
|
|
11
|
+
function getRequiredSingleValue(
|
|
12
|
+
PropType: RequiredSingle,
|
|
13
|
+
): PropertiesDefaultValue {
|
|
14
|
+
switch (PropType) {
|
|
15
|
+
case String:
|
|
16
|
+
return "";
|
|
17
|
+
case Number:
|
|
18
|
+
return 0;
|
|
19
|
+
case Array:
|
|
20
|
+
return [];
|
|
21
|
+
case Object:
|
|
22
|
+
return null;
|
|
23
|
+
case Boolean:
|
|
24
|
+
return false;
|
|
25
|
+
default:
|
|
26
|
+
/* istanbul ignore next */
|
|
27
|
+
throw Error(
|
|
28
|
+
"properties字段类型只能为 String | Number | Array | Object | Boolean",
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function IsRequiredSingle(config: PropertiesTypes): config is RequiredSingle {
|
|
33
|
+
return config instanceof Function;
|
|
34
|
+
}
|
|
35
|
+
function IsRequiredUnion(config: PropertiesTypes): config is RequiredUnion {
|
|
36
|
+
return !Reflect.has(config, "value");
|
|
37
|
+
}
|
|
38
|
+
export function getPropertiesValue(propertiesOpt: PropertiesConstraint | undefined): object | undefined {
|
|
39
|
+
if (propertiesOpt === undefined) return;
|
|
40
|
+
const result = {};
|
|
41
|
+
for (const key in propertiesOpt) {
|
|
42
|
+
const config = propertiesOpt[key];
|
|
43
|
+
/* istanbul ignore next */
|
|
44
|
+
if (IsRequiredSingle(config)) {
|
|
45
|
+
result[key] = getRequiredSingleValue(config);
|
|
46
|
+
} else if (IsRequiredUnion(config)) {
|
|
47
|
+
result[key] = getRequiredSingleValue(config.type);
|
|
48
|
+
} else {
|
|
49
|
+
result[key] = config.value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
}
|