文档

7. 数据加载

更新时间:
重要

本文中含有需要您注意的重要提示信息,忽略该信息可能对您的业务造成影响,请务必仔细阅读。

本文介绍自定义组件如何加载数据。

系统组件的数据加载能力

让我们先看下数据容器组件 Data、List 的数据加载能力。

首先,我们模拟一个简单的页面,展示数据加载的效果。

数据模型:创建3个实体模型,实体1、实体2、实体3。

image.png

页面模型:使用 Data、List 及其它组件搭建一个简单的页面。

image.png

层级关系:最外层是 Data 组件,中间是 List 组件,最里层又是 Data 组件。

最外层 Data 组件是上下文数据源,加载类型为实体1的实体对象。(Data 组件绑定上下文数据源,若没有上下文对象传递,会自动创建该实体对象。)

中间 List 组件是数据库数据源,加载类型为实体2的实体对象列表。

最里层的 Data 组件是逻辑流数据源,加载类型为实体3的实体对象。实体对象由逻辑流创建,并返回。

image.png

其中每个数据容器都有文本组件和输入框组件,用来展示和修改各实体的 name 字段,最里层的 Data 组件中有一个按钮,用于点击触发逻辑流,查看逻辑流参数。

image.png

现在预览应用。在预览成功后,在管理后台创建两个实体2的数据,name 字段分别是 2aa 、2bb(中间 List 组件加载数据使用)。

运行应用,查看效果。

image.png

可以看到每个数据容器组件加载数据成功,文本和输入框组件都展示的是对应实体的 name 字段,输入框修改,文本组件也可以正确更新,点击按钮,显示的消息也正常的展示各实体的 name 字段。

image.png

数据容器

通过上面系统组件的例子,我们可以看到 Data、List 组件的数据加载能力,即数据容器的能力。

简单理解,数据容器就是配置有数据源属性,并在运行时会加载对应实体数据的组件。

其子组件可以展示和修改数据容器加载实体的字段数据。

数据上下文

在上面的例子中,通过数据容器的嵌套,我们形成了一个数据链:实体对象(实体1)<- 实体对象(实体2)<- 实体对象(实体3),我们称之为数据上下文。

数据上下文中的实体类型和顺序由数据容器的数据源和数据容器的嵌套层次顺序决定。

数据上下文的用途在于判断一些数据的可见范围和一些场景的参数顺序。

逻辑流数据源

我们再来看最里层 Data 组件数据源的逻辑流搭建。

image.png

这里有两个参数,一个是实体2类型的实体对象,一个是实体1类型的实体对象,这就是数据上下文决定的。

因为最里层 Data 还没有加载,所以没有实体3类型的实体对象作为参数,顺序是从里到外,所以是先实体2类型再实体1类型。

说明

数据容器配置逻辑流数据源时创建的逻辑流,会根据数据上下文,自动创建匹配的逻辑流参数。

如果是之前创建好的逻辑流,可以手动修改参数,只要参数顺序和类型匹配即可。

字段选择

让我们看下中间 List 组件中的输入框组件数据的配置。

image.png

只能选择实体1和实体2,这里也是数据上下文决定的。组件能够使用的数据是从组件本身开始向外的数据容器所加载的数据,所以看不到实体3。

事件参数

再让我们查看最里层 Data 组件内的按钮,事件配置的逻辑流如下:

image.png

和逻辑流数据源的参数类似,这里因为最里层 Data 组件已经加载了,所以参数是实体3类型的实体对象、实体2类型的实体对象、实体1类型的实体对象。

数据容器的类型

上面的例子中,我们说数据上下文是实体对象(实体1)<- 实体对象(实体2)<- 实体对象(实体3),但是 List 组件加载的是实体对象列表,理论上真实的数据链关系应该是实体对象(实体1)<- 实体对象列表(实体2)<- 实体对象(实体3)。

差别是数据容器的类型决定的,平台目前定义了3种数据容器类型:Data、List、List-Data。

  1. Data:表示加载的是一个实体对象,数据上下文中也是一个实体对象。

  1. List:表示加载的是实体对象列表,数据上下文是实体对象列表中的一个实体对象。以 List 组件为例,加载数据后,是每个实体对象,都渲染了子组件,所以对于子组件来说,只对应了实体对象列表的一个。因此,我们定义数据上下文中是一个实体对象,而不是实体对象列表。

  1. List-Data: 表示加载的是实体数据列表,数据上下文也是实体数据列表。 这种类型的组件一般不是容器组件,比如数据选择框组件。

后面在实现组件的数据加载能力时,我们会看到数据容器类型的配置。

添加数据加载能力

