Skip to content

Commit ebca5e2

Browse files
authored
chore: 调整全局变量更新机制使得组件层无须额外编写检测代码 (#11865)
1 parent 4d64c8f commit ebca5e2

File tree

8 files changed

+220
-62
lines changed

8 files changed

+220
-62
lines changed

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

+58-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import difference from 'lodash/difference';
2+
import findLastIndex from 'lodash/findLastIndex';
23
import omit from 'lodash/omit';
34
import React from 'react';
45
import {isValidElementType} from 'react-is';
@@ -42,6 +43,9 @@ import {evalExpression, filter} from './utils/tpl';
4243
import Animations from './components/Animations';
4344
import {cloneObject} from './utils/object';
4445
import {observeGlobalVars} from './globalVar';
46+
import type {IRootStore} from './store/root';
47+
import {createObjectFromChain, extractObjectChain} from './utils';
48+
import {IIRendererStore} from './store/index';
4549

4650
interface SchemaRendererProps
4751
extends Partial<Omit<RendererProps, 'statusStore'>>,
@@ -110,6 +114,8 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
110114
unbindGlobalEvent: (() => void) | undefined = undefined;
111115
isStatic: any = undefined;
112116

117+
subStore?: IIRendererStore | null;
118+
113119
constructor(props: SchemaRendererProps) {
114120
super(props);
115121

@@ -118,6 +124,7 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
118124
this.reRender = this.reRender.bind(this);
119125
this.resolveRenderer(this.props);
120126
this.dispatchEvent = this.dispatchEvent.bind(this);
127+
this.storeRef = this.storeRef.bind(this);
121128
this.handleGlobalVarChange = this.handleGlobalVarChange.bind(this);
122129

123130
const schema = props.schema;
@@ -183,19 +190,57 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
183190
return false;
184191
}
185192

193+
storeRef(store: IIRendererStore | null) {
194+
this.subStore = store;
195+
}
196+
186197
handleGlobalVarChange() {
187198
const handler = this.renderer?.onGlobalVarChanged;
188-
const newData = cloneObject(this.props.data);
199+
const topStore: IRootStore = this.props.topStore;
200+
const chain = extractObjectChain(this.props.data).filter(
201+
(item: any) => !item.hasOwnProperty('__isTempGlobalLayer')
202+
);
203+
const globalLayerIdx = findLastIndex(
204+
chain,
205+
item =>
206+
item.hasOwnProperty('global') || item.hasOwnProperty('globalState')
207+
);
208+
209+
const globalData = {
210+
...topStore.nextGlobalData,
211+
212+
// 兼容旧的全局变量
213+
__page: topStore.nextGlobalData.__page,
214+
appVariables: topStore.nextGlobalData.appVariables,
215+
__isTempGlobalLayer: true
216+
};
217+
218+
if (globalLayerIdx !== -1) {
219+
chain.splice(globalLayerIdx + 1, 0, globalData);
220+
}
221+
const newData = createObjectFromChain(chain);
189222

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

228+
// 强制刷新并通过一个临时对象让下发给组件的全局变量更新
229+
// 等 react 完成一轮渲染后,将临时渲染切成正式渲染
230+
// 也就是说删掉临时对象,后续渲染读取真正变更后的全局变量
231+
//
232+
233+
// 为什么这么做?因为很多组件内部都会 diff this.props.data 和 prevProps.data
234+
// 如果对应的数据没有发生变化,则会跳过组件状态的更新
195235
this.tmpData = newData;
196-
this.forceUpdate(() => {
197-
delete this.tmpData;
198-
});
236+
this.subStore?.temporaryUpdateGlobalVars(globalData);
237+
topStore.addSyncGlobalVarStatePendingTask(
238+
callback => this.forceUpdate(callback),
239+
() => {
240+
delete this.tmpData;
241+
this.subStore?.unDoTemporaryUpdateGlobalVars();
242+
}
243+
);
199244
}
200245

201246
resolveRenderer(props: SchemaRendererProps, force = false): any {
@@ -369,6 +414,9 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
369414
return render!($path, schema as any, rest) as JSX.Element;
370415
}
371416

417+
// 用于全局变量刷新
418+
(rest as any).data = this.tmpData || rest.data;
419+
372420
const detectData =
373421
schema &&
374422
(schema.detectField === '&' ? rest : rest[schema.detectField || 'data']);
@@ -548,9 +596,6 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
548596
mobileUI: schema.useMobileUI === false ? false : rest.mobileUI
549597
};
550598

