amis源码 数据域 数据链解析

注意: 看此篇文章前需要你对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;
}
相关推荐
canonical_entropy5 小时前
金蝶云苍穹的Extension与Nop平台的Delta的区别
后端·低代码·架构
Kenneth風车5 小时前
【机器学习(五)】分类和回归任务-AdaBoost算法-Sentosa_DSML社区版
人工智能·算法·低代码·机器学习·数据分析
工业甲酰苯胺10 小时前
低代码开发:助力制造业数字化高质量发展
低代码
工业甲酰苯胺10 小时前
数字化转型背景下低代码开发模式变革的研究
低代码
BPM_宏天低代码10 小时前
低代码技术:简化应用开发的未来
低代码
Kenneth風车14 小时前
【第十三章:Sentosa_DSML社区版-机器学习聚类】
人工智能·低代码·机器学习·数据分析·聚类
有颜有货1 天前
低代码开发平台系统架构概述
低代码·系统架构
lgbisha1 天前
828华为云征文|华为云Flexus X实例docker部署最新Appsmith社区版,搭建自己的低代码平台
低代码·docker·华为云
快乐非自愿1 天前
一文详解低代码开发如何成为学校低成本数字化转型的新引擎
低代码
Kenneth風车2 天前
【机器学习(七)】分类和回归任务-K-近邻 (KNN)算法-Sentosa_DSML社区版
人工智能·算法·低代码·机器学习·分类·数据分析·回归