以上在了解了数据加载能力的效果后,让我们以实现一个简单的 List 组件为例,演示如何给组件添加数据加载能力。

  1. 在 src/config.ts 中添加属性配置,修改 isContainer 字段。

    import { API_VERSION, Category } from './api';
    
    export default {
      apiVersion: API_VERSION,
      version: '1.0',
      isContainer: true,
      title: '',
      componentName: 'MobiPcComponentABC',
      category: Category.COMMON,
      icon: 'https://img.alicdn.com/imgextra/i1/O1CN01oufFHB266LbYRkVr3_!!6000000007612-55-tps-20-20.svg',
      configure: [
        {
          title: '数据',
          type: 'group',
          display: 'block',
          items: [
            {
              name: 'dataSource',
              title: '数据源',
              setter: {
                type: 'data-table',
                props: {},
              },
            },
            {
              name: 'DataType',
              title: '数据容器类型',
              display: 'none',
              defaultValue: 'List',
              setter: {
                type: 'text',
                props: {},
              },
            },
            {
              name: 'isDataComponent',
              title: '是否为数据容器',
              display: 'none',
              defaultValue: true,
              setter: {
                type: 'bool',
                props: {},
              },
            },
          ],
        },
      ],
    };
    

    首先修改 isContainer 字段,使组件成为容器组件。之后添加3个属性,让组件拥有数据源配置:

    1. dataSource:使用 data-table,配置组件要加载的数据。

    2. DataType:数据容器的类型。注意这里的 display 是 none,defaultValue 是 List,所以这个一个固定的属性,只能由组件开发者指定。

    3. isDataComponent:是否是数据容器,同样也是一个固定的属性。

      重要

      dataSource DataType isDataComponent 这三个属性的name是固定的,不能修改。因为这些属性是需要平台读取的。

  2. 在组件中添加相应的处理代码。

    import React, { PropsWithChildren } from 'react';
    import api, {
      EntityObject,
      PropsWithPageContext
    } from './api';
    import config from './config';
    import './index.scss';
    
    interface Props {}
    
    const MobiPcComponentABC = (props: PropsWithPageContext<PropsWithChildren<Props>>) => {
      const { pageContext, children } = props;
    
      let content;
    
      if (api.isPreview()) {
        pageContext.getDataSource().processDataSource();
        const entityObjects = pageContext.getData<EntityObject[]>() || [];
        content = entityObjects.map((entityObject) => {
          return (
            <div key={entityObject.id}>
              {pageContext.changeChildContextData(children, entityObject)}
            </div>
          );
        });
      } else {
        content = <div>{children}</div>;
      }
      return <div className="MobiPcComponentABC">{content}</div>;
    };
    
    MobiPcComponentABC.config = config;
    
    export default MobiPcComponentABC;
    1. 类似 List 组件,这里我们根据实体对象个数渲染 children,而设计时没有数据,所以首先区分设计时和运行时。

    2. 设计时直接渲染 children。

    3. 运行时调用 DataSourceprocessDataSource API,加载数据。

    4. 调用 PageContextgetData API,获取加载的实体对象列表。

      警告

      注意数据源没有加载完成时,可能返回 undefined

    5. 然后根据实体对象列表的个数渲染 children。

    6. 调用 PageContextchangeChildContextDataAPI 把对应的实体对象传递给 children。

      重要

      数据容器的类型中,我们提到不同类型的数据容器,产生的数据上下文效果不一样,

      这种效果,并不是由平台独立实现的,需要数据容器组件配合,比如List类型的数据容器,就需要调用changeChildContextDataAPI 分配给 children 不同的实体对象。

    7. 至此,基础的数据加载能力添加完毕。

  3. 看一下设计时加载的效果,配置一个数据源,并添加子组件。image.png

  4. 在运行时查看效果,符合预期。image.png

实现思路总结

基于上面的修改,总结添加数据加载能力的核心步骤如下:

  1. 配置 dataSource DataType isDataComponent 三个属性。

  2. 在组件中调用 DataSourceprocessDataSource API,加载数据。

  3. 在组件中调用 PageContextgetData API,获取数据,并使用。

至此组件已经可以正常加载数据,获取的数据类型是 EntityObject,表示一个实体对象,如果想要进一步读取字段的数据,可以参考5. 数据展示和修改,添加字段的属性配置,通过 EntityObjectgetset 方法读取或者更新字段数据。 示例可以参考数据选择框组件模板

如果是容器组件,就需要考虑子组件的数据上下文。

当前大部分场景都是类似List组件每个实体对象渲染一组 children,就需要调用 PageContextchangeChildContextDataAPI 把对应的实体对象传递给 children。 示例可以参考List组件模板

如何确定数据容器类型

目前数据容器类型有 Data、List、List-Data 三种类型。他们有不同的使用场景。

首先我们需要确认组件是否是容器组件,其次是组件预期加载的实体对象是一个还是一组,还有预期产生的数据上下文是什么类型。

是否是

容器组件

加载的

数据个数

预期

上下文

推荐的

数据容器类型

说明

一个

一个

Data

当前只有 Data 类型是加载一个实体对象。

一组

一组

List-Data

这种类型的组件相当于把一组实体对象当成一个变量来使用,所以使用 List-Data 类型。

一个

一个

Data

当前只有 Data 类型是加载一个实体对象。

这种类型的组件和 Data 系统组件几乎一样,建议直接使用 Data 系统组件。

一组

一个

List

类似 List 组件,组件本身并不读取实体对象的字段,而是仅根据数据数量渲染 children,并控制 children 的布局

