跳到主要内容

架构设计

AgileJS Web 编辑器基于 React 19 + TypeScript 构建,采用模块化架构设计。

技术栈

核心框架

  • React 19.2.0: UI 框架
  • TypeScript: 类型安全
  • Vite 7.x: 构建工具
  • React Router 7.5: 路由管理

状态管理

  • Zustand 5.0: 轻量级状态管理
  • 简洁的 API,无需 Provider
  • 支持 React 并发特性

UI 库

  • TailwindCSS 4.1: 原子化 CSS
  • 支持深色模式
  • 响应式设计

Canvas 引擎

  • @fnt-agilejs/core: 自研图形引擎
  • 插件化架构
  • 高性能渲染

目录结构

packages/editor/
├── src/
│ ├── app.tsx # 应用根组件
│ ├── main.tsx # 应用入口
│ ├── components/ # UI 组件
│ │ ├── toolbar/ # 工具栏组件
│ │ ├── panel/ # 面板组件
│ │ ├── common/ # 通用组件
│ │ ├── dragShape/ # 拖拽图形
│ │ ├── PropertyPanel.tsx # 属性面板
│ │ └── ShapesPanel.tsx # 图形面板
│ ├── pages/ # 页面组件
│ │ ├── editor/ # 编辑器页面
│ │ ├── viewer/ # 查看器页面
│ │ └── home.tsx # 首页
│ ├── hooks/ # 自定义 Hooks
│ ├── store/ # 状态管理
│ │ ├── canvasStore.ts # Canvas 状态
│ │ └── index.ts # Store 导出
│ ├── config/ # 配置文件
│ │ └── nodeTemplates.ts # 节点模板
│ ├── routes/ # 路由配置
│ ├── types/ # TypeScript 类型
│ ├── utils/ # 工具函数
│ ├── mock/ # Mock 数据
│ │ └── examples/ # 示例文件
│ ├── service/ # API 服务
│ └── assets/ # 静态资源
├── index.html # HTML 模板
├── vite.config.ts # Vite 配置
├── tailwind.config.js # Tailwind 配置
└── package.json # 依赖配置

核心架构

1. 分层架构

┌─────────────────────────────────────┐
│ UI Components Layer │ React 组件
│ (Toolbar, Panel, PropertyPanel) │
├─────────────────────────────────────┤
│ State Management Layer │ Zustand Store
│ (canvasStore) │
├─────────────────────────────────────┤
│ Business Logic Layer │ Hooks, Utils
│ (useBlinkControl, utils) │
├─────────────────────────────────────┤
│ Canvas Engine Layer │ @fnt-agilejs/core
│ (CanvasEngine, Plugins) │
└─────────────────────────────────────┘

2. 数据流

// 单向数据流
User Action

Component Event Handler

Canvas Engine API / Zustand Action

Engine Event Emission

Component State Update

UI Re-render

示例:添加节点

// 1. 用户拖拽图形到画布
onDrop(e) {
const worldPos = engine.screenToWorld(e.clientX, e.clientY);

// 2. 创建节点
const node = createNodeByShape(shape, worldPos);

// 3. 执行命令
engine.history.execute(
new AddNodeCommand(engine.graph, node)
);

// 4. 引擎触发事件
// engine.events.emit('graph:change', { reason: 'node-added' })

// 5. 组件监听事件,更新 UI
}

3. 组件通信

父子组件通信

// 通过 props 传递
<PropertyPanel
engine={engine}
visible={propertyOpen}
onToggleTheme={handleToggleTheme}
/>

跨组件通信 ⚠️

// 通过 Zustand Store(实际未应用于业务,当前情况下使用Zustand样板代码也不少)
import useCanvasStore from '@/store/canvasStore';

function Component() {
const { engine, setEngine } = useCanvasStore();

// 使用 engine
}

引擎事件通信

// 通过 EventBus
engine.events.on('graph:selection-change', ({ nodes }) => {
// 更新选中状态
updateSelection(nodes);
});

核心模块

1. 编辑器页面 (pages/editor)

主编辑器页面,集成所有功能。

const Editor: React.FC = () => {
const engineRef = useRef<CanvasEngine | null>(null);

// 初始化引擎
useEffect(() => {
const engine = new CanvasEngine({
container: containerRef.current
});

// 注册渲染器
registerRenderers(engine);

// 注册插件
registerPlugins(engine);

// 启动引擎
engine.start();

engineRef.current = engine;

return () => engine.dispose();
}, []);

return (
<div>
<Toolbar engine={engine} />
<div ref={containerRef} />
<ShapesPanel />
<PropertyPanel engine={engine} />
</div>
);
};

2. 状态管理 (store/canvasStore)

全局状态管理。

interface CanvasState {
engine: CanvasEngine | null;
isEditing: boolean;
setEngine: (engine: CanvasEngine | null) => void;
setIsEditing: (isEditing: boolean) => void;
}

const useCanvasStore = create<CanvasState>((set) => ({
engine: null,
isEditing: false,
setEngine: (engine) => set({ engine }),
setIsEditing: (isEditing) => set({ isEditing }),
}));

3. 工具栏 (components/toolbar)

顶部工具栏,提供常用操作。

const Toolbar: React.FC<ToolbarProps> = ({ engine }) => {
return (
<div className="toolbar">
<UndoRedoAction engine={engine} />
<FitAllAction engine={engine} />
<NodeControlAction engine={engine} />
<LayerArrangeAction engine={engine} />
<ZoomAction engine={engine} />
</div>
);
};

