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

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

李庆政370

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

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

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

打赏作者

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

抵扣说明:

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

余额充值