重要

表格中的排列组合并不全面,主要原因是其它场景几乎不存在。所以目前为止我们仅定义了Data、List、List-Data 三种类型以应对目前的场景。

如何实现数据上下文

在确定数据容器的类型后,数据上下文应该是什么样就已经确定了,但是需要组件配合才能实现。

如果不是容器组件,数据容器的类型仅有 Data、List-Data 两种,不需要特别处理。

如果是容器组件,数据容器的类型仅有 Data、List 两种。需要调用 PageContextchangeChildContextDataAPI。

默认情况下,组件会继承父级组件的数据上下文。而数据容器加载了新的数据,形成了新的数据上下文,就需要修改 children 的数据上下文。

假设组件的嵌套层次和数据上下文关系如下:

image

PageContextchangeChildContextDataAPI 的效果是修改 children 的数据上下文,如果没有 data参数,就是把当前数据源加载后的数据上下文传递给 children。

如果有 data 参数,就是以 data 参数,形成新的数据上下文,传递给 children。

针对 Data 类型,需要配合实现的代码就是调用 PageContextchangeChildContextDataAPI,不传递 data 参数。

警告

特别注意这里是不传递 data 参数。

import React, { PropsWithChildren } from 'react';
import api, { PropsWithPageContext } from './api';
import config from './config';
import './index.scss';

interface Props {}

const MobiPcComponentABC = (props: PropsWithPageContext<PropsWithChildren<Props>>) => {
  const { pageContext, children } = props;

  let content;

  if (api.isPreview()) {
    pageContext.getDataSource().processDataSource();
    // 注意不传递data参数,目的在于修改children的上下文为当前加载的实体对象
    content = <div>{pageContext.changeChildContextData(children)}</div>;
  } else {
    content = <div>{children}</div>;
  }
  return <div className="MobiPcComponentABC">{content}</div>;
};

MobiPcComponentABC.config = config;

export default MobiPcComponentABC;

针对 List 类型需要配合实现的代码就是调用 PageContextchangeChildContextDataAPI,data 参数为要作为数据上下文的实体对象。

import React, { PropsWithChildren } from 'react';
import api, {
  EntityObject,
  PropsWithPageContext
} from './api';
import config from './config';
import './index.scss';

interface Props {}

const MobiPcComponentABC = (props: PropsWithPageContext<PropsWithChildren<Props>>) => {
  const { pageContext, children } = props;

  let content;

  if (api.isPreview()) {
    pageContext.getDataSource().processDataSource();
    const entityObjects = pageContext.getData<EntityObject[]>() || [];
    content = entityObjects.map((entityObject) => {
      // 传递实体对象给children作为新的数据上下文
      return (
        <div key={entityObject.id}>
          {pageContext.changeChildContextData(children, entityObject)}
        </div>
      );
    });
  } else {
    content = <div>{children}</div>;
  }
  return <div className="MobiPcComponentABC">{content}</div>;
};

MobiPcComponentABC.config = config;

export default MobiPcComponentABC;

数据过滤、排序、分页

平台提供 API,可以对要加载的数据,进行过滤、排序和分页操作。

DataSourceconfigDataFilter API 用于配置过滤、排序、分页条件。

DataSourcerefreshData API 用于重新请求数据。

重新请求数据之后,后续再调用 PageContextgetData API,获取的数据也是过滤、排序和分页之后的结果。

对于上下文数据源,过滤、排序和分页操作都是前端操作。

对于数据库数据源,过滤、排序和分页操作都是后端操作。

对于逻辑流数据源,通过DataSourceenableCustomSearchenableCustomPaginationAPI,可以开启自定义逻辑流搜索和分页。默认关闭,都是前端操作。如果开启会有额外的参数传递给逻辑流,方便逻辑流内实现搜索和分页逻辑。

是否提供自定义逻辑流搜索和分页功能,由组件自己决定。如果提供,需要组件增加属性配置,然后根据属性值,调用 DataSourceenableCustomSearchenableCustomPaginationAPI。

数据加载监听

因为数据加载有耗时,有一些场景,组件需要展示 loading 状态。

DataSourceprocessDataSourcerefreshData 都提供 onLoadingStart、onLoadingFinish 两个loading 状态回调参数,组件可以通过回调监听 loading 状态,实现 loading 效果。

另外数据源属性 DataSourceMeta 中有 enableLoading 字段表示用户是否开启 loading,组件可以结合此字段控制 loading 效果是否展示,以实现平台用户对 loading 效果的控制。

还有场景,组件需要监听数据源的变化,比如数据选择框组件的初始化,需要等数据加载完成或者每次数据变化时。

DataSourceregisterListener 可以注册监听回调,在每次数据变化时通知组件。

使用示例可以参考数据选择框组件模板

重要

DataSourceregisterListener 注册的回调,需要是多次执行无副作用的。原因在于会有场景数据变化了,但是数据内容其实没有变化。

接下来

掌握数据加载能力之后,让我们看下组件的错误检查能力。

  • 本页导读
文档反馈