在開發企業內部系統或資訊平台的專案中,常常會遇到需要呈現樹狀結構與流程順序圖示的情境,例如公司組織架構圖、部門關係圖、工作流程圖等。過去多數解法是透過繪圖工具產出靜態圖片,但這類圖示無法互動、無法拖曳,也難以維護或動態更新。
即使採用部分 UI 套件或視覺工具來產生流程圖或組織圖,常常也需要手動調整方塊位置、對齊箭頭,更新資料時流程容易走樣,增加維護負擔。
本文將示範如何使用 React Flow,打造一套互動式、資料驅動的組織架構圖,以「組織 → 部門 → 子部門」的層級為範例,說明如何:
- 自動渲染節點與連線箭頭
- 自訂每個節點的樣式與內容
- 利用資料結構控制整體圖示架構與層級
- 一鍵更新資料即反映圖面變化
透過 React Flow,不只可以製作清晰的組織結構圖,也非常適合建構如流程圖、決策圖、網路架構圖、節點流程設計工具等需要「前後關係」與「階層視覺化」的應用場景。
整體來說,這樣的開發方式讓你:
- 更快速建立組織圖與流程圖
- 減少手動排版時間
- 確保圖示維持一致與整齊
- 提高可讀性與可維護性
如果你正在找尋一個現代化、可維護、互動性高的組織圖解決方案,React Flow 將是非常值得一試的選項。
一、基本介紹
React Flow 是一個開源的 React 函式庫,用於在 Web 應用程式中建立互動式、基於節點的圖表和流程圖,支援自訂節點和邊線樣式、縮圖顯示和控制元件等功能,廣泛應用於資料視覺化、工作流程編輯器、機器學習等領域。
- 安裝 React-flow:
npm install @xyflow/react
- 基本元件認識:
- Node(節點):負責顯示資料的小格子
- Handle(把手):負責連結節點與節點的原點,分成source跟target兩種,source代表連結從這裡出發,target則代表連結目的地
- Edge(連接線):source跟target間的連接線

接下來就開始處理資料的顯示,也可以使用react-flow預設的節點樣式,但T編想自訂自己的樣式所以要先新增一個自訂節點。
二、Custom Nodes(自訂節點):
先新增一個react component,舉例想新增一個組織的節點:
import React from 'react';
import { Paper, Typography } from '@mui/material';
import type { Organization } from '../types';
import { Handle } from '@xyflow/react';
interface OrganizationNodeProps {
data: Organization;
}
export function OrganizationNode({data}: OrganizationNodeProps) {
return (
<>
<Paper variant="outlined" sx={{
padding: '12px',
border:'2px solid rgba(25, 118, 210)',
minWidth:'100px',
display:'flex',
alignItems:'center',
justifyContent:'center',
}}>
<Typography>{data.org_name}</Typography>
</Paper>
<Handle type="source" position='bottom'/>
</>
);
}
T編想顯示組織名稱以及有顏色的框線,因此先接收從外部傳進來的data(組織資料),並在內部顯示data.org_name。Handle在這裡就可以加進來了,組織為第一層,因此在組織節點的下面加一個source handle。
部門節點也類似只是要多一個<Handle type="target" position="top" />
因為他要用接收來自組織節點的連結。
三、主畫面
接下來就是將我們剛剛新增的自訂節點渲染至主畫面了
import '@xyflow/react/dist/style.css';//記得載入官方CSS樣式
export default function App() {
return (
<div style={{ height: '100%', width: '100%' }}>
<ReactFlow>
<Background />
<Controls />
</ReactFlow>
</div>
);
}
在官方文件裡面有說明外面一定要包一個父元件定好寬度及高度。

這樣就先創好了一個空的流程畫布 長這樣 :