551-
// 用于全局变量刷新
552-
props.data = this.tmpData || props.data;
553-
554599
// style 支持公式
555600
if (schema.style) {
556601
(props as any).style = buildStyle(schema.style, detectData);
@@ -588,9 +633,13 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
588633
}
589634

590635
let component = supportRef ? (
591-
<Component {...props} ref={this.childRef} />
636+
<Component {...props} ref={this.childRef} storeRef={this.storeRef} />
592637
) : (
593-
<Component {...props} forwardedRef={this.childRef} />
638+
<Component
639+
{...props}
640+
forwardedRef={this.childRef}
641+
storeRef={this.storeRef}
642+
/>
594643
);
595644

596645
if (schema.animations) {

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

+7-1
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 属性
@@ -50,6 +51,7 @@ export function HocStoreFactory(renderer: {
5051
store?: IIRendererStore;
5152
data?: RendererData;
5253
scope?: RendererData;
54+
storeRef?: (store: IIRendererStore | null) => void;
5355
rootStore: any;
5456
topStore: any;
5557
};
@@ -84,6 +86,9 @@ export function HocStoreFactory(renderer: {
8486
parentId: this.props.store ? this.props.store.id : ''
8587
}) as IIRendererStore;
8688
store.setTopStore(props.topStore);
89+
90+
props.storeRef?.(store);
91+
8792
this.store = store;
8893

8994
const extendsData =
@@ -402,6 +407,7 @@ export function HocStoreFactory(renderer: {
402407

403408
// @ts-ignore
404409
delete this.store;
410+
this.props.storeRef?.(null);
405411
}
406412

407413
renderChild(
@@ -424,7 +430,7 @@ export function HocStoreFactory(renderer: {
424430
}
425431

426432
render() {
427-
const {detectField, ...rest} = this.props;
433+
const {detectField, storeRef, ...rest} = this.props;
428434

429435
if (this.state.hidden || this.state.visible === false) {
430436
return null;

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

+30
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
injectObjectChain
2020
} from '../utils';
2121
import {DataChangeReason} from '../types';
22+
import findLastIndex from 'lodash/findLastIndex';
2223

2324
export const iRendererStore = StoreNode.named('iRendererStore')
2425
.props({
@@ -97,6 +98,35 @@ export const iRendererStore = StoreNode.named('iRendererStore')
9798
self.upStreamData = data;
9899
},
99100

101+
// 临时更新全局变量
102+
temporaryUpdateGlobalVars(globalVar: any) {
103+
const chain = extractObjectChain(self.data).filter(
104+
(item: any) => !item.hasOwnProperty('__isTempGlobalLayer')
105+
);
106+
const idx = findLastIndex(
107+
chain,
108+
item =>
109+
item.hasOwnProperty('global') || item.hasOwnProperty('globalState')
110+
);
111+
112+
if (idx !== -1) {
113+
chain.splice(idx + 1, 0, {
114+
...globalVar,
115+
__isTempGlobalLayer: true
116+
});
117+
}
118+
119+
self.data = createObjectFromChain(chain);
120+
},
121+
122+
// 撤销临时更新全局变量
123+
unDoTemporaryUpdateGlobalVars() {
124+
const chain = extractObjectChain(self.data).filter(
125+
(item: any) => !item.hasOwnProperty('__isTempGlobalLayer')
126+
);
127+
self.data = createObjectFromChain(chain);
128+
},
129+
100130
reset() {
101131
self.data = self.pristine;
102132
},

0 commit comments

Comments
 (0)