amis源码 onEvent事件动作 和 Action行为按钮解析

Action行为按钮组件 (仅支持click事件)

Action行为按钮是针对click点击事件的一些处理。actionType指定action作用类型:ajax、link、url、drawer、dialog、confirm、cancel、prev、next、copy、close

Button的本质就是一个Action 行为按钮(是 `action` 的别名)。

amis配置:{

            "type": "button",

            "actionType": "clear",

            "label": "清空"

        }

Action(button)行为按钮动作执行源码 :

amis/src/renderers/Action.tsx行为按钮组件

添加onEvent事件动作后,onEvent事件动作将优先执行

@Renderer({ type: 'action' })
export class ActionRenderer extends React.Component<ActionRendererProps> {

  doAction(
    action: ActionObject,
    args: {
      value?: string | {[key: string]: string};
    }
  ) {
    const actionType = action?.actionType as any;
    if (actionType === 'click') {
      this.handleAction(actionType, action);
    }
  }


 async handleAction( e: React.MouseEvent<any> | string | void | null, action: any  ) {
       
   //...省略

     // 触发onEvent 事件动作执行
      const rendererEvent = await dispatchEvent(
        e as React.MouseEvent<any> | string,
        mergedData
      );

      // 阻止原有动作执行
      if (rendererEvent?.prevented) {
        return;
      }

      onAction(e, action, mergedData); //Action行为按钮动作执行(props.onAction是从父级继承来的,action.actionType若在父组件onAction中匹配了则执行并return,否则继续向上传递最终会调用RootRenderer.tsx的handleAction)

  }

  render() {
    const {env, disabled, btnDisabled, loading, ...rest} = this.props;

    return (
      <Action
        {...(rest as any)}
        env={env}
        onAction={this.handleAction}
        isCurrentUrl={this.isCurrentAction}
        tooltipContainer={rest.popOverContainer || env.getModalContainer}
      />
    );
  }
}

@Renderer({
  type: 'button'
})
export class ButtonRenderer extends ActionRenderer {}

Amis-core/src/RootRenderer.tsx(onAction方法的最顶层实现) :

handleAction封装的一些reload、url、dialog、ajax通用动作调用处理,并通过props传递(onAction: handleAction)下去。

export class RootRenderer extends React.Component<RootRendererProps> {
  handleAction(
    e: React.UIEvent<any> | void,
    action: ActionObject,
    ctx: object,
    throwErrors: boolean = false,
    delegate?: IScopedContext
  ): any {
    const {env, messages, onAction, mobileUI, render} = this.props;
const store = this.store;

    const scoped = delegate || (this.context as IScopedContext);
    if (action.actionType === 'reload') {  //...省略
    } else if (
      action.actionType === 'url' ||
      action.actionType === 'link' ||
      action.actionType === 'jump'
    ) {//...省略
    } else if (action.actionType === 'email') {//...省略
    } else if (action.actionType === 'dialog') {//...省略
    } else if (action.actionType === 'ajax') { //...省略

}

  render() {
    return (
      <>
        {
          render(pathPrefix!, schema, {    
            ...rest,
            topStore: this.store,  //topStore是顶级store(RootStore树)
            data: this.store.downStream,  
            context: store.context,
            onAction: this.handleAction //传递onAction 封装的reload、url、link、jump等动作
          }) as JSX.Element
        }
      </>
    );
  }
}

onEvent(配置事件动作 支持多种类型事件)

amis配置:   

