开发不同功能的组件

本文介绍开发不同功能组件的步骤。

有子组件的组件

此类组件的开发步骤和基础开发步骤中描述的过程相同,此外需要注意:

  1. 配置文件需要注意以下几个参数,需要改为true。 原子组件是false。

      isContainer: true,
      canContain: true,
      canDropIn: true,
  2. props.children放到子组件的布局位置。和react的容器组件一样。

可以绑定数据源的组件

此类组件的开发步骤和基础开发步骤中的一样,此外还需要以下步骤:

  1. 增加prop配置。

    此类组件必须有3个prop配置,分别是dataSource、isDataComponent、DataType,如下所示:

    [{
              // dataSource 属性配置,用于设置数据源
              name: 'dataSource',
              title: '绑定类型',
              setter: {
                type: 'data-table',
                props: {
                  materialProp: 'dataSource',
                  options: [
                  ],
                  changeClear: true,
                }
              },
            },
            {
              // 指定 组件是否可以绑定数据源,注意display是none,所以直接取默认值
              name: 'isDataComponent',
              title: '是否可以绑定数据源',
              display: 'none',
              defaultValue: true,
              setter: {
                type: 'bool',
                props:{
                  
                },
              },
            },
            {
              // 绑定的数据源对应数据的类型, 有 Data List List-Data 三种
              name: 'DataType',
              title: '数据类型',
              display: 'none',
              defaultValue: 'List',
              setter: {
                type: 'text',
                props: {},
              },
            }]
    • dataSource和isDataComponent是固定写法,无需修改。

    • DataType是为了区分数据类型的,分为三种类型:Data、List、List-Data,需要根据实际情况在defaultValue配置。

    说明

    这三种类型的含义如下:

    • Data:加载的数据源是实体对象,一般只有单个child,比如魔笔默认提供的Data数据容器。

    • List:加载的数据源是实体对象列表,一般有多个child,每个child绑定一个实体对象,比如魔笔默认提供的List数据容器。

    • List-Data:加载的数据源是实体对象列表,列表是作为一个整体处理的,比如魔笔默认提供的图表类组件。

  2. 处理数据源prop。

    • isDataComponent、DataType不需要处理。

    • dataSource是配置的数据源,需要在render时,调用API加载具体的数据。

        if (api.isPreview(props)) {
          // 运行时
          // 加载数据
          const passThrough = api.processDataSource(props, {
            aggregate: false,
          });
        }
  3. 包装children。

    为了让children组件能够获取绑定的实体对象,需要包装children组件。

    对于Data类型的组件,需要把processDataSource返回的内容,透传给每个child。代码如下:

        if (api.isPreview(props)) {
          // 运行时
          // 加载数据
          const passThrough = api.processDataSource(props, {
            aggregate: false,
          });
          return (
            <div>
              {React.Children.map(this.props.children, (child) => {
                return React.cloneElement(child, {
                  __passThrough: passThrough,
                });
              })}
            </div>
          );
        }

    对于List类型的组件,一般会有一个ListItem组件,首先需要包装所有children。代码如下:

        if (api.isPreview(props)) {
          // 运行时
          // 加载数据
          const passThrough = api.processDataSource(props, {
            aggregate: false,
          });
          // 包装children
          const items = api.createListItemContent(
            props,
            {
              // Item 就是容器的Item组件
              ItemComponent: Item,
              name: "myListItem",
            },
            props.children,
            { ...passThrough }
          );
          return items;
        }

    就是需要在调用完processDataSource方法后,再调用createListItemContent方法包装children,返回的items就可以作为子组件放到布局中直接渲染了。

    同时ListItem组件内部还需要处理。代码如下:

      if (api.isPreview(props)) {
        const __passThrough = api.processDataSource(props, {
          aggregate: true,
        });
    
        return (
          <div>
            {React.Children.map(props.children, (child) => {
              return React.cloneElement(child, {
                __passThrough,
              });
            })}
          </div>
        );
      }

    即ListItem组件中还需要调用一遍processDataSource方法,注意aggregate参数为true,同时把返回的__passThrough透传给子组件。

    对于List-Data组件,一般没有children,即在页面搭建时没有设置子组件,而是在代码中自己根据数据创建内部UI。

    这种情况,可以通过processDataSource先加载数据,然后通过getListItems api获取实体对象列表,结合getDcValue等API获取实体对象的字段值创建UI。

  4. 配置分页逻辑。

    根据需要可以通过updateDataFilter等API配置分页参数和updateData API刷新数据。

    可以绑定数据源组件的示例可以参考模板Sample/data-container下的示例。

