bi-designer 是阿里数据中台团队自研的前端搭建引擎,基于它开发了阿里内部最大的数据分析平台,以及阿里云上的 QuickBI。
bi-designer 目前没有开源,因此文中使用的私有 npm 源
@alife/bi-designer
是无法在公网访问的。
本文介绍 bi-designer 组件的使用 API。
组件加载
组件实例定义在元信息 - element 中:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
element: () => <div />,
};
异步加载
使用 React.lazy 即可实现异步加载组件:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
// 懒加载
element: React.lazy(async () => import("./real-component")),
};
懒加载的组件会自动完成加载,如需自定义加载 Loading 效果,可以阅读 组件异步、错误处理 文档。
组件异步、错误处理
组件源码异步加载或者进行 Suspense 取数时,会调用 ComponentMeta.suspenseFallback 渲染。
组件渲染出错时,会调用 ComponentMeta.errorFallback 渲染。
异步加载
import { Interfaces } from "@alife/bi-designer";
const SuspenseFallback: Interfaces.InnerComponentElement = ({
componentInstance,
componentmeta,
}) => {
return <span>Loading</span>;
};
const componentMeta = {
componentName: "suspense-custom-fallback",
element: React.lazy(async () => {
await sleep(2000);
return Promise.resolve({ default: () => null });
}),
suspenseFallback,
};
上面例子中,对异步加载的组件定义了 suspenseFallback 来处理异步中的状态。
错误处理
import { Interfaces } from "@alife/bi-designer";
const errorFallback: Interfaces.ErrorFallbackElement = ({
componentInstance,
componentmeta,
error,
}) => {
return <span>错误:{error.toString()}</span>;
};
const componentMeta = {
componentName: "error-custom-fallback",
element: () => {
throw new Error("error!");
},
errorFallback,
};
上面例子中, errorFallback 处理了组件抛出的任何错误。
error :当前组件报错信息。
容器组件
容器元素可以被拖入子元素,只要将 isContainer 设置为 true 即可:
export const yourComponentMeta: Interfaces.ComponentMeta = {
componentName: "yourComponent",
element: YourComponent,
isContainer: true,
};
之后可以从 props.children 访问到子元素:
const YourComponent = ({ children }) => {
return <div>{children}</div>;
};
多插槽容器组件
多插槽容器即一个容器内部有多个位置可响应拖拽。
实现多插槽容器组件注意两点即可:
这个大容器组件本身不为容器类型,因为我们要拖入到子元素,不需要拖入到它自己本身。
内部通过 ComponentLoader 添加容器类组件作为子元素。
比如我们要利用 Antd Card 实现一个多插槽容器,首先把 Card 申明为普通组件:
export const cardComponentMeta: Interfaces.ComponentMeta = {
componentName: "card",
element: CardComponent,
};
在实现 Card 功能时,我们在两处内部可拖拽区域调用 ComponentLoader 加载一个事先定义好的容器组件 div :
import { ComponentLoader, useDesigner } from '@alife/bi-designer'
const CardComponent: Interfaces.ComponentElement = () => {
const { useKeepComponentLoaders } = useDesigner()
useKeepComponentLoaders(['1'])
return (
<Card
actions={[...]}
>
<ComponentLoader
id="1"
componentName="div"
props={
{style: { minHeight: 30 }}}
/>
</Card>
);
};
总结一下,我们可以利用 ComponentLoader 在组件内部加载任意组件,如果加载的是容器组件,就相当于增加了一块内部插槽。这种插槽可以插入理论上无数种容器组件,根据业务需求而定,比如上面这种最简单的 div 容器,可以是这么实现的:
const Div: Interfaces.ComponentElement = ({ children, style }) => {
return (
<div style={
{ width: "100%", height: "100%", ...style }}>{children}</div>
);
};
Tabs 容器组件
Tabs 容器可以看作动态数量的多插槽容器:
import { ComponentLoader, useDesigner } from "@alife/bi-designer";
const TabsComponent: Interfaces.ComponentElement = ({ tabs }) => {
const { useKeepComponentLoaders } = useDesigner();
useKeepComponentLoaders(tabs?.map((each) => each.key));
return (
<div>
<Tabs>
{tabs?.map((each) => (
<Tabs.TabPane tab={`Tab${each.title}`} key={each.key}>
/* 举个例子,拿 div 这个组件作为 TabPane 的容器 */
<ComponentLoader id={each.key} componentName="div" />
</Tabs.TabPane>
))}
</Tabs>
</div>
);
};
Tabs 根据配置动态渲染 TabPane ,为每个 TabPane 塞入一个容器即可。
注意, useKeepComponentLoaders 函数可以让数据变化后某个子 Tab 消失时,及时做画布脏数据清除。另外即便数据不是动态的,也要及时更新这个函数,比如某次更新, ComponentLoader id 为 3 的值从代码移除了,也要把 3 这个 id 从 useKeepComponentLoaders 中移除。
组件宽高
对于能自适应高度的组件,最佳方案是设置 100% 的宽高:
import { Interfaces } from "@alife/bi-designer";
const CustomComponent: Interfaces.ComponentElement = () => {
return <div style={
{ width: "100%", height: "100%", minHeight: 50 }} />;
};
流式布局下 height: '100%' 高度会坍塌,因此可以设置个最小高度固定值兜底,或者通过 props 让用户配置。
如果组件不支持自适应宽高,比如渲染 canvas、svg 等图表时,需要自己监听宽高,或者利用 容器拓展组件 props 功能,在容器算好宽高具体值,再传入组件。
当然也可以直接设置一个默认高度,或者根据内容动态撑开组件,在流式布局、磁贴布局下可以自动撑开容器(磁贴布局编辑模式下拖拽的高度允许被运行时自动撑大),在自由布局下无法撑开,会出现内滚动条。
组件配置默认值
组件配置表单的默认值在 ComponentMeta.props 中定义:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
props: [
{
name: "title",
defaultValue: "标题",
},
],
};
Props 描述了组件入参信息,包括:
interface MetaProps {
/**
* 属性名
*/
name: string;
/**
* 属性类型
*/
type?: string;
/**
* 属性描述
*/
description?: string;
/**
* 默认值
*/
defaultValue?: any;
}
如果只设置默认值,只需要关心 name 和 defaultValue 。
组件配置表单
组件配置表单在 ComponentMeta.propsSchema 中定义:
import { Interfaces } from '@alife/bi-designer'
const componentMeta: Interfaces.ComponentMeta = {
platform: 'fbi', // 平台名称
propsSchema: {
style: {
color: {
title: 'Color',
type: 'color',
redirect: 'color',
},
},
},
}
platform :项目类型。不同项目类型的 propsSchema 结构可能不同,其他取数逻辑可能也不同。
propsSchema :表单配置结构,符合 UISchema 规范。对于特殊表单可能使用自己的规范。
组件配置修改回调
组件配置修改回调在每次组件实例信息被修改时触发,在 ComponentMeta.onPropsChange 中定义:
import { Interfaces } from "@alife/bi-designer";
const componentMeta: Interfaces.ComponentMeta = {
onPropsChange: ({ prevProps, currentProps, componentMeta