這裡有幾個reactflow的內建元件<Background />
為後面的點點背景、<Controls />
為左邊的控制按鈕。
接下來將節點渲染上去:
先宣告自訂節點類型
import { OrganizationNode } from "../../components/OrganizationNode";
import { GroupNode } from "../../components/GroupNode";
const nodeTypes: NodeTypes = {
organizationNode: OrganizationNode,
groupNode: GroupNode,
};
接著將節點類型傳給reactflow:
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes} //<-這行
edgeTypes={edgeTypes}
fitView
nodesConnectable={false}>
</ReactFlow>
接著就可以宣告節點了,官方文件的宣告節點方法:
const nodes = [
{
id: 'node-1',
//你剛剛新增的節點類型 像我就會是organizationNode或groupNode
type: 'textUpdater',
position: { x: 0, y: 0 },//節點初始化的位置
data: { value: 123 },//資料
},
];
而T編的需求是需要節點根據api傳回來的資料渲染,假如組織的資料傳回來是這樣:
{
id: 1,
org_name: "XX組織";
}
接著就新增組織節點
//用來拿組織資料的hook
const { data: orgData, loading, error } = useOneOrganizationData(id);
const orgNode: Node<Organization> = {
id: `org-${orgData.id}`,
type: "organizationNode",
data: orgData,
position: { x: 200, y: 0 },
};
因為傳進去reactflow的nodes一定是要一個array
<ReactFlow
nodes={nodes} //<-這行
edges={edges}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
fitView
nodesConnectable={false}>
</ReactFlow>
因此先宣告一個nodes陣列,接著將orgNode push進去,完整程式碼:
const nodes: Node[] = [];
if (orgData) {//拿到資料後再新增
const orgNode: Node<Organization> = {
id: `org-${orgData.id}`,
type: "organizationNode",
data: orgData,
position: { x: 200, y: 0 },
};
nodes.push(orgNode);
}
nodes就會長這樣:
[
{id: "org-23", type: "organizationNode", data: Object, position: {x: 200, y: 0}},
]
接著組織節點就渲染出來了

這裡用 push 是最簡單的示範,實務上你可能會用 useState 或 useNodesState 來管理節點狀態,才能支援互動(拖曳、動態新增節點)。
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
組織下的部門節點也是同理,只是在專案中組織可能會有複數個,因此要用array先將部門節點map出來,再加入nodes陣列裡:
const groups: Group[] = Array.isArray(groupData) ? (groupData as Group[]) : [];
groups.forEach((g, i) => {
const gNode: Node<Group> = {
id: `g-${g.id}`,
type: "groupNode",
data: g,
position: { x: 200+i*200, y:100 },
};
nodes.push(gNode);
});
接下來就是處理組織與部門間的連線(Edges)了
四、Custom Edges(自訂邊)
跟自訂節點類似,也可以去自訂邊的樣式,根據官方文件先新增一個自訂節點元件
import { BaseEdge, getStraightPath } from '@xyflow/react';
export function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) {
const [edgePath] = getStraightPath({//根據傳進來的xy計算直線路徑
sourceX,
sourceY,
targetX,
targetY,
});
return (
<>
<BaseEdge id={id} path={edgePath} />
</>
);
}
reactflow也提供了很多方便計算路徑的函數,比如:
- getBezierPath
- getSmoothStepPath
- getStraightPath
只是不同函數需要的props也會有所不同,這次先以最簡單的直線為例,接著跟節點一樣建立edgeTypes
const edgeTypes = {
'custom-edge': CustomEdge,
};
然後將edgeType傳遞給ReactFlow
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes} //<-這行
fitView
nodesConnectable={false}>
</ReactFlow>
接著建立一個初始連線,官方的文件的範例:
const initialEdges = [
{
id: 'e1',
source: 'n1', //開始節點id
target: 'n2', //目標節點id
type: 'custom-edge',//記得將type改成剛自訂好的edgeTypes
},
];
節點間就建立連線了!

接著到我們這次的專案裡,我們的需求是將組織與底下的所有部門建立連線,因為線會不只一條,因此我們一樣先存進array裡再map出來
if (!orgData) return [];
const groups: Group[] = Array.isArray(groupData) ? (groupData as Group[]) : [];
return groups.map((g) => ({
id: `e-${orgData.id}-${g.id}`,//e-組織id-部門id
source: `org-${orgData.id}`,//都是從最上層的組織出發
target: `g-${g.id}`,//底下的部門id
type: "custom-edge",
animated: true,//將連線添加動畫效果,有方向性比較了解上下關係
}));
}, [orgData, groupData]);
實際渲染時請以useMemo
穩定nodeTypes/edgeTypes
的參考,並以useNodesState / useEdgesState
受控管理nodes/edges
;若由資料導出nodes/edges
,至少用useMemo
在資料不變時保持參考穩定,以避免節點被卸載重載(例如拖曳位置重置)。
五、最後成果:

這個library還有非常多非常多功能,比如拖拉節點、白板功能、排列演算法,也可以去官方的playground玩玩看:https://play.reactflow.dev