npm package: @knotx/react
Description: React for Knotx
Author: boenfu
Latest version: 0.5.10

# @knotx/react

[![npm version](https://img.shields.io/npm/v/@knotx/react.svg)](https://www.npmjs.com/package/@knotx/react)
[![license](https://img.shields.io/npm/l/@knotx/react.svg)](https://github.com/boenfu/knotx/blob/main/LICENSE)

> 🚀 一个强大的 React 节点编辑器组件，支持可视化流程图、组织架构图、脑图等多种场景

## 📦 安装

```bash
npm install @knotx/react
```

```bash
yarn add @knotx/react
```

```bash
pnpm add @knotx/react
```

### 对等依赖

该包需要以下对等依赖：

```json
{
  "react": ">=17.0.0",
  "react-dom": ">=17.0.0"
}
```

## 🎯 基本概述

`@knotx/react` 是 Knotx 生态系统中的 React 适配器，提供了一个功能强大且灵活的节点编辑器组件。它支持：

- 🎨 **可视化节点编辑** - 拖拽、缩放、连接节点
- 🔧 **插件系统** - 高度可扩展的插件架构
- 🎭 **自定义渲染** - 完全自定义的节点和边的渲染
- 📱 **响应式设计** - 适配不同屏幕尺寸
- ⚡ **高性能** - 基于 RxJS 的响应式状态管理
- 🎪 **丰富交互** - 支持选择、拖拽、缩放、小地图等

## 🚀 快速开始

### 基础用法

```tsx
import type { Edge, Node } from '@knotx/core'

import { Knotx } from '@knotx/react'
import React from 'react'

function App() {
  const initialNodes: Node[] = [
    {
      id: '1',
      type: 'basic',
      position: { x: 300, y: 300 },
      measured: { width: 160, height: 60 },
      data: { label: '节点 1' }
    },
    {
      id: '2',
      type: 'basic',
      position: { x: 600, y: 300 },
      measured: { width: 160, height: 60 },
      data: { label: '节点 2' }
    }
  ]

  const initialEdges: Edge[] = [
    {
      id: '1-2',
      type: 'bezier',
      source: '1',
      target: '2',
      data: {}
    }
  ]

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Knotx
        initialNodes={initialNodes}
        initialEdges={initialEdges}
        plugins={[]} // 插件列表
      />
    </div>
  )
}

export default App
```

### 自定义节点渲染

```tsx
import type { Node } from '@knotx/core'

import { BasePlugin } from '@knotx/core'
import { nodeType } from '@knotx/decorators'
import { Knotx } from '@knotx/react'
import React from 'react'

// 创建自定义节点插件
class CustomNodePlugin extends BasePlugin<'customNode'> {
  name = 'customNode' as const

  @nodeType('custom')
  renderCustomNode({ node }: { node: Node }) {
    return (
      <div
        style={{
          width: '100%',
          height: '100%',
          background: '#f0f0f0',
          border: '2px solid #ccc',
          borderRadius: '8px',
          padding: '12px',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          fontSize: '14px',
          fontWeight: 'bold'
        }}
      >
        {node.data?.label || node.id}
      </div>
    )
  }
}

function App() {
  const nodes: Node[] = [
    {
      id: '1',
      type: 'custom',
      position: { x: 300, y: 300 },
      measured: { width: 200, height: 80 },
      data: { label: '自定义节点' }
    }
  ]

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Knotx
        initialNodes={nodes}
        plugins={[CustomNodePlugin]}
      />
    </div>
  )
}
```

### 受控组件模式

```tsx
import type { Edge, Node, ReactEngine } from '@knotx/react'

import { Knotx } from '@knotx/react'
import React, { useState } from 'react'

function App() {
  const [nodes, setNodes] = useState<Node[]>([
    {
      id: '1',
      type: 'basic',
      position: { x: 300, y: 300 },
      measured: { width: 160, height: 60 },
      data: { label: '节点 1' }
    }
  ])

  const [edges, setEdges] = useState<Edge[]>([])

  const handleInit = (engine: ReactEngine) => {
    console.log('引擎初始化完成:', engine)

    // 监听节点变化
    engine.nodesManager.dataMap$.subscribe((nodesMap) => {
      setNodes(Array.from(nodesMap.values()))
    })

    // 监听边变化
    engine.edgesManager.dataMap$.subscribe((edgesMap) => {
      setEdges(Array.from(edgesMap.values()))
    })
  }

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Knotx
        nodes={nodes}
        edges={edges}
        onInit={handleInit}
        plugins={[]}
      />
    </div>
  )
}
```

## 🔧 API 参考

### Knotx 组件

`Knotx` 是主要的 React 组件，用于渲染节点编辑器。

#### Props

| 属性 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| `className` | `string` | - | 容器的 CSS 类名 |
| `style` | `CSSProperties` | - | 容器的内联样式 |
| `getContainerStyle` | `(engine: ReactEngine) => CSSProperties` | - | 获取容器样式的函数 |
| `initialNodes` | `Node[]` | `[]` | 初始节点数据 |
| `initialEdges` | `Edge[]` | `[]` | 初始边数据 |
| `nodes` | `Node[]` | - | 受控的节点数据 |
| `edges` | `Edge[]` | - | 受控的边数据 |
| `plugins` | `Plugin[]` | `[]` | 插件列表 |
| `pluginConfig` | `PluginConfig` | `{}` | 插件配置 |
| `disablePresetPlugins` | `boolean` | `false` | 禁用预设插件 |
| `onInit` | `(engine: ReactEngine) => void` | - | 引擎初始化回调 |
| `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | 布局方向 |
| `ref` | `Ref<KnotxInstance>` | - | 组件引用 |

#### 示例

```tsx
import type { KnotxInstance, ReactEngine } from '@knotx/react'

import { Knotx } from '@knotx/react'
import React, { useRef } from 'react'

function App() {
  const knotxRef = useRef<KnotxInstance>(null)

  const handleInit = (engine: ReactEngine) => {
    console.log('引擎初始化完成')
  }

  const handleRerender = () => {
    knotxRef.current?.rerender()
  }

  return (
    <div>
      <button onClick={handleRerender}>重新渲染</button>
      <Knotx
        ref={knotxRef}
        className="my-knotx"
        style={{ border: '1px solid #ccc' }}
        initialNodes={[]}
        initialEdges={[]}
        plugins={[]}
        direction="horizontal"
        onInit={handleInit}
        getContainerStyle={engine => ({
          backgroundColor: '#f5f5f5'
        })}
      />
    </div>
  )
}
```

### KnotxInstance 接口

通过 `ref` 获取的组件实例接口。

#### 属性

| 属性 | 类型 | 描述 |
|------|------|------|
| `engineRef` | `MutableRefObject<ReactEngine \| null>` | 引擎实例引用 |
| `rerender` | `() => void` | 强制重新渲染组件 |

#### 示例

```tsx
import type { KnotxInstance } from '@knotx/react'

import { Knotx } from '@knotx/react'
import React, { useEffect, useRef } from 'react'

function App() {
  const knotxRef = useRef<KnotxInstance>(null)

  useEffect(() => {
    if (knotxRef.current) {
      const engine = knotxRef.current.engineRef.current
      if (engine) {
        // 使用引擎 API
        console.log('当前节点数量:', engine.nodesManager.dataMap$.value.size)
      }
    }
  }, [])

  return (
    <Knotx
      ref={knotxRef}
      initialNodes={[]}
      initialEdges={[]}
      plugins={[]}
    />
  )
}
```

### ReactEngine 类型

扩展自 `@knotx/core` 的 `Engine` 类型，专门用于 React 环境。

#### 主要方法

| 方法 | 描述 |
|------|------|
| `dispatchNodeOperation(operation: NodeOperation)` | 分发节点操作 |
| `dispatchEdgeOperation(operation: EdgeOperation)` | 分发边操作 |
| `getLayerComponents(layer: number)` | 获取层级组件 |
| `destroy()` | 销毁引擎实例 |

### 插件配置

#### 插件配置类型

```typescript
interface PluginConfig {
  [pluginName: string]: any
}
```

#### 示例

```tsx
import { Canvas } from '@knotx/plugins-canvas'
import { Drag } from '@knotx/plugins-drag'
import { Knotx } from '@knotx/react'
import React from 'react'

function App() {
  const pluginConfig = {
    canvas: {
      minScale: 0.1,
      maxScale: 2.0,
      wheel: {
        step: 0.1,
        wheelDisabled: false
      }
    },
    drag: {
      allowDrag: true,
      dragThreshold: 5
    }
  }

  return (
    <Knotx
      initialNodes={[]}
      initialEdges={[]}
      plugins={[Canvas, Drag]}
      pluginConfig={pluginConfig}
    />
  )
}
```

## 🧩 插件系统

### 常用插件

以下是一些常用的插件：

- `@knotx/plugins-canvas` - 画布功能（缩放、平移）
- `@knotx/plugins-drag` - 拖拽功能
- `@knotx/plugins-selection` - 选择功能
- `@knotx/plugins-minimap` - 小地图
- `@knotx/plugins-background` - 背景
- `@knotx/plugins-bounding` - 边界调整
- `@knotx/plugins-connection-line` - 连接线
- `@knotx/plugins-group` - 分组
- `@knotx/plugins-history` - 历史记录

### 创建自定义插件

```tsx
import type { EdgeProps, Node } from '@knotx/core'

import { BasePlugin } from '@knotx/core'
import { edgeType, nodeType } from '@knotx/decorators'

class MyPlugin extends BasePlugin<'myPlugin'> {
  name = 'myPlugin' as const

  @nodeType('myNode')
  renderMyNode({ node }: { node: Node }) {
    return (
      <div style={{ padding: '10px', border: '1px solid #ccc' }}>
        自定义节点:
        {' '}
        {node.id}
      </div>
    )
  }

  @edgeType('myEdge')
  renderMyEdge(props: EdgeProps) {
    return (
      <path
        d={props.path}
        stroke="#ff0000"
        strokeWidth={2}
        fill="none"
      />
    )
  }
}

// 使用插件
function App() {
  return (
    <Knotx
      initialNodes={[
        {
          id: '1',
          type: 'myNode',
          position: { x: 100, y: 100 },
          measured: { width: 120, height: 60 },
          data: {}
        }
      ]}
      initialEdges={[]}
      plugins={[MyPlugin]}
    />
  )
}
```

## 📂 文件目录结构

```
packages/react/
├── src/
│   ├── components/           # React 组件
│   │   ├── content.tsx      # 层级内容组件
│   │   └── index.ts         # 组件导出
│   ├── hooks/               # React Hooks
│   │   ├── container.ts     # 容器引用 Hook
│   │   ├── data.ts          # 数据更新 Hook
│   │   ├── engine.ts        # 引擎管理 Hook
│   │   ├── plugin.ts        # 插件管理 Hook
│   │   └── index.ts         # Hooks 导出
│   ├── definition.ts        # 类型定义
│   ├── engine.ts            # React 引擎初始化
│   ├── index.ts             # 主入口文件
│   ├── knotx.tsx            # 主要 Knotx 组件
│   └── layer.tsx            # 层级渲染组件
├── dist/                    # 构建输出目录
├── CHANGELOG.md             # 变更日志
├── package.json             # 包配置
├── README.md                # 中文文档
├── README.en.md             # 英文文档
└── tsconfig.json            # TypeScript 配置
```

### 核心文件说明

| 文件 | 描述 |
|------|------|
| `knotx.tsx` | 主要的 Knotx React 组件实现 |
| `definition.ts` | TypeScript 类型定义和接口 |
| `engine.ts` | React 引擎初始化和运行时配置 |
| `layer.tsx` | 层级渲染逻辑 |
| `components/content.tsx` | 层级内容渲染组件 |
| `hooks/container.ts` | 容器大小监听 Hook |
| `hooks/data.ts` | 数据差异更新 Hook |
| `hooks/engine.ts` | 引擎生命周期管理 Hook |
| `hooks/plugin.ts` | 插件合并和配置更新 Hook |

## 🔗 类型导出

该包重新导出了所有来自 `@knotx/core` 的类型，方便使用：

```typescript
import type {
  // 其他
  Direction,
  Edge,
  EdgeOperation,
  EdgeProps,

  Engine,
  EngineOptions,

  IData,
  IPlugin,

  KnotxInstance,
  KnotxProps,

  Layer,
  // 核心类型
  Node,

  // 操作类型
  NodeOperation,
  NodePosition,
  // 属性类型
  NodeProps,
  Plugin,

  // 配置类型
  PluginConfigs,
  // 位置类型
  Position,
  // React 特定类型
  ReactEngine
} from '@knotx/react'
```

## 🎨 最佳实践

### 1. 性能优化

```tsx
import { Knotx } from '@knotx/react'
import React, { memo, useMemo } from 'react'

const MyKnotx = memo(({ data }: { data: any }) => {
  // 使用 useMemo 缓存节点和边数据
  const nodes = useMemo(() => {
    return data.nodes || []
  }, [data.nodes])

  const edges = useMemo(() => {
    return data.edges || []
  }, [data.edges])

  return (
    <Knotx
      nodes={nodes}
      edges={edges}
      plugins={[]}
    />
  )
})
```

### 2. 错误处理

```tsx
import { Knotx } from '@knotx/react'
import React, { ErrorBoundary } from 'react'

class KnotxErrorBoundary extends React.Component {
  constructor(props: any) {
    super(props)
    this.state = { hasError: false }
  }

  static getDerivedStateFromError(error: Error) {
    // 处理错误：记录错误并返回错误状态
    console.error('React Error Boundary:', error)
    return { hasError: true }
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    // 处理错误：记录错误信息
    console.error('Knotx Error:', error, errorInfo)

    // 可以在这里添加错误上报逻辑
    // 例如：this.reportError(error, errorInfo)
  }

  render() {
    if (this.state.hasError) {
      return <div>Something went wrong with Knotx.</div>
    }

    return this.props.children
  }
}

function App() {
  return (
    <KnotxErrorBoundary>
      <Knotx
        initialNodes={[]}
        initialEdges={[]}
        plugins={[]}
      />
    </KnotxErrorBoundary>
  )
}
```

### 3. 主题定制

```tsx
import { BasePlugin } from '@knotx/core'
import { layer } from '@knotx/decorators'
import { Knotx } from '@knotx/react'
import React from 'react'

class ThemePlugin extends BasePlugin<'theme'> {
  name = 'theme' as const

  @layer(100)
  renderTheme() {
    return (
      <style>
        {`
          .knotx-container {
            --node-bg: #ffffff;
            --node-border: #e1e4e8;
            --edge-stroke: #586069;
            --selection-bg: rgba(0, 123, 255, 0.1);
            --selection-border: #007bff;
          }

          .dark-theme .knotx-container {
            --node-bg: #2d3748;
            --node-border: #4a5568;
            --edge-stroke: #a0aec0;
            --selection-bg: rgba(66, 153, 225, 0.1);
            --selection-border: #4299e1;
          }
        `}
      </style>
    )
  }
}

function App() {
  return (
    <div className="dark-theme">
      <Knotx
        initialNodes={[]}
        initialEdges={[]}
        plugins={[ThemePlugin]}
      />
    </div>
  )
}
```

## 📝 常见问题

### Q: 如何处理大量节点的性能问题？

A: 可以使用虚拟化技术，只渲染可见区域的节点：

```tsx
import { BasePlugin } from '@knotx/core'
import { Knotx } from '@knotx/react'
import React from 'react'

// 实现虚拟化插件
class VirtualizationPlugin extends BasePlugin<'virtualization'> {
  name = 'virtualization' as const

  // 实现虚拟化逻辑
  // ...
}
```

### Q: 如何实现节点的自定义连接点？

A: 使用 `@knotx/plugins-connection-line` 插件：

```tsx
import { ConnectionLine } from '@knotx/plugins-connection-line'

// 在节点中添加连接点
function CustomNode({ node }) {
  return (
    <div>
      <div
        className="connection-handle"
        data-handle-position="top"
      />
      节点内容
    </div>
  )
}
```

### Q: 如何保存和恢复画布状态？

A: 通过引擎的数据管理器：

```tsx
function saveState(engine: ReactEngine) {
  const state = {
    nodes: Array.from(engine.nodesManager.dataMap$.value.values()),
    edges: Array.from(engine.edgesManager.dataMap$.value.values()),
    viewport: engine.container
  }
  localStorage.setItem('knotx-state', JSON.stringify(state))
}

function loadState(engine: ReactEngine) {
  const state = JSON.parse(localStorage.getItem('knotx-state') || '{}')
  // 恢复状态逻辑
}
```

## 📄 许可证

MIT License - 详见 [LICENSE](https://github.com/boenfu/knotx/blob/main/LICENSE) 文件

## 🤝 贡献

欢迎提交 Pull Request 和 Issue！

## 🔗 相关链接

- [GitHub 仓库](https://github.com/boenfu/knotx)
- [在线示例](https://knotx.dev)
- [API 文档](https://knotx.dev/docs)
- [更新日志](./CHANGELOG.md)