奥创前端使用介绍
如何使用
安装核心包
若要使用奥创开发页面,需安装奥创核心包,可用 yarn 或 npm 进行安装。
yarn add @aochuang/core || npm install @aochuang/core
渲染样例
第一步:由前后端同学共同商讨,分析如何划分组件,并得出相应页面协议数据,以如下协议数据为例:
一般协议数据由 5 部分组成,分别是 linkage,hierarchy,data,endpoint,reload。
hierarchy:描述了整个页面的组成结构
root 代表该页面的根组件,一个页面的渲染,需找到页面的根组件,再根据structure结构,进行渲染。若不存在根组件,页面将无法渲染;
structure 描述了组件间的结构关系,页面也就是根据这个结构进行依次渲染;例如该协议中,组件 Child_1 表示 Item_1 的自组件。
components 涵盖了页面存在的所有组件。
linkage:对于前端,比较关注的是 linkage 中 inputs 和 requests 属性,一个组件,如果属于 inputs 类型,则该组件能够通过调用 action 里的 onChange
方法,对协议组件上的 data 数据属性进行修改,但不会触发其他组件的变化;如果属于 requests 类型,则该组件不仅能调用 action 里的 onChange 方法,还会触发
ajax 请求,通常请求地址为 linkage 下的 url,并带上触发请求组件的 data,所有 inputs 下的 data 数据,以及 operator(触发 onchange 的组件的 uniqueId),hierarchy 和 linkage 下的所有数据。
// 页面变化触发的请求样例,(框架层会处理)
postRequest(url, {
operator: props.data.uniqueId, //触发 onchange 的组件的 uniqueId
data: JSON.stringify(submitData), //触发请求组件的 data和所有 inputs 下的 data 数据
linkage: JSON.stringify(currProtocolData.linkage),
hierarchy: JSON.stringify(currProtocolData.hierarchy),
})
后台将返回最新的协议数据,以此达到修改其他组件协议数据的功能,常用于当你想触发某个组件的改变,引起其他组件的变化的请况。
注意: 一个组件不能既是 inputs 类型,又是 requests 类型。
data:各个组件的标识数据,通常有些固定属性,$class 对应后台 java 类指向。uniquedId 表示该组件 data 的唯一标识,通常由 tag + id 构成,parentTag + parentId 构成父组件的唯一标识。
一般组件的业务数据属性都放在 prop 下。
endpoint:渲染端,表示在 pc 端渲染还是在移动端渲染。
reload:是否重新加载。为 true 时,调用 adjust 接口,后端返回的协议数据将会全部替换原来的协议数据,为 false 时,协议只替换后端返回的 data 数据,以及 linkage 部分。
// 协议数据
const protocolData = {
linkage: {
inputs: ["Child_1"],
requests: ["Item_1"],
url:''
},
hierarchy: {
root: "Item_1",
structure: {
Item_1: ["Child_1"],
},
components:[
"Item_1",
"Child_1"
]
},
data: {
Item_1: {
uniqueId: "Item_1",
$class: "Item_1",
id: '1',
tag: 'item'
parentTag:null,
parentId:null,
prop: {
name: "组件 A",
count: 111,
},
},
Child_1: {
uniqueId: "Child_1",
$class: "Child_1",
id: '1',
tag: 'child'
parentTag:'item',
parentId:'1',
prop: {
name: "组件 B",
},
},
},
endpoint: { type: 'PC' },
reload: false,
};
第二步: 将协议数据里的组件对应成前端 react 组件,可以根据自己的业务需求进行实现,并注册到奥创核心,交由奥创核心调度渲染。
如下,定义了组件 A,组件 B,并分别将其注册到奥创核心
import React, { memo, useEffect } from "react";
import { UIFunctionComponent } from "@aochuang/core";
function Aco(props: IAochuangComponent) {
return (
<div>
{props.data.prop.name || "未定义"}
{props.children} //当组件下还有子组件的情况下,必须在此进行声明
</div>
);
}
function Bco(props: IAochuangComponent) {
return (
<div>
{props.data.prop.name || "未定义"}
</div>);
}
UIFunctionComponent(Aco, "item"); // 将组件A注册到奥创核心,注意:组件注册名不能带有下划线
UIFunctionComponent(Bco, "child"); // 将组件B注册到奥创核心
export default memo(Aco);
如果你使用的是类组件,那么可以使用装饰器的方式进行注册。
import React, { memo, useEffect,Component } from "react";
import { UIComponent } from "@aochuang/core";
// 将组件A注册到奥创核心 注意:组件注册名不能带有下划线
@UIComponent("item")
class Aco extends Component<IAochuangComponent> {
render() {
const {data, children} = this.props;
const {name} = data.prop || {};
return (
<div>
{data.prop.name || "未定义"}
</div>
);
}
}
export default memo(Aco);
第三步:创建渲染入口组件,并将之前写好的页面组件加载进来,根据对应的协议数据,即可完成最基本的渲染。
import { useEffect, useState } from "react";
import { Starter } from "@aochuang/core";
import { render } from "react-dom";
import { cloneDeep, set } from "lodash";
// 引入第一步中的协议数据,真实应用中,协议数据应该是调用接口所得
import protocolData from './protocolData'
export default function Test() {
const [show, setShow] = useState(false);
useEffect(() => {
(async () => {
await import("./Aco"); // 引入并加载组件,需等组件全部加载完成,奥创组件方可正常渲染
setShow(true);
})();
}, []);
return (
<div>
// 奥创组件渲染
{show ? <Starter data={protocolData} /> : null}
</div>
);
}
render(<Test />, document.getElementById("root"));
核心接口
//奥创监听器接口定义
interface UltronListener {
add(name: string, fn: Function): () => void;
remove(name: string, indicator?: string): void;
emit(name: string, ...data: any);
events: Record<string, string>;
}
// 奥创action接口定义
interface IAochuangComponentAction {
onChange: (key: string | Record<string, any>, value: any) => void;
onSubmit: (url: string) => Promise<any>;
listener: UltronListener;
}
// 奥创linkage接口定义
interface ILinkage {
inputs: string[];
requests: string[];
url: string;
}
// 奥创hierarchy接口定义
interface IHierarchy {
root: string;
components: string[];
structure: Record<string, string[]>;
}
//
interface IEndPoint {}
// 奥创协议接口定义
interface IAochuangProtocol {
linkage: ILinkage;
hierarchy: IHierarchy;
endpoint: IEndPoint;
data: Record<string, IBizComponentData>;
reload?: boolean; //reload为true时,调用adjust接口会替换整个协议数据
}
// 奥创组件列表类型定义
type IBizComponents = string[];
// 奥创组件data接口定义
interface IBizComponentData<prop = any> {
$class: string; // 对应后台Java类
uniqueId: string; // 组件数据唯一id
id: string; // 实例id
tag: string; // 注册的组件类型 id + tag = uniqueId
parentTag?: string | null; // 父级组件类型
parentId?: string | null; // 父级实例id
prop?: prop; // 业务属性
}
// 奥创组件props接口定义
interface IAochuangComponent<T = IBizComponentData> {
action: IAochuangComponentAction;
data: T;
children?: any; // 子组件列表 string[]
childrenData: T[]; // 子组件数据列表
extraPropData?: any; // 额外扩展prop属性
}
常用方法
当常规的奥创主动调用渲染不能满足实际应用开发需求的情况下,奥创核心还提供以下几个常用的方法,来帮助开发者更好的完成组件的自定义渲染。
1.getChildren(uniqueId: string)
根据组件 uniqueId,获取子组件列表,例如当你需要获取特定子组件,并分别对其进行渲染时,可以根据组件的 uniqueId,获取子组件的 uniqueId 列表,进行进一步的处理。一般
会与 getData,getDom,renderItem 配合使用。
2.getData(uniqueId: string)
根据组件 uniqueId,获取组件 data 数据,当你只需要获取组件的数据属性,并不对组件进行实现的时候,使用该方法进行获取。
例如,现在有一个 table 组件,下面挂着 columnList 组件,columnList 下又是各个列属性组件,此时若我不对列属性组件进行单独实现,只需获取列属性组件里的相关数据即可。
具体代码参考如下:
const getColumns = () => {
const columns = flatten(
props.childrenData
.filter((dataItem: IBizComponentData) => dataItem.tag === 'columnList')
.map((i: any) => {
const children = renderInstance.getChildren(i.uniqueId);
return children.map((uniqueId: string) =>
renderInstance.getData(uniqueId)
);
})
);
return columns.map((i: IBizComponentData) => {
let key = [];
if (i.prop.key.indexOf('.') !== -1) {
key = i.prop.key.split('.');
} else {
key = i.prop.key;
}
return {
dataIndex: key,
title: i.prop.name,
};
});
};
3.renderItem(Comp: React.ComponentType,uniqueId: string,extraData?: any)
主动渲染奥创组件的方法,与 getDom 类似,区别在于传入的 Comp 组件未注册到奥创核心,通常用于 Tabs 与 TabPanel 这种强关联组件,TabPanel 无法单独注册到奥创核心。当 TabPanel 还存在子组件,
则可用到 renderItem 的写法,主动渲染未注册到奥创核心的 TabPanel 组件。示例如下:
import React, { useEffect } from 'react';
import { Tabs } from 'antd';
// @ts-ignore
import { UIFunctionComponent, renderInstance } from '@aochuang/core';
import Style from './index.less';
const TaskTabList = (props: IAochuangComponent) => {
const { currentSelectedTabId } = props.data.prop || {};
const tabOnChange = (activeKey: string) => {
props.action.onChange('currentSelectedTabId', activeKey);
};
useEffect(() => {
if (!currentSelectedTabId) {
console.log('发起请求', props.childrenData[0].prop.id);
props.action.onChange(
'currentSelectedTabId',
props.childrenData[0].prop.id
);
}
}, []);
return (
<Tabs
onChange={tabOnChange}
activeKey={currentSelectedTabId}
className={Style['tab-active']}
>
{props.childrenData.map((i: IBizComponentData, index: number) => (
<Tabs.TabPane key={i.prop.id} tab={i.prop.title}>
{renderInstance.renderItem(TaskTab, i.uniqueId)}
</Tabs.TabPane>
))}
</Tabs>
);
};
const TaskTab = (props: IAochuangComponent) => <>{props.children}</>;
UIFunctionComponent(TaskTabList, 'TaskTabList');
export default TaskTabList;
4.getDom(uniqueId: string, propData?: any)
可根据组件 uniqueId 主动渲染奥创组件,并且何为组件透传额外的 props 数据:propData。
还是举 table 组件为例,当我需要既需要列属性组件里的数据,有需要针对某列进行特殊展示时,如标签,进度条的展示,这时不得不对列属性组件进行实现,并通过组件
uniqueId 主动进行渲染,代码如下所示:
const getColumns = () => {
const columns = flatten(
props.childrenData
.filter((dataItem: IBizComponentData) => dataItem.tag === 'columnList')
.map((i: any) => {
const children = renderInstance.getChildren(i.uniqueId);
return children.map((uniqueId: string) =>
renderInstance.getData(uniqueId)
);
})
);
return columns.map((i: IBizComponentData) => {
let key = [];
if (i.prop.key.indexOf('.') !== -1) {
key = i.prop.key.split('.');
} else {
key = i.prop.key;
}
return {
dataIndex: key,
title: i.prop.name,
render: (text: any, record: any, index: number) =>
renderInstance.getDom(i.uniqueId, { text, record, index }),
};
});
};