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}, render={this.renderChild}下去

比如以下俩个例子(Form和Page):

一、Form中FormItem元素渲染 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},render={this.renderChild})

2-1.Form.tsx中renderChild方法(form.body中formItem渲染)如下:

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是WithStore.tsx中的renderChild方法: 进行了预处理。

      renderChild(
        region: string,
        node: SchemaNode,
        subProps: {
          data?: object;
          [propName: string]: any;
        } = {}
      ) {
        let {render} = this.props;

        return render(region, node, {
          data: this.store.data,
          dataUpdatedAt: this.store.updatedAt,
          ...subProps,
          scope: this.store.data,
          store: this.store
        });
      }

//此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方法如下:

进行预处理向下传递了相关参数 和 subProps.data(即formStore.data) || rest.data(即topStore.downStream), 最终调用了父组件render(即Root.tsx中renderChild方法,最终走<SchemRenderer>识别并渲染FormItem子组件)

4.FormItem中:

FormItem可以调用props.onChange修改form数据域(父组件)的值,通过props.data可以获取form数据域(父组件)

如果没有新的指定了storeType的组件覆盖,那么子组件的props.store和props.data都是复用的父组件

二、Page中body子元素渲染(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传递来的

  }

//此render是WithStore.tsx中的renderChild方法: 进行了预处理。

子组件中的props.data是在WithStore.tsx中render调用时,向下传递的,如下所示。

      renderChild(
        region: string,
        node: SchemaNode,
        subProps: {
          data?: object;
          [propName: string]: any;
        } = {}
      ) {
        let {render} = this.props;

        return render(region, node, {
          data: this.store.data,
          dataUpdatedAt: this.store.updatedAt,
          ...subProps,
          scope: this.store.data,
          store: this.store
        });
      }

//此render是SchemaRenderer.tsx中的renderChild方法

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;
}

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
amis 是一个低代码前端框架,它使用 JSON 配置来生成页面,可以节省页面开发工作量,极大提升开发前端页面的效率。 目前在百度广泛用于内部平台的前端开发,已有 100+ 部门使用,创建了 3w+ 页面。 特点: 1、不需要懂前端:在百度内部,大部分 amis 用户之前从来没写过前端页面,也不会 JavaScript,却能做出专业且复杂的后台界面,这是所有其他前端 UI 库都无法做到的; 2、不受前端技术更新的影响:百度内部最老的 amis 页面是 4 年多前创建的,至今还在使用,而当年的 Angular/Vue/React 版本现在都废弃了,当年流行的 Gulp 也被 Webpack 取代了,如果这些页面不是用 amis,现在的维护成本会很高; 3、享受 amis 的不断升级:amis 一直在提升细节交互体验,比如表格首行冻结、下拉框大数据下不卡顿等,之前的 JSON 配置完全不需要修改; 4、可以完全使用可视化页面编辑器 来制作页面:一般前端可视化编辑器只能用来做静态原型,而 amis 可视化编辑器做出的页面是可以直接上线的。 5、提供完整的界面解决方案:其它 UI 框架必须使用 JavaScript 来组装业务逻辑,而 amis 只需 JSON 配置就能完成完整功能开发,包括数据获取、表单提交及验证等功能,做出来的页面不需要经过二次开发就能直接上线; 6、内置 100+ 种 UI 组件:包括其它 UI 框架都不会提供的富文本编辑器、条件组合等,能满足各种页面组件展现的需求,而且对于特殊的展现形式还可以通过 自定义组件 来扩充; 7、容器支持无限级嵌套:可以通过组合来满足各种布局需求; 8、经历了长时间的实战考验:amis 在百度内部得到了广泛使用,在 4 年多的时间里创建了 3 万+ 页面,从内容审核到机器管理,从数据分析到模型训练,amis 满足了各种各样的页面需求,最复杂的页面有超过 1 万行 JSON 配置。   amis前端低代码框架 更新日志: v1.1.7 Feature Wrapper 组件 style 支持动态获取 数据映射支持 cookie 获取 内置 filter 新增 map 方法 Rating 组件支持清空 Tabs 的 activeKey 支持变量 Excel 导出支持自定义文件名 数据映射的 key 可以支持 . 或者 [] 来指定路径 Tree-Selector 支持懒加载 升级 ECharts 到 5.1.1 升级 Monaco-Editor 到 0.24.0 Enhancement 升级 mst 到 3 的最新版本 开发使用 concurrently 避免新开一个窗口 data-link 优化 Wizard 组件新增 startStep 配置项 按钮 tooltip 整理,支持 disabledTip Each 组件空状态时文字居左,同时将空数组状态也认为是空状态 去掉 Tab line 模式下顶部的 padding Uuid 有值时不设置,没值自动设置 TextArea 组件最小行数限制 & 静态展示超出等 Form 远端校验显示报错时,可以再次提交 Nav 的 mapTree 需要 depthFirst Checkboxes 分组样式优化 DateTime-Range下拉增加 popoverClassName 属性,可以自定义弹框的 className; 父级有缩放比时弹框宽度计算问题修复; Date 快捷键支持上月底 autoFill 支持多选 CRUD 的 toolbar 默认不再将最后一个组件放右边 接口兼容多种 json 返回格式 CRUD filterable 判断是否选中不要那么严格 Button-Group disabled 统一使用透明度的方式来实现,不然无法区分选中状态是哪个 调整日期按钮位置顺序 和 Dialog 统一 Bugfix 修复 Audio should not call load method at first render 修复 文档多余描述 修复 CRUD filter Combo模式不能清空查询条件 修复 初始状态 autoFill 不同步的问题 修复 文档样例错误 修复 Audio 组件 src 属性不符合预期的行为 修复 表单联合校验问题 修复 PopOver 宽度计算问题 修复 图片表单项 disabled 影响放大功能的问题 修复 Transfer selectTitle resultTitle 不生效的问题 修复 Tree 组件问题 修复 Fiule 组件错误提示样式问题 修复 Select 组件自定义菜单模式下无法全选问题 修复 Number 最大最小值校验问题 修复 sdk 中 dialog 里的编辑器弹窗被遮挡问题 修复
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李庆政370

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值