Skip to content
导航栏

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 属性可以使用模板语法。
js
// \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 组件已经注册进去了。

ts
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")这样就能拿到目标组件的实例了(前提是目标组件已经配置了namexxxName`)。

目前有两种应用场景:

  1. 组件内部查找

比如在 amis 事件处理中,就可以通过 event.context.scoped 来获取当前组件的 scoped 实例,然后通过这个实例来查找其他组件。

json
{
  "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
    }
  }
}
  1. 组件外部查找

在使用 sdk 集成的场景下,在使用embed方法后,会返回一个 amis 实例,实际上就是一个大的scoped,这个实例上有getComponentByNamegetComponentById方法。

js
let amisInstance = amisEmbed.embed('#root', app);

比如在这里使用 amisInstance 来获取组件实例:

js
targetComponent = amisInstance.getComponentById(props?.$schema?.componentId);

targetComponent = amisInstance.getComponentByName(
  props?.$schema?.componentName,
);