Amis 内部组件查找逻辑
在 amis 的使用过程中,有很多场景需要组件间通信。有多种方式,共享变量,比如在表单中,表单项可以共享引用一个上级变量。action 事件中也可以通过组件的 target
来指定目标组件。还有一些场景中,需要在代码中动态获取组件的引用,比如在表单提交时,需要拿到表单的值或是设置表单的值。
为了了解原理,最好的方法是阅读源码。
组件查找逻辑
当在动作组件 action
里配置 target
时,会调用方法getComponentByName
, 而在这个方法的内部其实会查找组件名名称或是组件的 id
,单看方法名会有误解。
比如在测试示例里,配置了 target
:组件 ID 也能找到对应的组件。
https://aisuda.bce.baidu.com/amis/zh-CN/components/form/formula#手动应用
- 使用
getComponentByName
时,组件的 name 属性可以使用模板语法。 - 使用
getComponentById
时,组件的 id 属性可以使用模板语法。
// \amis\packages\amis-core\src\Scoped.tsx
getComponentByName(name: string) {
if (~name.indexOf('.')) {
const paths = name.split('.');
const len = paths.length;
return paths.reduce((scope, name, idx) => {
if (scope && scope.getComponentByName) {
const result: ScopedComponentType = scope.getComponentByName(name);
return result && idx < len - 1 ? result.context : result;
}
return null;
}, this);
}
const resolved = find(
components,
component =>
//组件的name属性可以使用模板语法。
filter(component.props.name, component.props.data) === name ||
component.props.id === name
);
return resolved || (parent && parent.getComponentByName(name));
},
getComponentById(id: string) {
let root: AliasIScopedContext = this;
// 找到顶端scoped
while (root.parent && root.parent !== rootScopedContext) {
root = root.parent;
}
// 向下查找
let component = undefined;
findTree([root], (item: TreeItem) =>
item.getComponents().find((cmpt: ScopedComponentType) => {
//组件的id属性可以使用模板语法。
if (filter(cmpt.props.id, cmpt.props.data) === id) {
component = cmpt;
return true;
}
return false;
})
) as ScopedComponentType | undefined;
return component;
}
深入理解
这里涉及到组件间通信,amis 中有个机制就是,把需要被引用的组件设置一个 name 值,然后其他组件就可以通过这个 name 与其通信,比如这个例子。其实内部是依赖于内部的一个 Scoped Context。你的组件希望可以被别的组件引用,你需要把自己注册进去,默认自定义的非表单类组件并没有把自己注册进去,内部表单组件和一些内置的 amis 组件已经注册进去了。
import * as React from 'react';
import { Renderer, ScopedContext } from 'amis';
@Renderer({
type: 'my-renderer',
})
export class CustomRenderer extends React.Component {
static contextType = ScopedContext;
constructor() {
const scoped = this.context;
scoped.registerComponent(this);
}
componentWillUnmount() {
const scoped = this.context;
scoped.unRegisterComponent(this);
}
// 其他部分省略了。
}
当组件注册后,其他组件就能引用到了。同时,如果你想找别的组件,也同样是通过 scoped
这个 context,如:
scoped.getComponentByName("xxxName")这样就能拿到目标组件的实例了(前提是目标组件已经配置了
name为
xxxName`)。
目前有两种应用场景:
- 组件内部查找
比如在 amis 事件处理中,就可以通过 event.context.scoped 来获取当前组件的 scoped
实例,然后通过这个实例来查找其他组件。
{
"onEvent": {
"confirm": {
"actions": [
{
"actionType": "custom",
"script": "const page = event.context.scoped.getComponentById('model_columns').state?.page;\n const idx = (page ? page - 1 : 0) * event.context.scoped.getComponentById('model_columns').props?.perPage + event.data?.index;\n event.setData({...event.data,'__index':idx});\n"
},
{
"groupType": "component",
"actionType": "setValue",
"args": {
"value": {
"&": "$$"
},
"index": "${__index}"
},
"componentId": "model_columns"
}
],
"weight": 0
}
}
}
- 组件外部查找
在使用 sdk 集成的场景下,在使用embed
方法后,会返回一个 amis 实例,实际上就是一个大的scoped
,这个实例上有getComponentByName
和getComponentById
方法。
let amisInstance = amisEmbed.embed('#root', app);
比如在这里使用 amisInstance
来获取组件实例:
targetComponent = amisInstance.getComponentById(props?.$schema?.componentId);
targetComponent = amisInstance.getComponentByName(
props?.$schema?.componentName,
);