本文介绍开发不同功能组件的步骤。
有子组件的组件
此类组件的开发步骤和基础开发步骤中描述的过程相同,此外需要注意:
配置文件需要注意以下几个参数,需要改为true。 原子组件是false。
isContainer: true, canContain: true, canDropIn: true,
把
props.children
放到子组件的布局位置。和react的容器组件一样。
可以绑定数据源的组件
此类组件的开发步骤和基础开发步骤中的一样,此外还需要以下步骤:
增加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:加载的数据源是实体对象列表,列表是作为一个整体处理的,比如魔笔默认提供的图表类组件。
处理数据源prop。
isDataComponent、DataType不需要处理。
dataSource是配置的数据源,需要在render时,调用API加载具体的数据。
if (api.isPreview(props)) { // 运行时 // 加载数据 const passThrough = api.processDataSource(props, { aggregate: false, }); }
包装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。
配置分页逻辑。
根据需要可以通过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;