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。另外我司招人,歡迎大佬們加入:招聘連接!!!