Skip to content

Commit ebaba09

Browse files
committed
chore: 调整全局变量更新机制使得组件层无须额外编写检测代码
1 parent e4f4eca commit ebaba09

File tree

8 files changed

+174
-58
lines changed

8 files changed

+174
-58
lines changed

Diff for: packages/amis-core/src/SchemaRenderer.tsx

+29-7
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ import {evalExpression, filter} from './utils/tpl';
4242
import Animations from './components/Animations';
4343
import {cloneObject} from './utils/object';
4444
import {observeGlobalVars} from './globalVar';
45+
import type {IRootStore} from './store/root';
46+
import {createObjectFromChain, extractObjectChain} from './utils';
4547

4648
interface SchemaRendererProps
4749
extends Partial<Omit<RendererProps, 'statusStore'>>,
@@ -185,17 +187,37 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
185187

186188
handleGlobalVarChange() {
187189
const handler = this.renderer?.onGlobalVarChanged;
188-
const newData = cloneObject(this.props.data);
190+
const topStore: IRootStore = this.props.topStore;
191+
const chain = extractObjectChain(this.props.data).map((item: any) =>
192+
item.hasOwnProperty('global') || item.hasOwnProperty('globalState')
193+
? {
194+
...topStore.nextGlobalData,
195+
__globalVarChanged: true,
196+
__globalVarChangedAt: Date.now()
197+
}
198+
: item
199+
);
200+
const newData = createObjectFromChain(chain);
189201

190202
// 如果渲染器自己做了实现,且返回 false,则不再继续往下执行
191203
if (handler?.(this.cRef, this.props.schema, newData) === false) {
192204
return;
193205
}
194206

207+
// 强制刷新并通过一个临时对象让下发给组件的全局变量更新
208+
// 等 react 完成一轮渲染后,将临时渲染切成正式渲染
209+
// 也就是说删掉临时对象,后续渲染读取真正变更后的全局变量
210+
//
211+
212+
// 为什么这么做?因为很多组件内部都会 diff this.props.data 和 prevProps.data
213+
// 如果对应的数据没有发生变化,则会跳过组件状态的更新
195214
this.tmpData = newData;
196-
this.forceUpdate(() => {
197-
delete this.tmpData;
198-
});
215+
topStore.addSyncGlobalVarStatePendingTask(
216+
callback => this.forceUpdate(callback),
217+
() => {
218+
delete this.tmpData;
219+
}
220+
);
199221
}
200222

201223
resolveRenderer(props: SchemaRendererProps, force = false): any {
@@ -369,6 +391,9 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
369391
return render!($path, schema as any, rest) as JSX.Element;
370392
}
371393

394+
// 用于全局变量刷新
395+
(rest as any).data = this.tmpData || rest.data;
396+
372397
const detectData =
373398
schema &&
374399
(schema.detectField === '&' ? rest : rest[schema.detectField || 'data']);
@@ -548,9 +573,6 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
548573
mobileUI: schema.useMobileUI === false ? false : rest.mobileUI
549574
};
550575