可以绑定数据源组件的代码示例

  • 配置文件:

    /**
     * 示例组件的配置,用于演示如何配置组件
     */
    export default {
      version: '1.0',
      isContainer: true,
      title: 'XXXAlias',
      componentName: 'XXXComponent',
      // 高级 | 其他
      category: '基础',
      icon: 'https://example.svg',
      docUrl: 'http://gitlab.example.md',
      configure: [
        {
          title: '样式',
          type: 'group',
          display: 'block',
          items: [
            {
              // width 属性配置
              name: 'width',
              title: '宽度',
              defaultValue: {
                value: 100,
                unit: '%',
              },
              setter: {
                type: 'new-size-setter',
                props: {},
              },
            },
            {
              // height 属性配置
              name: 'height',
              title: '高度',
              defaultValue: {
                value: 40,
                unit: 'px',
              },
              setter: {
                type: 'new-size-setter',
                props: {
                  viewPort: true,
                },
              },
            },
            {
              // margin 属性配置
              name: 'margin',
              title: '外间距',
              defaultValue: '0px 0px 0px 0px',
              setter: {
                type: 'four',
                props: {},
              },
            },
            {
              // padding属性配置
              name: 'padding',
              title: '内间距',
              defaultValue: '0px 0px 0px 0px',
              setter: {
                type: 'four',
                props: {},
              },
            },
          ],
        },
        {
          title: '数据',
          type: 'group',
          display: 'block',
          items: [
            {
              // dataSource 属性配置,用于设置数据源
              name: 'dataSource',
              title: '绑定类型',
              setter: {
                type: 'data-table',
                props: {
                  materialProp: 'dataSource',
                  options: [],
                  changeClear: true,
                },
              },
            },
            {
              // 指定 组件是否可以绑定数据源,注意display是none,所以直接取默认值
              name: 'isDataComponent',
              title: '是否可以绑定数据源',
              display: 'none',
              defaultValue: true,
              setter: {
                type: 'bool',
                props: {},
              },
            },
            {
              // 指定 数据类型, 有 Data List List-Data 三种
              name: 'DataType',
              title: '数据类型',
              display: 'none',
              defaultValue: 'List',
              setter: {
                type: 'text',
                props: {},
              },
            },
            {
              // sortField 属性配置, 按哪个字段排序
              name: 'sortField',
              title: '选择排序字段',
              setter: {
                type: 'data-field',
                props: {
                  supportFieldType: ['text', 'char', 'integer', 'long', 'decimal'],
                },
              },
            },
            {
              // searchField 属性配置, 按哪个字段搜索
              name: 'searchField',
              title: '选择搜索字段',
              setter: {
                type: 'data-field',
                props: {
                  supportFieldType: ['text', 'char', 'integer', 'long', 'decimal'],
                },
              },
            },
          ],
        },
        {
          title: '事件',
          type: 'group',
          display: 'block',
          items: [
            {
              // action 属性配置,用于响应一些事件,比如按钮点击
              name: 'action',
              title: '点击Item触发动作',
              defaultValue: 'none',
              setter: {
                type: 'buttonTab',
                props: {},
              },
            },
          ],
        },
      ],
    };
    
  • 组件代码:

    import React, { useEffect } from 'react';
    import { observer } from 'mobx-react';
    import { useApi } from '../../../api/createApi';
    import config from '../config';
    import { Search, Button, Pagination } from '@alifd/next';
    import MobiComponentApi from '../../../api/mobiComponentApi';
    
    const Item = observer((props: any) => {
      const api = useApi(props);
      console.log('func list item render ', props);
      if (api.isPreview(props)) {
        const __passThrough = api.processDataSource(props, {
          aggregate: true,
        });
    
        return (
          <div
            onClick={() => {
              api.emitDcEvent(props, { key: 'action' });
            }}
          >
            {React.Children.map(props.children, (child) => {
              return React.cloneElement(child, {
                __passThrough,
              });
            })}
          </div>
        );
      } else {
        return <div>{props.children}</div>;
      }
    });
    
    const pageSize = 4;
    const defaultPage = 1;
    
    // 在这个demo里,我们简单模拟一个横向的列表
    const List = observer((props: any) => {
      const api = useApi(props);
      console.log('func list render ', props);
      useEffect(() => {
        api.updateDataFilter({
          page: {
            number: defaultPage,
            size: pageSize,
          },
        });
        api.updateData(props, { reload: true });
      }, []);
    
      const {
        width = {},
        height = {},
        margin,
        padding,
        dataSource,
        action,
        className,
        sortField,
        searchField,
        ...other
      } = props;
      // 根据 width height margin padding 拼装 style
      const style = {
        width: api.getWidth(width, margin),
        height: api.getHeight(height),
        margin: margin,
        padding: padding,
      };
    
      function getChildrenItem(api: MobiComponentApi, props: any) {
        if (api.isPreview(props)) {
          // 运行时
          // 加载数据
          const passThrough = api.processDataSource(props, {
            aggregate: false,
          });
          // 包装children
          const items = api.createListItemContent(
            props,
            {
              ItemComponent: Item,
              name: 'myListItem',
            },
            props.children,
            { ...passThrough, action },
          );
          return items;
        } else {
          // 设计时, 我们暂时只展示一个
          return <Item>{props.children}</Item>;
        }
      }
    
      const childrenItems = getChildrenItem(api, props);
      const layoutStyle = {
        display: 'flex',
        flexDirection: 'row' as 'row',
        overflowX: 'scroll' as 'scroll',
      };
      const searchStyle = { width: '200px' };
      return (
        <div style={style}>
          <div style={layoutStyle}>{childrenItems}</div>
          <div>
            <Search
              key="2"
              shape="simple"
              onSearch={(value) => {
                api.updateDataFilter({
                  filter: {
                    type: 'AND',
                    groups: [
                      {
                        type: 'AND',
                        filters: [
                          {
                            type: 'CONTAINS',
                            field: searchField,
                            values: [value],
                          },
                        ],
                      },
                    ],
                  },
                });
                api.updateData(props, { reload: true });
              }}
              style={searchStyle}
            />
            <Button
              onClick={() => {
                api.updateDataFilter({
                  sort: [
                    {
                      type: 'ASC',
                      field: sortField,
                    },
                  ],
                });
                api.updateData(props, { reload: false });
              }}
            >
              升序
            </Button>
            <Button
              onClick={() => {
                api.updateDataFilter({
                  sort: [
                    {
                      type: 'DESC',
                      field: sortField,
                    },
                  ],
                });
                api.updateData(props, { reload: false });
              }}
            >
              降序
            </Button>
            <Pagination
              type="simple"
              defaultCurrent={defaultPage}
              pageSize={pageSize}
              total={api.getListTotalCount(props)}
              onChange={(value) => {
                api.updateDataFilter({
                  page: {
                    number: value,
                    size: pageSize,
                  },
                });
                api.updateData(props, { reload: false });
              }}
            />
          </div>
        </div>
      );
    });
    
    List.config = config;
    
    export default List;