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

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

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

amis配置:{

"type": "button",

"actionType": "clear",

"label": "清空"

}

自定义组件使用Action(button)行为按钮:

onMount: (dom, data, onChange, props) => {
  const button = document.createElement('button');
  button.innerText = '点击修改姓名';
  button.onclick = event => {
    onChange('new name', 'name');

    props.onAction(  //详见amis/src/renderes/Action.tsx
      event,
      {
        type: 'action',
        label: '弹个框',
        actionType: 'dialog',
        dialog: {
          title: '弹框',
          body: 'Hello World!'
        }
      },
      {} // 这是 data
    );
    event.preventDefault();
  };
  dom.appendChild(button);
};

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

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

添加onEvent事件动作后,onEvent事件动作将先于旧版Action行为动作执行

@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是从RootRenderer.tsx中继承来的)

  }
}

Amis-core/src/RootRenderer.tsx( onAction方法的具体实现) :

handleAction封装的一些reload、url、dialog、ajax通用动作调用处理,并通过props分发(onAction : handleAction)下去。Action行为按钮组件会调用。

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等动作(Action行为按钮会使用此方法)
          }) 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 事件动作。

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

  runActions await runActions(listener.actions, listener.renderer, rendererEvent);

onEvent 中actions 执行源码 :

onEvent :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动作ru n方法的具体 实现,均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
    );
相关推荐
BPM_宏天低代码13 小时前
低代码 BPA:简化业务流程自动化的新趋势
运维·低代码·自动化
液态不合群4 天前
数智时代:以低代码开发为催化剂 加速中国制造转型升级
低代码·制造
BPM_宏天低代码4 天前
低代码用户中心:简化开发,提升效率的新时代
低代码
小麦项目管理指南4 天前
如何利用低代码开源框架实现高效开发?
大数据·低代码·开源·产品运营·产品经理
工业甲酰苯胺6 天前
加强科技平台企业赋能 加快发展新质生产力
低代码
ZKNOW甄知科技7 天前
低代码架构浅析
程序人生·低代码·架构·it
百数7 天前
百数功能更新——表单提交支持跳转到外部链接并支持传参
java·前端·低代码
BPM_宏天低代码7 天前
低代码信息中心:赋能创新的未来
低代码
canonical-entropy7 天前
Nop平台核心代码阅读导引
低代码·开源·可逆计算·nop平台
HUIBUR科技9 天前
AI驱动的低代码未来:加速应用开发的智能解决方案
低代码