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
    );
相关推荐
Jeking21721 小时前
低代码平台表单设计器 unione form editor 组件 —— 富文本编辑器
低代码·动态表单·表单设计·表单引擎·unione cloud
多租户观察室1 天前
中小微企业适用低代码开发平台有哪些选型
低代码
数睿数据无代码开发2 天前
2026 无代码平台企业选型推荐
低代码·无代码
咬人喵喵2 天前
E2编辑器里的零高容器是什么?怎么用?
低代码·微信·编辑器·交互·svg
Jeking2172 天前
低代码平台表单设计器 unione form editor 布局组件 — 折叠面板
低代码·动态表单·表单设计·表单引擎·unione cloud
低代码行业资讯3 天前
五大实锤证据:AI不会终结低代码,只会倒逼技术进化
低代码·ai
Teable任意门互动3 天前
深度解析:AI 赋能开源多维表格,实现企业全场景数据整合与高效应用
数据库·人工智能·低代码·信息可视化·开源·数据库开发
JEECG低代码平台3 天前
JimuReport 积木报表 v2.3.4 版本发布,免费的可视化 AI 报表
人工智能·低代码·数据可视化·报表工具
踩着两条虫4 天前
AI 低代码引擎可视化设计器交互机制实战
前端·vue.js·人工智能·低代码·架构
低代码布道师4 天前
健身房私教管理系统 (三):巧妙利用分步表单,解耦 1+N 模型的双表连续写入
低代码