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