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