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
    );
相关推荐
低代码布道师8 小时前
微搭低代码MBA培训管理系统14——线索跟进
低代码
IT研究所8 小时前
从工单到智能分析:AIGC运维助手应用价值
大数据·运维·数据库·人工智能·科技·低代码·自动化
液态不合群11 小时前
Java低代码平台工作流引擎设计与实现:从人工审批到智能自动化
java·低代码·状态模式·工作流
码上解惑12 小时前
基于 Spring AI Alibaba ReactAgent 辅助低代码表单设计的实现原理与实操步骤
人工智能·spring·低代码·ai
阴阳怪气乌托邦1 天前
请注意!AI低代码正在干掉传统开发
人工智能·低代码·工作流引擎
决斗小饼干1 天前
还在硬编码决策逻辑?JNPF决策流正在干掉大批“手工审批”
低代码·工作流引擎
麦聪聊数据1 天前
SQL2API 网关的透明缓存与请求合并机制
数据库·sql·低代码·微服务
JEECG低代码平台1 天前
JeecgBoot低代码平台作为 Qiankun 子应用接入指南
低代码
JEECG官方1 天前
JeecgBoot低代码 AI Skills 实战:自然语言驱动 BPM 流程自动生成
低代码
JEECG低代码平台1 天前
JeecgBoot低代码 AI Skills 实战:自然语言驱动 BPM 流程自动生成
人工智能·低代码