---
title: 使用 React 定义节点
order: 13
---
在 G6 中,自定义节点通常需要操作 DOM 或 Canvas 元素,但借助 `@antv/g6-extension-react` 一方生态库,可以直接使用 React 组件作为节点内容,提升开发效率与可维护性。
## 自定义节点方案选择
### G6 节点
✅ **推荐场景:**
- 节点只是简单的几何图形
- 需要高效渲染超过 2,000 个节点的场景
- 需要直接操作图形实例进行精细控制
> 有关如何使用 Canvas 图形自定义节点的详细信息,请参阅 [自定义节点](/manual/element/node/custom-node) 文档
### React Node
✅ **推荐场景:**
- 需要集成 Ant Design 等 UI 库的业务系统
- 节点包含表单输入、状态切换等交互逻辑
- 已有 React 设计系统需要复用的场景
## 快速入门
### 环境准备
在开始之前,请确保您已经:
- **安装 React 项目**:确保已安装并创建 React 项目。
- **React 版本要求**:确保使用的 React 版本 >=16.8.0。
### 安装依赖
要使用 `@antv/g6-extension-react`,请运行以下命令:
:::code-group
```bash [npm]
npm install @antv/g6-extension-react
```
```bash [yarn]
yarn add @antv/g6-extension-react
```
```bash [pnpm]
pnpm add @antv/g6-extension-react
```
:::
### 组件集成
#### 1. 注册 React 节点类型
通过扩展机制注册 React 节点类型:
```jsx
import { ExtensionCategory, register } from '@antv/g6';
import { ReactNode } from '@antv/g6-extension-react';
register(ExtensionCategory.NODE, 'react-node', ReactNode);
```
`register` 方法需要三个参数:
- 扩展类别:`ExtensionCategory.NODE` 表示这是一个节点类型
- 类型名称:`react-node` 是我们给这个自定义节点起的名字,后续会在配置中使用
- 类定义:ReactNode 是 `@antv/g6-extension-react` 导出的实现类
#### 2. 定义业务组件
定义一个简单的 React 组件作为节点的内容:
```jsx
const MyReactNode = () => {
return
node
;
};
```
#### 3. 使用组件
在图配置中使用自定义的 React 节点。通过在图配置中指定节点类型和样式,来使用自定义的 React 组件。
- `type`:指定节点类型为 `react-node` (使用与注册时起的名字)
- `style.component`:定义节点的 React 组件内容
```jsx
const graph = new Graph({
node: {
type: 'react-node',
style: {
component: () => ,
},
},
});
graph.render();
```
## 高级功能
### 状态管理
在复杂图可视化场景中,节点需要动态响应交互状态。我们提供两种互补的状态管理方案:
#### 响应内置交互状态
G6 提供内置的交互状态管理状态,如 `hover-activate` 和 `click-select`。可以通过节点数据中的 `data.states` 字段获取当前节点状态,并根据状态调整节点样式。
**示例**:在节点被 hover 时改变背景颜色。
```jsx
import { ExtensionCategory, register, Graph } from '@antv/g6';
import { ReactNode } from '@antv/g6-extension-react';
register(ExtensionCategory.NODE, 'react-node', ReactNode);
const StatefulNode = ({ data }) => {
const isActive = data.states?.includes('active');
return (
{data.data.label}
);
};
const graph = new Graph({
data: {
nodes: [
{ id: 'node1', style: { x: 100, y: 200 }, data: { label: 'node1' } },
{ id: 'node2', style: { x: 300, y: 200 }, data: { label: 'node2' } },
],
},
node: {
type: 'react-node',
style: {
component: (data) => ,
},
},
behaviors: ['hover-activate'],
});
graph.render();
```
#### 自定义业务状态
当需要管理业务相关状态(如审批状态、风险等级)时,可通过扩展节点数据实现:
**示例**:通过 data 添加 `selected` 变量,实现节点选中和取消选中的样式变化。
```jsx
import { ExtensionCategory, register, Graph } from '@antv/g6';
import { ReactNode } from '@antv/g6-extension-react';
register(ExtensionCategory.NODE, 'react-node', ReactNode);
const MyReactNode = ({ data, graph }) => {
const handleClick = () => {
graph.updateNodeData([{ id: data.id, data: { selected: !data.data.selected } }]);
graph.draw();
};
return (
Node
);
};
const graph = new Graph({
data: {
nodes: [
{
id: 'node1',
style: { x: 100, y: 100 },
data: { selected: true },
},
],
},
node: {
type: 'react-node',
style: {
component: (data) => ,
},
},
});
graph.render();
```
### 事件交互
实现节点与图实例的双向通信,使节点和图实例可以相互更新。
**示例**:通过自定义节点操作图数据,并重新渲染图形。
```jsx
const IDCardNode = ({ id, selected, graph }) => {
const handleSelect = () => {
graph.updateNodeData([{ id, data: { selected: true } }]);
graph.draw();
};
return ;
};
const graph = new Graph({
node: {
type: 'react-node',
style: {
component: ({ id, data }) => ,
},
},
});
```
## 实际案例
```js | ob { inject: true }
import { DatabaseFilled } from '@ant-design/icons';
import { ExtensionCategory, Graph, register } from '@antv/g6';
import { ReactNode } from '@antv/g6-extension-react';
import { Badge, Flex, Input, Tag, Typography } from 'antd';
import { useEffect, useRef } from 'react';
import { createRoot } from 'react-dom/client';
const { Text } = Typography;
register(ExtensionCategory.NODE, 'react', ReactNode);
const Node = ({ data, onChange }) => {
const { status, type } = data.data;
return (
Server
{type}
{data.id}
*URL:
{
const url = event.target.value;
onChange?.(url);
}}
/>
);
};
export const ReactNodeDemo = () => {
const containerRef = useRef();
useEffect(() => {
const graph = new Graph({
container: containerRef.current,
data: {
nodes: [
{
id: 'local-server-1',
data: { status: 'success', type: 'local', url: 'http://localhost:3000' },
style: { x: 50, y: 50 },
},
{
id: 'remote-server-1',
data: { status: 'warning', type: 'remote' },
style: { x: 350, y: 50 },
},
],
edges: [{ source: 'local-server-1', target: 'remote-server-1' }],
},
node: {
type: 'react',
style: {
size: [240, 100],
component: (data) => ,
},
},
behaviors: ['drag-element', 'zoom-canvas', 'drag-canvas'],
});
graph.render();
}, []);
return ;
};
const root = createRoot(document.getElementById('container'));
root.render();
```
```js | ob { inject: true }
import { UserOutlined } from '@ant-design/icons';
import { ExtensionCategory, Graph, register } from '@antv/g6';
import { ReactNode } from '@antv/g6-extension-react';
import { Avatar, Button, Card, Descriptions, Select, Space, Typography } from 'antd';
import React, { useEffect, useRef } from 'react';
import { createRoot } from 'react-dom/client';
const { Title, Text } = Typography;
const { Option } = Select;
register(ExtensionCategory.NODE, 'react-node', ReactNode);
const IDCardNode = ({ id, data }) => {
const { name, idNumber, address, expanded, selected, graph } = data;
const toggleExpand = (e) => {
e.stopPropagation();
graph.updateNodeData([
{
id,
data: { expanded: !expanded },
},
]);
graph.render();
};
const handleSelect = (value) => {
graph.updateNodeData([
{
id,
data: { selected: value !== 0 },
},
]);
if (value === 2) {
// 获取与当前节点相连的所有节点
const connectedNodes = graph.getNeighborNodesData(id);
connectedNodes.forEach((node) => {
graph.updateNodeData([
{
id: node.id,
data: { selected: true },
},
]);
});
}
graph.render();
};
const CardTitle = (
} />
{name}
);
return (
{expanded ? (
{idNumber}
{address}
) : (
IDCard Information
)}
);
};
// 定义 Graph 数据
const data = {
nodes: [
{
id: 'node1',
data: {
name: 'Alice',
idNumber: 'IDUSAASD2131734',
address: '1234 Broadway, Apt 5B, New York, NY 10001',
expanded: false, // 初始状态为收缩
selected: false, // 初始状态为未选中
selectedOption: 1, // 初始选择本节点
},
style: { x: 50, y: 50 },
},
{
id: 'node2',
data: {
name: 'Bob',
idNumber: 'IDUSAASD1431920',
address: '3030 Chestnut St, Philadelphia, PA 19104',
expanded: false, // 初始状态为收缩
selected: false, // 初始状态为未选中
selectedOption: 0, // 初始不选择
},
style: { x: 700, y: 100 },
},
{
id: 'node3',
data: {
name: 'Charlie',
idNumber: 'IDUSAASD1431921',
address: '4040 Elm St, Chicago, IL 60611',
expanded: false,
selected: true,
selectedOption: 0,
},
},
{
id: 'node4',
data: {
name: 'David',
idNumber: 'IDUSAASD1431922',
address: '5050 Oak St, Houston, TX 77002',
expanded: false,
selected: false,
selectedOption: 0,
},
},
{
id: 'node5',
data: {
name: 'Eve',
idNumber: 'IDUSAASD1431923',
address: '6060 Pine St, Phoenix, AZ 85001',
expanded: false,
selected: false,
selectedOption: 0,
},
},
],
edges: [
{ source: 'node1', target: 'node2' },
{ source: 'node2', target: 'node3' },
{ source: 'node3', target: 'node4' },
{ source: 'node4', target: 'node5' },
],
};
export const ReactNodeDemo = () => {
const containerRef = useRef();
const graphRef = useRef(null);
useEffect(() => {
// 创建 Graph 实例
const graph = new Graph({
autoFit: 'view',
container: containerRef.current,
data,
node: {
type: 'react-node',
style: {
size: (datum) => (datum.data.expanded ? [340, 236] : [340, 105]), // 调整大小以适应内容
component: (data) => ,
},
},
behaviors: ['drag-element', 'zoom-canvas', 'drag-canvas'],
layout: {
type: 'snake',
cols: 2,
rowGap: 100,
colGap: 220,
},
});
// 渲染 Graph
graph.render();
// 保存 graph 实例
graphRef.current = graph;
return () => {
graph.destroy();
};
}, []);
return ;
};
// 渲染 React 组件到 DOM
const root = createRoot(document.getElementById('container'));
root.render();
```