4. 属性面板 (PropertyPanel)

右侧属性面板,编辑节点/边属性。

const PropertyPanel: React.FC<PropertyPanelProps> = ({ engine }) => {
const [selectionKind, setSelectionKind] = useState<'none' | 'node' | 'edge'>('none');

// 监听选择变化
useEffect(() => {
if (!engine) return;

const handler = () => {
const selected = engine.graph.getNodes().filter(n => n.selected);
setSelectionKind(selected.length > 0 ? 'node' : 'none');
};

engine.events.on('graph:selection-change', handler);
return () => engine.events.off('graph:selection-change', handler);
}, [engine]);

return (
<div className="property-panel">
{selectionKind === 'node' && <NodeProperties engine={engine} />}
{selectionKind === 'edge' && <EdgeProperties engine={engine} />}
</div>
);
};

5. 图形面板 (ShapesPanel)

左侧图形面板,提供可拖拽的图形。

const ShapesPanel: React.FC = () => {
return (
<div className="shapes-panel">
{paletteGroups.map(group => (
<div key={group.key}>
<h3>{group.label}</h3>
{group.items.map(item => (
<DraggableItem key={item.key} item={item} />
))}
</div>
))}
</div>
);
};

设计模式

1. 命令模式

所有编辑操作通过命令模式实现撤销/重做。

// 自定义命令
class SetCanvasBackgroundCommand implements ICommand {
constructor(
private engine: CanvasEngine,
private newBackground: string
) {
this.oldBackground = engine.background;
}

execute(): void {
this.engine.background = this.newBackground;
}

undo(): void {
this.engine.background = this.oldBackground;
}
}

// 使用
engine.history.execute(
new SetCanvasBackgroundCommand(engine, '#ffffff')
);

2. 发布/订阅模式

通过事件系统实现组件解耦。

// 发布
engine.events.emit('graph:selection-change', { nodes: selected, edges: [], reason: 'manual' });

// 订阅
engine.events.on('graph:selection-change', ({ nodes }) => {
// 处理选择变化
});

3. 工厂模式

节点创建使用工厂模式。

function createNodeByShape(
shape: string,
position: Point
): NodeData {
const template = nodeTemplates[shape];

return {
id: generateId(),
shape,
position,
size: template.size,
data: template.data
};
}

4. 组合模式

组件采用组合模式构建复杂 UI。

<Editor>
<Toolbar>
<UndoRedoAction />
<ZoomAction />
</Toolbar>
<Canvas />
<PropertyPanel>
<NodeProperties />
<EdgeProperties />
</PropertyPanel>
</Editor>

性能优化

1. 懒加载

// 路由懒加载
const Editor = lazy(() => import('@/pages/editor'));

// 示例数据懒加载
const exampleModules = import.meta.glob(
'@/mock/examples/*.json',
{ eager: false }
);

2. 虚拟化

// 大量图形时只渲染可见区域
function getVisibleShapes(shapes: Shape[], viewport: Viewport) {
return shapes.filter(shape =>
isInViewport(shape, viewport)
);
}

3. 防抖/节流

// 防抖:等待输入完成后执行
const debouncedSave = debounce(() => {
saveToServer();
}, 500);

// 节流:限制执行频率
const throttledRender = throttle(() => {
engine.render();
}, 16); // ~60fps

4. Memo 优化

// 避免不必要的重渲染
const ToolbarAction = React.memo<ToolbarActionProps>(
({ engine, label }) => {
return <button>{label}</button>;
},
(prev, next) => prev.engine === next.engine
);

扩展性设计

1. 插件化

引擎功能通过插件扩展。

// 自定义插件
class CustomPlugin implements Plugin {
id = 'custom-plugin';

onAttach(engine: CanvasEngine): void {
// 初始化
}

onDetach(): void {
// 清理
}
}

// 注册
engine.plugins.use(new CustomPlugin());

2. 主题化

支持浅色/深色主题。

// 主题切换
function toggleTheme() {
const newTheme = theme === 'light' ? 'dark' : 'light';

// 更新 DOM
document.documentElement.classList.toggle('dark');

// 更新引擎
engine.setTheme(newTheme);

// 持久化
localStorage.setItem('theme', newTheme);
}

3. 配置化

通过配置文件定制编辑器。

// 节点模板配置
export const nodeTemplates = {
rect: {
size: { width: 120, height: 80 },
data: {
label: '矩形',
fill: '#ffffff',
stroke: '#000000'
}
},
// ...
};

最佳实践

1. TypeScript 类型安全

// 定义清晰的类型
interface NodeData {
id: string;
shape: string;
position: Point;
size: Size;
data?: Record<string, any>;
}

// 使用类型守卫
function isNodeData(data: any): data is NodeData {
return data && typeof data.id === 'string';
}

2. 错误边界

class ErrorBoundary extends React.Component {
componentDidCatch(error: Error) {
console.error('Error:', error);
// 上报错误
}

render() {
return this.props.children;
}
}

3. 资源清理

useEffect(() => {
const engine = new CanvasEngine({ container });

// 清理函数
return () => {
engine.dispose();
};
}, []);

4. 代码分割

// 按路由分割
const routes = [
{
path: '/editor',
element: <Editor />,
// Vite 自动分割
}
];