git地址css
前段时间重构了下面这样一个页面(产品页面不方便截图):html
相似于拓扑图的配置,原来是使用go.js实现的,相似的库还有antv g6。重构主要是为了提升代码质量,下降维护成本,产品上须要更强的定制化能力(对付产品经理的变态需求),因此通过一番研究以后,最后决定放弃使用现成的库。缘由以下:node
固然,以上两个库仍是至关强大的,不过基于这些缘由,本身基于(DOM + SVG)撸一个拓扑图配置的工具库topology-byfereact
import React from 'react';
import { Topology, topologyWrapper, TemplateWrapper } from 'topology-byfe';
import { ITopologyNode, ITopologyData, IWrapperOptions } from 'topology-byfe/lib/declare';
import './index.less';
interface FlowState {
data: ITopologyData;
}
class Flow extends React.Component<{}, FlowState> {
state: FlowState = {
data: { lines: [], nodes: [] },
};
generatorNodeData = (isBig: boolean) => ({
id: `${Date.now()}`,
name: isBig ? '宽节点' : '窄节点',
content: isBig ? '这是一个宽节点' : '这是一个窄节点',
branches: isBig ? ['锚点1', '锚点2', '锚点3'] : ['锚点1'],
});
handleSelect = (data: ITopologyData) => {
console.log(data);
}
renderTreeNode = (data: ITopologyNode, { anchorDecorator }: IWrapperOptions) => {
const {
name = '',
content = '',
branches = [],
} = data;
return (
<div className="topology-node">
<div className="node-header">{name}</div>
<p className="node-content">{content}</p>
{branches.length > 0 && (
<div className="flow-node-branches-wrapper">
{branches.map(
(item: string, index: number) => anchorDecorator({
anchorId: `${index}`,
})(<div className="flow-node-branch">{item}</div>),
)}
</div>
)}
</div>
);
};
onChange = (data: ITopologyData, type: string) => {
this.setState({ data });
console.log('change type:', type);
};
render() {
const { data } = this.state;
return (
<div className="topology">
<div className="topology-templates">
<TemplateWrapper generator={() => this.generatorNodeData(true)}>
<div className="topology-templates-item">宽节点</div>
</TemplateWrapper>
<TemplateWrapper generator={() => this.generatorNodeData(false)}>
<div className="topology-templates-item">窄节点</div>
</TemplateWrapper>
</div>
<div style={{ width: '100%', height: 800 }}>
<Topology
data={data}
autoLayout
onChange={this.onChange}
onSelect={this.handleSelect}
renderTreeNode={this.renderTreeNode}
/>
</div>
</div>
);
}
}
export default topologyWrapper(Flow);
复制代码
能够看到,包里只提供了极少的api,文档几分钟就能看完,之因此少,是由于库只负责将节点放到正确的位置,连上线就行了,其余的“概不负责”,修改样式能够在你的div上加个class,添加事件就再上个onXXX,想作啥作啥。git
最主要的缘由就是为了“简单”!对于拓扑图这样 的场景,画一个简单的节点,用DOM实现只须要简单的几行代码,用canvas的话就要写一大堆了,别人来看估计就是“一大坨”了。同时相对于须要先学习一套组件,而后用那些“奇奇怪怪”的api去写交互和样式,直接上手就开始撸本身最熟悉的div + css岂不是很开心?对于开发而言,调试DOM可以看到每个元素的细节,而canvas就无能为力了。github
核心部分代码:npm
<Topology
data={data}
autoLayout
onChange={this.onChange}
onSelect={this.handleSelect}
renderTreeNode={this.renderTreeNode}
/>
复制代码
data = {
nodes: [
{ id: '1', position: { x: 0, y: 0 } },
{ id: '2', position: { x: 100, y: 100 } }
],
lines: [
{ start: '1-0', end: '2' }
]
}
复制代码
data包含两个属性:nodes和lines,nodes记录节点信息,每一个node含有id和position属性,id是必选的,position记录了节点的位置,若是不包含position,点击自动布局,将会自动生成。canvas
lines记录节点与节点间的关系,start记录起点信息,格式为:'起点id-锚点id',end记录终点信息,格式为:'终点id'。api
上面的效果图能够看到右下角第二个图标点击自动布局功能,为了方便排版,自动布局会根据树结构计算节点的位置。当初始数据没有position字段时,若是autoLayout为true,组件会自动触发布局功能,至关于点击了自动布局按钮。bash
组件使用相似input或者select,当有新增节点或者连线发生时,触发onChange,onChange带有两个参数,newData和changeType, 整个过程彻底受控,你能够在onChange中作一些校验,决定数据是否更新。
当选择节点或者线段时触发,参数selectData格式同data。
renderTreeNode接收两个参数:nodeData, decorators,返回节点的DOM。
renderTreeNode = (data: ITopologyNode, { anchorDecorator }) => {
// name、content、branches都是自定义的字段,经过模板节点生成,详见TemplateWrapper
const {
name = '',
content = '',
branches = [],
} = data;
return (
<div className="topology-node">
<div className="node-header">{name}</div>
<p className="node-content">{content}</p>
{branches.length > 0 && (
<div className="flow-node-branches-wrapper">
{branches.map(
(item: string, index: number) => anchorDecorator({
anchorId: `${index}`,
})(<div className="flow-node-branch">{item}</div>),
)}
</div>
)}
</div>
);
};
复制代码
anchorDecorator({ anchorId: `${index}` })(
<div className="flow-node-branch">
{item}
</div>
)
复制代码
anchorDecorator是一个装饰器函数,接受一个options,目前只包含一个anchorId属性,即锚点id,若是不传的话,内部会自动生成一个自增id。能够看到,锚点长什么样,放到哪儿彻底由你本身决定。
<div className="topology-templates">
<TemplateWrapper generator={() => this.generatorNodeData(true)}>
<div className="topology-templates-item">宽节点</div>
</TemplateWrapper>
<TemplateWrapper generator={() => this.generatorNodeData(false)}>
<div className="topology-templates-item">窄节点</div>
</TemplateWrapper>
</div>
复制代码
经过templateWrapper包装生成一个模板节点,接收一个generator函数,当添加节点时,会调用这个函数,生成节点的初始数据,里面包含什么值由你决定,但必须包含一个惟一的id值。
export default topologyWrapper(Flow);
复制代码
包含拖拽部分的最上层组件必需要用topologyWrapper包一下,这是由于使用了react-dnd须要设置backend,这里只是作了一个简单的导出,方便使用:
export const topologyWrapper = DragDropContext(HTML5BackEnd);
复制代码
从需求出发来看,须要拥有更好的定制化能力和灵活性,从重构的角度来看,须要让代码更简单明了。go.js功能十分强大,但对于拓扑图这种相对较简单的场景而言,并不须要那么多复杂的能力,反而可能出现上面说的问题,全部canvas的实现应该都会有相似的问题。因此充分利用DOM的能力,在能实现需求的状况下,本库或许是更好的选择。
项目只实现了简单的功能,存在不足欢迎大佬们提issue,pr。另外我司招人,欢迎大佬们加入:招聘连接!!!