 {
      type: 'button',
      onEvent: {
        click: {
          actions: [
            {
              actionType: 'toast',
              args: {
                msgType: 'info',
                msg: '派发点击事件'
              }
            }
          ]
        }

onEvent事件分发源码:

//dispatchEvent分发事件,触发onEvent事件动作执行。

dispatchEvent( e, createObject(data, { nativeEvent: e }));

amis-core/src/utils/renderer-event.ts:

rendererEventListeners是一个集合,维护着所有onEvent 事件动作。

import {ListenerAction, ListenerContext, runActions} from '../actions/Action';
// 触发事件
export async function dispatchEvent(
  e: string | React.MouseEvent<any>,
  renderer: React.Component<RendererProps>,
  scoped: IScopedContext,
  data: any,
  broadcast?: RendererEvent<any>
): Promise<RendererEvent<any> | void> {

  //......省略

  // 过滤&排序
  const listeners = rendererEventListeners
    .filter(
      (item: RendererEventListener) =>
        item.type === eventName &&
        (broadcast ? true : item.renderer === renderer)
    )
    .sort(
      (prev: RendererEventListener, next: RendererEventListener) =>
        next.weight - prev.weight
);

  let executedCount = 0;
  const checkExecuted = () => {
    executedCount++;
    if (executedCount === listeners.length) {
      unbindEvent?.(eventName);
    }
  };
//循环对每个listener 通过runActions方法(Action.ts)进行动作调用(多个动作是按顺序依此执行的)
  for (let listener of listeners) { 
    const {
      wait = 100,
      trailing = true,
      leading = false,
      maxWait = 10000
    } = listener?.debounce || {};
    if (listener?.debounce) {
      const debounced = debounce(
        async () => {
          await runActions(listener.actions, listener.renderer, rendererEvent);
          checkExecuted();
        },
        wait,
        {
          trailing,
          leading,
          maxWait
        }
      );
      rendererEventListeners.forEach(item => {
        // 找到事件队列中正在执行的事件加上标识,下次待执行队列就会把这个事件过滤掉
        if (
          item.renderer === listener.renderer &&
          listener.type === item.type
        ) {
          item.executing = true;
          item.debounceInstance = debounced;
        }
      });
      debounced();
    } else {
      await runActions(listener.actions, listener.renderer, rendererEvent);
      checkExecuted();
    }

    if (listener?.track) {
      const {id: trackId, name: trackName} = listener.track;
      renderer?.props?.env?.tracker({
        eventType: listener.type,
        eventData: {
          trackId,
          trackName
        }
      });
    }

    // 停止后续监听器执行
    if (rendererEvent.stoped) {
      break;
    }
  }
  return Promise.resolve(rendererEvent);
}

onEvent中actions执行源码 :

amis-core/src/actions

1.Action.ts是基类定义了RendererAction、ListenerAction等基础接口。

runAction方法进行动作的通用预处理并执行不同type的action Class的run方法触发动作。核心代码如下

  await actionInstrance.run(…..)

runActions循环执行runAction(通过event参数在动作间传递参数),核心代码如下:

  for (const actionConfig of actions) {

       await runAction(actionInstrance, actionConfig, renderer, event);

  }

所以若配置了多个动作,动作是按顺序依此执行的。

2.CmptAction、AjaxAction等是各种类型的Action动作run方法的具体实现,均implements RendererAction(重写run方法) & extends ListenerAction(继承公共属性和方法)

比如:

2-1.CmptAction.ts:

是对setValue、reload和组件专属动作(comp.doAction())等动作的run方法实现

async run(  action: ICmptAction, renderer: ListenerContext, event: RendererEvent<any>) {

      /** 根据唯一ID查找指定组件, 触发组件未指定id或未指定响应组件componentId,则使用触发组件响应 */
    const key = action.componentId || action.componentName;

    let component = key && renderer.props.$schema[action.componentId ? 'id' : 'name'] !== key //指定了目标组件id/name 且 当前渲染器renderer组件id/name不是目标组件id/name

        ? event.context.scoped?.[action.componentId ? 'getComponentById' : 'getComponentByName'](key)
        : renderer;
    const dataMergeMode = action.dataMergeMode || 'merge';

   if (action.actionType === 'setValue') {
      const beforeSetData = renderer?.props?.env?.beforeSetData;
      const path = action.args?.path;

     /** 如果args中携带path参数, 则认为是全局变量赋值, 否则认为是组件变量赋值 */
      if ( path && typeof path === 'string' && beforeSetData &&  typeof beforeSetData === 'function') {
        const res = await beforeSetData(renderer, action, event);
        if (res === false) { return;  }
      }
      if (component?.setData) {
        return component?.setData(action.args?.value,dataMergeMode === 'override', action.args?.index);
      } else {
        return component?.props.onChange?.(action.args?.value);
      }
}
    // 执行组件动作
    try {
      const result = await component?.doAction?.(
        action,
        event.data,
        true,
        action.args
      );

    //...省略

} catch(e) {   }

}

2-2.CustomAction.ts:

自定义动作内置的doAction参数 本质还是调用runActions进行onEvent的动作调用: 

    // 执行自定义编排脚本
    let scriptFunc = action.args?.script ?? action.script;
    if (typeof scriptFunc === 'string') {
      scriptFunc = str2AsyncFunction(
        scriptFunc,
        'context',
        'doAction',
        'event'
      ) as any;
    }

    // 外部可以直接调用doAction来完成动作调用
    // 可以通过上下文直接编排动作调用,通过event来进行动作干预
    let result = await (scriptFunc as any)?.call(
      null,
      renderer,
      (action: ListenerAction) => runActions(action, renderer, event),
      event,
      action
    );

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 里的编辑器弹窗被遮挡问题 修复
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

李庆政370

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

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

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

打赏作者

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

抵扣说明:

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

余额充值