注意: 看此篇文章前需要你对react mobx-state-tree有一定了解,如果不了解请先看这篇文章 React 之 mobx-state-tree(Redux替代品) 状态管理-CSDN博客
store的定义(mobx)
数据域基本是在store中维护的:
amis-core/src/store文件夹是mobx 定义,这里主要介绍几个比较重要的:
1. node.ts定义了基础model:
其他所有store定义基本都是基于此model定义进行.named, .props, .views, .actions拓展的
export const StoreNode = types
.model('StoreNode', { id: types.identifier, path: '', storeType: types.string, disposed: false, parentId: '', childrenIds: types.optional(types.array(types.string), []) })
.views(self => { })
.actions(self=>{ })
2-1. index.ts 中:
定义了RendererStore渲染器store的形状 和 addStore、removeStore、get stores来维护组件store:
export const RendererStore = types
.model('RendererStore', {
storeType: 'RendererStore'
}).views( get stores() {
return getStores();
} )
.actions(self =>({
addStore(store){ //若是RootStore,创建RootStore定义的model,否则找到对应类型定义的model然后再创建
if (store.storeType === RootStore.name) {
return addStore(RootStore.create(store, getEnv(self)));//创建model
}
const factory = find( allowedStoreList, item => item.name === store.storeType )!;
return addStore(factory.create(store as any, getEnv(self)));//创建model
}
})
2-2. RendererStore 的使用:
amis.embed时会根据amisEnv.session从缓存中获取,缓存中没有则创建新的RendererStore
amis-core/src/index.tsx(创建了RendererStore的mobx树):
import {RegisterStore, RendererStore} from './store';
render(){
let store = stores[options.session || 'global'];
if (!store) {
store = RendererStore.create({}, options); //
}
(window as any).amisStore = store; // 为了方便 debug. rootStore
//...省略
}
后续渲染组件Component时,会使用amisStore.addStore()创建组件store。
3.iRenderer.ts
iRenderer基于node.ts基础store拓展而来。增加了data prop和对data的处理action
service.ts、table.ts、combo.ts、list.ts均是由iRenderer.ts拓展而来
crud.ts、form.ts、modal.ts、root.ts均是由service.ts拓展而来
export const iRendererStore = StoreNode.named('iRendererStore')
.props({
data: types.optional(types.frozen(), {}),
pristine: types.optional(types.frozen(), {}),
action: types.optional(types.frozen(), undefined),
dialogOpen: false,
dialogData: types.optional(types.frozen(), undefined),
drawerOpen: false,
drawerData: types.optional(types.frozen(), undefined)
})
.views(self => ({
getValueByName(name: string, canAccessSuper: boolean = true) {
return getVariable(self.data, name, canAccessSuper);
},
getPristineValueByName(name: string) {
return getVariable(self.pristine, name, false);
}
}))
.actions(self => {
return {
setTopStore(value: any) {
top = value;
},
initData(data: object = {}, skipSetPristine = false) {
self.initedAt = Date.now();
if (self.data.__tag) {
data = injectObjectChain(data, self.data.__tag);
}
!skipSetPristine && (self.pristine = data);
self.data = data;
},
reset() {
self.data = self.pristine;
},
updateData(
data: object = {},
tag?: object,
replace?: boolean,
concatFields?: string | string[]
) {
if (concatFields) {
data = concatData(data, self.data, concatFields);
}
const prev = self.data;
let newData;
if (tag) {
let proto = createObject((self.data as any).__super || null, {
...tag,
__tag: tag
});
newData = createObject(proto, {
...(replace ? {} : self.data),
...data
});
} else {
newData = extendObject(self.data, data, !replace);
}
Object.defineProperty(newData, '__prev', {
value: {...prev},
enumerable: false,
configurable: false,
writable: false
});
self.data = newData;
},
};
});
4.root.ts
基于service.ts的store拓展而来,主要做顶级数据域。
主要在amis-core/src/RootRenderer中进行了初始化顶级数据域topStore(RootStore类型的mobx树),设置amisProps.data 为顶级数据域
export const RootStore = ServiceStore.named('RootStore')
.props({
runtimeError: types.frozen(),
runtimeErrorStack: types.frozen(),
query: types.frozen()
})
.volatile(self => {
return {
context: {}
};
})
.views(self => ({
get downStream() {
let result = self.data;
if (self.context || self.query) {
const chain = extractObjectChain(result);
self.context && chain.unshift(self.context);
self.query &&
chain.splice(chain.length - 1, 0, {
...self.query,
__query: self.query
});
result = createObjectFromChain(chain);
}
return result;
}
}))
@Renderer和@FormItem中storeType属性,会创建新store
****@Renderer和@FormItem中指定了storeType的会通过WithStore来创建store和初始化数据域,****并通过props传递下store和store.data。
如果没有新的指定了storeType的组件覆盖,那么子组件的props.store和props.data都是复用的父组件
ps:如果指定了isolateScope为true的还会封装一层Scoped : config.component = Scoped(config.component, config.type);
在amis/src/renderers下
像Chart.tsx、CRUD、Dialog、Drawer、Page、App、Service中均指定了storeType:
@Renderer({ type: 'chart', storeType: ServiceStore.name })
@Renderer({ type: 'crud', storeType: CRUDStore.name, isolateScope: true})
@Renderer({ type: 'dialog', storeType: ModalStore.name, storeExtendsData: false, isolateScope: true, shouldSyncSuperStore: ()=>{} })
@Renderer({ type: 'page', storeType: ServiceStore.name, isolateScope: true})
@Renderer({ type: 'app', storeType: AppStore.name })
@Renderer({ type: 'service', storeType: ServiceStore.name, isolateScope: true, storeExtendsData: (props: any) => (props.formStore ? false : true) })
@FormItem({ type: 'combo', storeType: ComboStore.name, ...省略 })
Card、Avatar、input-text 等其他大部分组件则只有type属性,不会创建store
amis-core/src/factory.tsc中对@Renderer装饰器的处理逻辑如下:
判断config.storeType存在,则使用WithStore暴露的HocStoreFactory包装一层进行处理。
amis-core/src/renderers/Item.tsx中@FormItem的处理如下:
判断config.storeType存在,则使用WithStore暴露的HocStoreFactory包装一层进行处理。
amis-core/src/WithStore.tsx:
用于@FormItem和@Renderer中指定了storeType的store(mobx树)创建(大部分都未指定,只有一部分指定了)
同时会props传递 data={this.store.data},store={this.store}, scope={this.store.data}下去
比如下俩个例子(Form和Page):
一Form、amis-core/src/renderers/Form.tsx:
1. @Renderer 指定了storeType
@Renderer({ type: 'form', storeType: FormStore.name, isolateScope: true, ...省略, shouldSyncSuperStore: ()=>{} })
WithStore会 创建新store(FormStore类型的mobx树)并初始化数据域,并通过props传递下去(data={this.store.data},store={this.store}, scope={this.store.data})
2-1.Form.tsx中renderChild方法如下:
subProps中包含1.form的data 2. onChange方法(修改form的data):
const { render } = this.props;
const form = this.props.store;//WithStore传递来的当前form store
const subProps = {
formStore: form,
data: store.data ,//WithStore传递来的当前form store.data
onChange: this.handleChange}
return render(`{region ? \`{region}/` : ''}${key}`, subSchema, subProps);
//此render是SchemaRenderer.tsx中的renderChild方法: 进行了预处理。
2-2.Form.tsx中handleChange核心部分如下:
handleChange( value: any, name: string, submit: boolean, changePristine = false) {
const {store, formLazyChange, persistDataKeys} = this.props;
store.changeValue(name, value, changePristine);//WithStore传递来的当前form store
//...省略
}
3.SchemaRenderer.tsx中renderChild方法如下:
进行预处理向下传递了rest.store(即this.props.store父组件store) 和 subProps.data||rest.data, 最终调用了父组件render(即Root.tsx中renderChild方法,最终走<SchemRenderer>渲染)
4.FormItem中:
FormItem可以调用props.onChange修改form数据域(父组件)的值,通过props.data可以获取form数据域(父组件)
如果没有新的指定了storeType的组件覆盖,那么子组件的props.store和props.data都是复用的父组件
二Page、(amis/src/renderers/Page.tsx):
也有onChange,传递给子组件,改变page的数据域。但是 subProps 没有传递data数据域
const subProps = {
onAction: this.handleAction,
onChange: this.handleChange,
onBulkChange: this.handleBulkChange,
};
{(Array.isArray(regions) ? ~regions.indexOf('body') : body)
?render('body', body || '', subProps)
: null}
handleChange( value: any, name: string, submit?: boolean, changePristine?: boolean ) {
const {store, onChange} = this.props;
if (typeof name === 'string' && name) {
store.changeValue(name, value, changePristine);//WithStore传递来的当前page store
}
//向上派送
onChange?.apply(null, arguments);//onChange是父级props传递来的
}
子组件中的props.data是在走SchemaRenderer渲染时,向下传递的,如下所示。
这里Page调用props.render方法(即这个renderChild)渲染子组件时的subProps没有data,就从rest.data(this.props.data)里取(此时SchemaRenderer的this.props保存的是page的数据)。
ps: rest.store(父组件store)也传递下去了
SchemaRenderer.tsx中renderChild方法如下:
Amis 数据链 实现 :
数据链的实现主要是通过createObject构建将__proto__指向父组件数据并存到__super上。
createObject做了俩件事
1.用Object.create(superProps)创建一个新对象,将原型(proto)指向superProps,同时还附加了__super。
取值时向上通过原型链可以取到所有父级值(还可以通过__super获取父数据域),修改时只修改到当前对象中。
2.将props所有值赋值给新对象。
Object.keys只能取到props的key(不检测原型链)。 in 判断为true(检测原型链) hasOwnProperty 为false(不检测原型链)
amis-core/src/utils/utils/object.ts:
// 方便取值的时候能够把上层的取到,但是获取的时候不会全部把所有的数据获取到。
export function createObject(
superProps?: {[propName: string]: any},
props?: {[propName: string]: any},
properties?: any
): object {
if (superProps && Object.isFrozen(superProps)) {
superProps = cloneObject(superProps);
}
const obj = superProps
? Object.create(superProps, {
...properties,
__super: {
value: superProps,
writable: false,
enumerable: false
}
})
: Object.create(Object.prototype, properties);
props &&
isObject(props) &&
Object.keys(props).forEach(key => (obj[key] = props[key]));
return obj;
}