551-
// 用于全局变量刷新
552-
props.data = this.tmpData || props.data;
553-
554576
// style 支持公式
555577
if (schema.style) {
556578
(props as any).style = buildStyle(schema.style, detectData);

Diff for: packages/amis-core/src/WithStore.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from './utils/helper';
2020
import {dataMapping, tokenize} from './utils/tpl-builtin';
2121
import {RootStoreContext} from './WithRootStore';
22+
import {extractObjectChain} from './utils/object';
2223

2324
/**
2425
* 忽略静态数据中的 schema 属性
@@ -388,6 +389,18 @@ export function HocStoreFactory(renderer: {
388389
props.data?.__changeReason
389390
);
390391
}
392+
393+
if (
394+
props.data?.__globalVarChangedAt &&
395+
prevProps.data?.__globalVarChangedAt !==
396+
props.data?.__globalVarChangedAt
397+
) {
398+
const globalVar = {
399+
global: props.data.global,
400+
globalState: props.data.globalState
401+
};
402+
globalVar && store.updateGlobalVars(globalVar);
403+
}
391404
}
392405

393406
componentWillUnmount() {

Diff for: packages/amis-core/src/globalVar.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {isExpression} from './utils/formula';
99
import {reaction} from 'mobx';
1010
import {resolveVariableAndFilter} from './utils/resolveVariableAndFilter';
1111
import {IRootStore} from './store/root';
12+
import isPlainObject from 'lodash/isPlainObject';
1213

1314
/**
1415
* 全局变量的定义
@@ -298,6 +299,17 @@ export function observeGlobalVars(
298299
key,
299300
value
300301
});
302+
} else if (isPlainObject(value) && !value.type) {
303+
// 最多支持两层,多了可能就会有性能问题了
304+
// 再多一层主要是为了支持某些 api 配置的是对象形式
305+
Object.keys(value).forEach(k => {
306+
if (isGlobalVarExpression(value[k])) {
307+
expressions.push({
308+
key: `${key}.${k}`,
309+
value: value[k]
310+
});
311+
}
312+
});
301313
} else if (
302314
[
303315
'items',
@@ -338,7 +350,7 @@ export function observeGlobalVars(
338350
exp =>
339351
`${exp.key}:${resolveVariableAndFilter(
340352
exp.value,
341-
topStore.downStream,
353+
topStore.nextGlobalData,
342354
'| json' // 如果用了复杂对象,要靠这个来比较
343355
)}`
344356
)

Diff for: packages/amis-core/src/renderers/Item.tsx

+1-18
Original file line numberDiff line numberDiff line change
@@ -2363,24 +2363,7 @@ export function registerFormItem(config: FormItemConfig): RendererConfig {
23632363
...config,
23642364
weight: typeof config.weight !== 'undefined' ? config.weight : -100, // 优先级高点
23652365
component: Control as any,
2366-
isFormItem: true,
2367-
onGlobalVarChanged: function (instance, schema, data): any {
2368-
if (config.onGlobalVarChanged?.apply(this, arguments) === false) {
2369-
return false;
2370-
}
2371-
2372-
if (isGlobalVarExpression(schema.source)) {
2373-
(instance.props as any).reloadOptions?.();
2374-
}
2375-
2376-
// 目前表单项的全局变量更新要靠这个方式
2377-
if (isGlobalVarExpression(schema.value)) {
2378-
(instance.props as any).onChange(
2379-
resolveVariableAndFilter(schema.value, data, '| raw')
2380-
);
2381-
return false;
2382-
}
2383-
}
2366+
isFormItem: true
23842367
});
23852368
}
23862369

Diff for: packages/amis-core/src/store/iRenderer.ts

+9
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,15 @@ export const iRendererStore = StoreNode.named('iRendererStore')
9797
self.upStreamData = data;
9898
},
9999

100+
updateGlobalVars(globalVar: any) {
101+
const chain = extractObjectChain(self.data).map(item =>
102+
item.hasOwnProperty('global') || item.hasOwnProperty('globalState')
103+
? globalVar
104+
: item
105+
);
106+
self.data = createObjectFromChain(chain);
107+
},
108+
100109
reset() {
101110
self.data = self.pristine;
102111
},

Diff for: packages/amis-core/src/store/root.ts

+88-8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ export const RootStore = ServiceStore.named('RootStore')
2727
runtimeErrorStack: types.frozen(),
2828
query: types.frozen(),
2929
ready: false,
30+
31+
// 临时变更,等 react 完成一轮渲染后,将临时变更切成正式变更
32+
// 主要是为了让可能需要重新渲染的部分组件可以实现 this.props.data 和 prevProps.data 不一致
33+
// 因为很多组件内部会 diff this.props.data 和 prevProps.data 来决定是否更新的逻辑
34+
globalVarTempStates: types.optional(
35+
types.map(types.frozen<GlobalVariableState>()),
36+
{}
37+
),
38+
39+
// 正式变更
3040
globalVarStates: types.optional(
3141
types.map(types.frozen<GlobalVariableState>()),
3242
{}
@@ -43,6 +53,46 @@ export const RootStore = ServiceStore.named('RootStore')
4353
};
4454
})
4555
.views(self => ({
56+
get nextGlobalData() {
57+
let globalData = {} as any;
58+
let globalState = {} as any;
59+
60+
if (self.globalVarTempStates.size) {
61+
let touched = false;
62+
let saved = true;
63+
let errors: any = {};
64+
let initialized = true;
65+
self.globalVarTempStates.forEach((state, key) => {
66+
globalData[key] = state.value;
67+
touched = touched || state.touched;
68+
if (!state.saved) {
69+
saved = false;
70+
}
71+
72+
if (state.errorMessages.length) {
73+
errors[key] = state.errorMessages;
74+
}
75+
if (!state.initialized) {
76+
initialized = false;
77+
}
78+
});
79+
80+
globalState = {
81+
fields: self.globalVarTempStates.toJSON(),
82+
initialized: initialized,
83+
touched: touched,
84+
saved: saved,
85+
errors: errors,
86+
valid: !Object.keys(errors).length
87+
};
88+
}
89+
90+
return {
91+
global: globalData,
92+
globalState: globalState
93+
};
94+
},
95+
4696
get downStream() {
4797
let result = self.data;
4898

@@ -168,7 +218,7 @@ export const RootStore = ServiceStore.named('RootStore')
168218

169219
if (getter) {
170220
await getGlobalVarData(item, context, getter);
171-
const state = self.globalVarStates.get(item.key)!;
221+
const state = self.globalVarTempStates.get(item.key)!;
172222
updateState(item.key, {
173223
initialized: true,
174224
pristine: state.value
@@ -234,7 +284,7 @@ export const RootStore = ServiceStore.named('RootStore')
234284
continue;
235285
}
236286

237-
const state = self.globalVarStates.get(key);
287+
const state = self.globalVarTempStates.get(key);
238288
if (state) {
239289
updateState(key, {
240290
value: data[key],
@@ -275,7 +325,7 @@ export const RootStore = ServiceStore.named('RootStore')
275325
const itemsNotInitialized: Array<GlobalVariableItemFull> = [];
276326

277327
for (let item of globalVars) {
278-
let state = self.globalVarStates.get(item.key);
328+
let state = self.globalVarTempStates.get(item.key);
279329
if (state?.initialized) {
280330
continue;
281331
}
@@ -323,8 +373,10 @@ export const RootStore = ServiceStore.named('RootStore')
323373
self.globalVars = yield initializeGlobalVars(newVars, updateVars);
324374

325375
removeVars.forEach(item => {
326-
self.globalVarStates.delete(item.key);
376+
self.globalVarTempStates.delete(item.key);
327377
});
378+
379+
syncGlobalVarStates();
328380
});
329381

330382
// 更新全局变量的值
@@ -350,7 +402,7 @@ export const RootStore = ServiceStore.named('RootStore')
350402
value: any;
351403
}
352404
) => {
353-
const state = self.globalVarStates.get(key);
405+
const state = self.globalVarTempStates.get(key);
354406
if (!state) {
355407
return;
356408
}
@@ -483,7 +535,7 @@ export const RootStore = ServiceStore.named('RootStore')
483535
}> = [];
484536
const values: any = {};
485537
for (let varItem of self.globalVars) {
486-
const state = self.globalVarStates.get(varItem.key);
538+
const state = self.globalVarTempStates.get(varItem.key);
487539
if (!state?.touched) {
488540
continue;
489541
} else if (key && key !== varItem.key) {
@@ -536,19 +588,45 @@ export const RootStore = ServiceStore.named('RootStore')
536588
leading: false
537589
});
538590

591+
function syncGlobalVarStates() {
592+
self.globalVarStates.clear();
593+
self.globalVarTempStates.forEach((state, key) => {
594+
self.globalVarStates.set(key, {...state});
595+
});
596+
}
597+
598+
let pendingCount = 0;
599+
let callbacks: Array<() => void> = [];
600+
function addSyncGlobalVarStatePendingTask(
601+
fn: (callback: () => void) => void,
602+
callback?: () => void
603+
) {
604+
pendingCount++;
605+
callback && callbacks.push(callback);
606+
fn(() => {
607+
pendingCount--;
608+
609+
if (pendingCount === 0) {
610+
callbacks.forEach(callback => callback());
611+
callbacks = [];
612+
(self as any).syncGlobalVarStates();
613+
}
614+
});
615+
}
616+
539617
return {
540618
updateContext(context: any) {
541619
// 因为 context 不是受控属性,直接共用引用好了
542620
// 否则还会触发孩子节点的重新渲染
543621
Object.assign(self.context, context);
544622
},
545623
updateGlobalVarState(key: string, state: Partial<GlobalVariableState>) {
546-
const origin = self.globalVarStates.get(key);
624+
const origin = self.globalVarTempStates.get(key);
547625
const newState = {
548626
...(origin || createGlobalVarState()),
549627
...state
550628
};
551-
self.globalVarStates.set(key, newState as any);
629+
self.globalVarTempStates.set(key, newState as any);
552630
},
553631
setGlobalVars,
554632
updateGlobalVarValue,
@@ -565,6 +643,8 @@ export const RootStore = ServiceStore.named('RootStore')
565643
}
566644
},
567645
init: init,
646+
syncGlobalVarStates,
647+
addSyncGlobalVarStatePendingTask,
568648
afterDestroy() {
569649
lazySaveGlobalVarValues.flush();
570650
}

Diff for: packages/amis-editor-core/src/variable.ts

+20-7
Original file line numberDiff line numberDiff line change
@@ -259,15 +259,24 @@ export class VariableManager {
259259

260260
const options = [
261261
...this.getVariableOptions(),
262-
...this.getPageVariablesOptions()
262+
...this.getPageVariablesOptions(),
263+
...this.getGlobalVariablesOptions()
263264
];
264-
const node = findTree(
265-
options,
266-
item => item[valueField ?? 'value'] === path
267-
);
268-
265+
let nodePaths: Array<any> = [];
266+
const node = findTree(options, (item, key, level, paths) => {
267+
if (item[valueField ?? 'value'] === path) {
268+
nodePaths = paths.concat(item);
269+
return true;
270+
}
271+
return false;
272+
});
269273
return node
270-
? node[labelField ?? 'label'] ?? node[valueField ?? 'value'] ?? ''
274+
? nodePaths
275+
.map(
276+
node =>
277+
node[labelField ?? 'label'] ?? node[valueField ?? 'value'] ?? ''
278+
)
279+
.join(' / ')
271280
: '';
272281
}
273282

@@ -288,7 +297,11 @@ export class VariableManager {
288297
if (item.type === 'array') {
289298
delete item.children;
290299
}
300+
if (item.value === 'global') {
301+
item.disabled = true;
302+
}
291303
});
304+
292305
return options;
293306
}
294307
}

0 commit comments

Comments
 (0)