第9章:插件系统架构
本章概要
核心问题:如何设计一个可扩展的插件系统?
无限画布的功能高度模块化,通过插件系统实现功能的解耦和扩展。本章将深入分析:
- PluginSystem 设计:插件的注册、管理和生命周期
- 插件分类体系:渲染插件与业务插件的分层设计
- 核心插件解析:标尺、参考线、选区高亮等关键插件
- 自定义插件开发:插件开发规范和最佳实践
目录
9.1 PluginSystem 设计
9.1.1 核心架构
PluginSystem 是插件管理的核心类,负责插件的注册、查询和销毁:
typescript
// 来源:infinite-renderer/src/plugins/plugin-system.ts
export interface PluginSystemOptions {
viewport: IViewport<IPageVm>;
vmEngine: IVmEngine;
context: IContext;
eventBoundary: IEventBoundary;
}
class PluginSystem implements IPluginSystem {
private viewport: IViewport<IPageVm>;
private vmEngine: IVmEngine;
private context: IContext;
private eventBoundary: IEventBoundary;
private pluginMap: Map<string, IPlugin> = new Map();
private id = 0;
constructor(options: PluginSystemOptions) {
const { viewport, vmEngine, context, eventBoundary } = options;
this.viewport = viewport;
this.vmEngine = vmEngine;
this.context = context;
this.eventBoundary = eventBoundary;
}
}架构图:
┌─────────────────────────────────────────────────────────────┐
│ PluginSystem │
├─────────────────────────────────────────────────────────────┤
│ │
│ 依赖注入: │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Viewport │ │ VmEngine │ │ Context │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ EventBoundary │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 插件注册表: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ pluginMap: Map<string, IPlugin> │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ "Outline" │ │ "Highlight" │ │ │
│ │ │ OutlinePlugin│ │HighlightPlugin│ │ │
│ │ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ "Ruler" │ │ "Guide" │ │ │
│ │ │ RulerPlugin │ │ GuidePlugin │ │ │
│ │ └──────────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘9.1.2 插件注册机制
PluginSystem 提供多种注册方式:
typescript
// 来源:infinite-renderer/src/plugins/plugin-system.ts
class PluginSystem implements IPluginSystem {
// 创建插件实例(不注册)
createPlugin<T extends IPlugin>(Class: new (props: IPluginOptions) => T): T {
const instance = new Class({
viewport: this.viewport,
vmEngine: this.vmEngine,
context: this.context,
eventBoundary: this.eventBoundary,
system: this,
});
// 触发 onCreated 生命周期钩子
instance.onCreated();
return instance;
}
// 注册插件(创建 + 添加到注册表)
registerPlugin<T extends IPlugin>(
Class: new (props: IPluginOptions) => T,
name = Class.name, // 默认使用类名
): T {
const plugin = this.createPlugin(Class);
// 自动生成名称
if (!name) {
name = `plugin-${++this.id}`;
}
this.addPlugin(name, plugin);
return plugin;
}
// 直接添加已创建的插件
addPlugin(name: string, plugin: IPlugin): void {
if (this.pluginMap.has(name)) {
throw new Error(`Duplicated plugin name: '${name}'`);
}
this.pluginMap.set(name, plugin);
// 开发模式下挂载到 this 以便调试
// @ts-expect-error
if (import.meta.env.APP_NODE_ENV === 'development') {
this[name] = plugin;
}
}
}注册方式对比:
| 方法 | 用途 | 是否注册 | 是否调用 onCreated |
|---|---|---|---|
createPlugin | 创建临时/独立插件 | ❌ | ✓ |
registerPlugin | 标准注册流程 | ✓ | ✓ |
addPlugin | 添加已创建的插件 | ✓ | ❌(已调用) |
9.1.3 生命周期管理
插件的获取和移除:
typescript
// 来源:infinite-renderer/src/plugins/plugin-system.ts
class PluginSystem implements IPluginSystem {
// 移除插件
removePlugin(name: string): IPlugin | undefined;
removePlugin<T extends IPlugin>(Class: new (props: IPluginOptions) => T): IPlugin | undefined;
removePlugin(name: Function | string): IPlugin | undefined {
// 支持按类名或名称移除
if (typeof name === 'function') {
name = name.name;
}
const plugin = this.pluginMap.get(name);
this.pluginMap.delete(name);
// 触发 onDestroy 生命周期钩子
plugin?.onDestroy();
return plugin;
}
// 断言获取插件(不存在则抛错)
assertPlugin(name: string): IPlugin;
assertPlugin<T extends IPlugin>(Class: T): T;
assertPlugin(name: Function | string): IPlugin {
if (typeof name === 'function') {
name = name.name;
}
const plugin = this.pluginMap.get(name);
if (!plugin) {
throw new Error(`Plugin not found: '${name}'`);
}
return plugin;
}
// 销毁所有插件
destroy(): void {
for (const [_, value] of this.pluginMap) {
value.onDestroy();
}
this.pluginMap.clear();
}
}生命周期流程图:
插件生命周期:
┌────────────┐ ┌────────────┐ ┌────────────┐
│ 创建阶段 │──────→│ 运行阶段 │──────→│ 销毁阶段 │
└────────────┘ └────────────┘ └────────────┘
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ constructor│ │ toggle() │ │ onDestroy()│
│ │ │ render() │ │ │
│ onCreated()│ │ clear() │ │ │
└────────────┘ └────────────┘ └────────────┘9.2 插件基类体系
9.2.1 BasePlugin 基类
所有插件的基础类,提供通用功能:
typescript
// 来源:infinite-renderer/src/plugins/base/base-plugin.ts
export class BasePlugin<T extends EventEmitter.ValidEventTypes = object>
extends EventEmitter<T>
implements IPlugin
{
// 插件是否禁用
get disabled(): boolean {
return this._disabled;
}
protected _disabled = false;
// 核心依赖注入
protected viewport: IViewport<IPageVm>;
protected context: IContext;
protected vmEngine: IVmEngine;
protected system: IPluginSystem;
protected eventBoundary: IEventBoundary;
constructor(options: IPluginOptions) {
super();
const { viewport, context, vmEngine, system, eventBoundary } = options;
this.viewport = viewport;
this.context = context;
this.vmEngine = vmEngine;
this.system = system;
this.eventBoundary = eventBoundary;
}
// 生命周期钩子
onCreated(): void {
// 子类覆写
}
onDestroy(): void {
// 子类覆写
}
// 启用/禁用插件
toggle(value: boolean): void {
this._disabled = !value;
}
// 下一帧执行
nextTick<T = any>(fn: TickerCallback<T>, context: T, priority?: UPDATE_PRIORITY) {
this.viewport.app.ticker.addOnce<T>(fn, context, priority);
}
}BasePlugin 特性:
| 特性 | 说明 |
|---|---|
| 事件发射器 | 继承 EventEmitter,支持自定义事件 |
| 依赖注入 | 自动注入 viewport、vmEngine、context 等 |
| 禁用控制 | toggle() 方法控制启用/禁用状态 |
| 帧调度 | nextTick() 在下一渲染帧执行回调 |
9.2.2 LayerPlugin 图层插件
用于创建渲染图层的插件基类:
typescript
// 来源:infinite-renderer/src/plugins/base/layer-plugin.ts
export abstract class LayerPlugin<
T extends EventEmitter.ValidEventTypes = object,
> extends BasePlugin<T> {
// 子类必须提供 view 容器
protected abstract view: Container;
onCreated(): void {
// 添加到视口图层
this.viewport.addLayer(this.view);
// 注册每帧更新
this.viewport.app.ticker.add(this.update, this, UPDATE_PRIORITY.NORMAL);
}
onDestroy(): void {
// 从视口移除图层
this.viewport.removeLayer(this.view);
// 取消帧更新注册
this.viewport.app.ticker.remove(this.update, this);
}
@Bind
update(dt: number) {
try {
if (this.disabled) {
this.view.visible = false;
this.clear();
} else {
this.view.visible = true;
this.render(dt);
}
} catch (error) {
console.error(error);
}
}
// 子类实现:渲染逻辑
abstract render(dt: number): void;
// 子类实现:清空逻辑
abstract clear(): void;
}LayerPlugin 渲染流程:
每帧更新流程:
Ticker.update()
│
▼
┌──────────────────┐
│ LayerPlugin. │
│ update() │
└────────┬─────────┘
│
▼
┌──────────────────┐ ┌──────────────────┐
│ disabled === ? │────→│ Yes: clear() │
└────────┬─────────┘ │ view.visible = │
│ No │ false │
▼ └──────────────────┘
┌──────────────────┐
│ view.visible = │
│ true │
│ render(dt) │
└──────────────────┘9.3 双层插件架构
无限画布采用双层插件架构:渲染层插件和业务层插件。
9.3.1 渲染层插件
渲染层插件位于 infinite-renderer 包,直接操作 PixiJS 对象:
渲染层插件目录:
infinite-renderer/src/plugins/
├── background/ # 背景插件
│ ├── background-plugin.ts
│ └── background-vm.ts
└── base/ # 基类
├── base-plugin.ts
└── layer-plugin.ts
infinite-plugins/src/renderer-plugins/
├── outline/ # 选区高亮
├── highlight/ # Hover 高亮
├── guide/ # 参考线
├── ruler/ # 标尺
├── scrollbar/ # 滚动条
├── connector/ # 连接线
├── magic-brush/ # 魔法画笔
└── ...渲染层插件特点:
| 特点 | 说明 |
|---|---|
| 继承 | 继承 BasePlugin 或 LayerPlugin |
| 依赖 | 直接使用 viewport、vmEngine、eventBoundary |
| 渲染 | 使用 PixiJS Graphics/Container 渲染 |
| 注册 | 通过 surface.plugin.registerPlugin() 注册 |
9.3.2 业务层插件
业务层插件位于 infinite-plugins/src/plugins,封装业务逻辑:
业务层插件目录:
infinite-plugins/src/plugins/
├── viewport-plugin/ # 视口控制
├── hand-plugin/ # 抓手工具
├── hover-plugin/ # Hover 检测
├── hotkeys-plugin/ # 快捷键
├── guide-plugin/ # 参考线业务
├── ruler-plugin/ # 标尺业务
├── scrollbar-plugin/ # 滚动条业务
├── outline-plugin/ # 选区高亮业务
├── connector-plugin/ # 连接线业务
└── ...业务层插件接口:
typescript
// 来源:@editor/framework 的 Plugin 接口
export interface Plugin {
name: string;
version: string;
// 注册命令
commands?(editor: VPEditor): Record<string, Function>;
// 注册钩子
hooks?(editor: VPEditor): Record<string, Function>;
// 注册事件监听
events?(editor: VPEditor): Record<string, Function>;
// 挂载时调用
mount?(editor: VPEditor): void;
// 销毁时调用
destroy?(): void;
}9.3.3 两层协作模式
业务层插件通常会创建并管理一个渲染层插件:
typescript
// 来源:infinite-plugins/src/plugins/ruler-plugin/ruler-plugin.ts
function createRulerPlugin(): Plugin {
let app: BoardSurface | null = null;
let ruler: RulerPlugin | null = null; // 渲染层插件实例
return {
name: 'ruler',
version: '1.0.0',
commands(editor) {
return {
// 初始化时创建渲染层插件
initialize(_app: BoardSurface) {
app = _app;
// 注册渲染层插件
ruler = app.plugin.registerPlugin(RulerPlugin);
ruler.toggle(visibleRef.value);
ruler.setArea(areaRef.value);
// 设置业务逻辑
useRulerPlugin(editor, ruler, visibleRef);
// ...
},
toggleVisible(visible: boolean) {
visibleRef.value = visible;
},
};
},
// 销毁时移除渲染层插件
destroy() {
app?.plugin.removePlugin(RulerPlugin);
app = null;
},
};
}双层协作图:
┌─────────────────────────────────────────────────────────────┐
│ 业务层插件 │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ createRulerPlugin() │ │
│ │ │ │
│ │ commands: { │ │
│ │ initialize(app) { │ │
│ │ ruler = app.plugin.registerPlugin(RulerPlugin) │───┼──┐
│ │ } │ │ │
│ │ toggleVisible(visible) { ... } │ │ │
│ │ } │ │ │
│ │ │ │ │
│ │ events: { ... } │ │ │
│ │ │ │ │
│ │ destroy() { │ │ │
│ │ app.plugin.removePlugin(RulerPlugin) │───┼──┤
│ │ } │ │ │
│ └──────────────────────────────────────────────────────┘ │ │
│ │ │
└─────────────────────────────────────────────────────────────┘ │
│
│
┌─────────────────────────────────────────────────────────────┐ │
│ 渲染层插件 │ │
│ │ │
│ ┌──────────────────────────────────────────────────────┐ │ │
│ │ class RulerPlugin extends LayerPlugin │◀──┼──┘
│ │ │ │
│ │ protected view = new Container(); │ │
│ │ │ │
│ │ render() { │ │
│ │ // 绘制标尺刻度 │ │
│ │ } │ │
│ │ │ │
│ │ clear() { │ │
│ │ // 清空绘制 │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘9.4 核心插件解析
9.4.1 OutlinePlugin 选区高亮
用于绘制选中元素的边框:
typescript
// 来源:infinite-plugins/src/renderer-plugins/outline/outline-plugin.ts
export class OutlinePlugin extends LayerPlugin {
protected view = new Graphics();
// 选中的元素集合
private selected = new Set<IBaseElementVm>();
// 协作者选中的元素
private collaborator = new Map<IBaseElementVm, ColorSource>();
// 自定义样式
private customStrokeWidth: number | null = null;
private customColor: ColorSource | null = null;
onCreated(): void {
this.view.name = 'OutlinePlugin';
super.onCreated();
}
// 选中元素
select(models: BaseElementModel[]): void {
this.toggle(true);
this.selected.clear();
for (const model of models) {
const view = this.vmEngine.getVm(model);
if (view) {
this.selected.add(view);
}
}
}
// 取消选中
unselect(): void {
this.selected.clear();
this.checkDisable();
}
// 渲染选区边框
render(): void {
const { screen } = this.viewport.app;
const graphics = this.view.clear();
const { zoom } = this.viewport;
// 区分锁定和未锁定元素
const locked: IBaseElementVm[] = [];
const unlocked: IBaseElementVm[] = [];
for (const element of this.selected) {
const model = element.getModel();
if (model.lock) {
locked.push(element);
} else {
unlocked.push(element);
}
}
// 渲染未锁定元素(蓝色边框)
const strokeWidth = this.customStrokeWidth ?? DEFAULT_STROKE_WIDTH;
const color = this.customColor ?? DEFAULT_COLOR();
graphics.strokeStyle(strokeWidth, color);
for (const element of unlocked) {
const rect = element.getModelBounds(false, RECTANGLE);
if (screen.intersects(rect) && element.view.visible) {
renderOutline(graphics, element, zoom);
}
}
// 渲染协作者选中元素(自定义颜色)
for (const [element, color] of this.collaborator) {
const rect = element.getModelBounds(false, RECTANGLE);
if (screen.intersects(rect) && element.view.visible) {
graphics.strokeStyle(2, color);
renderOutline(graphics, element, zoom);
}
}
// 渲染锁定元素(红色边框)
graphics.strokeStyle(2, 0xf54531);
for (const element of locked) {
const rect = element.getModelBounds(false, RECTANGLE);
if (screen.intersects(rect) && element.view.visible) {
renderOutline(graphics, element, zoom);
}
}
}
clear(): void {
this.view.clear();
}
}9.4.2 HighlightPlugin Hover 高亮
用于绘制 Hover 状态的元素高亮:
typescript
// 来源:infinite-plugins/src/renderer-plugins/highlight/highlight-plugin.ts
export class HighlightPlugin extends LayerPlugin {
protected view = new Graphics();
// 激活的元素列表
protected activated: IBaseElementVm[] = [];
// 高亮颜色
protected highlightColor: ColorSource = getCssValue('--transform-border-color-regular') || 0x4d7cff;
onCreated(): void {
this.view.name = 'HighlightPlugin';
super.onCreated();
}
// 激活元素高亮
activate(models: BaseElementModel[]): void {
this.activated.length = 0;
for (const model of models) {
const view = this.vmEngine.getVm(model);
if (view) {
this.activated.push(view);
}
}
this.checkDisable();
}
// 取消激活
inactivate(): void {
this.activated.length = 0;
this.checkDisable();
}
// 设置高亮颜色
setColor(color: ColorSource) {
this.highlightColor = color;
}
render(): void {
const { screen } = this.viewport.app;
const temp = new Rectangle();
const graphics = this.view.clear();
graphics.strokeStyle(2, this.highlightColor);
for (const element of this.activated) {
if (!element || element.destroyed) continue;
const rect = element.getModelBounds(false, temp);
if (screen.intersects(rect)) {
element.view.updateTransform();
renderOutline(graphics, element, this.viewport.zoom);
}
}
}
clear(): void {
this.view.clear();
}
}9.4.3 GuidePlugin 参考线
参考线是最复杂的插件之一,支持:
- 页面级和画板级参考线
- 拖拽创建/移动/删除
- Hover/Focus 状态
- 高亮线段
- 协作状态
typescript
// 来源:infinite-plugins/src/renderer-plugins/guide/guide-plugin.ts
export class GuidePlugin extends LayerPlugin<GuideEvents> {
static GUIDE_EPSILON = 1; // 参考线精度
// 默认样式
static DEFAULT_STYLE: GuideStyle = {
lineWidth: 1,
lineColor: '#E17EFF',
hoverColor: '#CF33FF',
focusColor: '#2254F4',
disabledColor: '#B4B8BF',
};
protected view: Container = new Container();
protected guideLayer = new Graphics(); // 参考线图层
protected segmentLayer = new Graphics(); // 高亮线段图层
protected extendLayer = new Graphics(); // 延长线图层
protected manager: GuideManager; // 参考线数据管理器
protected editor: GuideEditor; // 参考线编辑器
protected ruler!: RulerPlugin; // 依赖的标尺插件
constructor(options: IPluginOptions) {
super(options);
this.manager = new GuideManager();
// 获取依赖的 RulerPlugin
this.ruler = this.system.assertPlugin(RulerPlugin);
this.editor = new GuideEditor({
viewport: this.viewport,
manager: this.manager,
ruler: this.ruler,
});
// ...
}
onCreated(): void {
this.view.name = 'GuidePlugin';
this.view.addChild(this.guideLayer, this.segmentLayer, this.extendLayer);
if (!this.disabled) {
this.editor.activate();
}
// 事件转发
this.editor.on('pointerenter', (event) => this.emit('pointerenter', event));
this.editor.on('pointerleave', (event) => this.emit('pointerleave', event));
this.editor.on('focus', (event) => this.emit('focus', event));
this.editor.on('dragstart', (event) => this.emit('dragstart', event));
this.editor.on('dragmove', (event) => this.handleDragMove(event));
this.editor.on('dragend', (event) => this.handleDragEnd(event));
this.ruler.on('pullout', this.handleRulerPullout, this);
// 将参考线图层注册为事件遮罩
this.eventBoundary.addCoverMask(this.guideLayer);
super.onCreated();
}
onDestroy(): void {
this.editor.inactivate();
this.ruler.off('pullout', this.handleRulerPullout, this);
this.eventBoundary.removeCoverMask(this.guideLayer);
super.onDestroy();
}
render(): void {
this.guideLayer.clear();
this.segmentLayer.clear();
this.extendLayer.clear();
this.manager.clear();
// 添加页面参考线
this.addGuides(this.manager, this.viewport.page, null);
// 添加画板参考线
for (const child of this.viewport.page.children) {
const model = child.getModel();
if (model.type !== 'layout') continue;
const bounds = child.getBounds(false);
if (screen.intersects(bounds)) {
this.addGuides(this.manager, child, bounds);
}
}
// 渲染参考线
this.guideLayer.beginFill(this.style.lineColor);
this.renderGuides(this.manager);
// 渲染 Hover 状态
if (this.editor.hoverState && !this.editor.hoverState.disabled) {
// ...
}
// 渲染 Focus 状态
if (this.editor.focusState && !this.editor.focusState.disabled) {
// ...
}
}
}9.4.4 RulerPlugin 标尺
标尺插件用于显示刻度尺:
typescript
// 核心功能
export class RulerPlugin extends LayerPlugin {
protected view = new Container();
// X/Y 轴标尺
protected xRuler: Ruler;
protected yRuler: Ruler;
// 设置标尺区域
setArea(area: Rectangle | null): void;
// 设置标尺作用域(画板)
setScope(scope: LayoutModel | null): void;
// 判断是否超出标尺范围
isBeyondXRuler(y: number): boolean;
isBeyondYRuler(x: number): boolean;
}9.4.5 ScrollbarPlugin 滚动条
虚拟滚动条插件:
typescript
// 来源:infinite-plugins/src/plugins/scrollbar-plugin/scrollbar-plugin.ts
export function createScrollbarPlugin(options: ScrollbarPluginOptions = {}): Plugin {
let app: BoardSurface | undefined;
let scrollbar: ScrollbarPlugin;
return {
name: 'scrollbar',
version: '1.0.0',
commands(editor) {
return {
initialize(_app: BoardSurface) {
app = _app;
scrollbar = app.plugin.registerPlugin(ScrollbarPlugin);
useScrollbar(editor, scrollbar);
useExpand(editor, scrollbar);
},
// 调整视口位置
adjustPosition(x, y, frame, expand, clientWidth, clientHeight, padding) {
return adjustPosition(x, y, frame, expand, clientWidth, clientHeight, padding);
},
};
},
// 钩子:拦截位置调整
hooks(editor) {
return {
adjustPosition(next, x, y, model) {
// ... 加入扩展区域后的二次矫正
},
};
},
destroy() {
app?.plugin.removePlugin(ScrollbarPlugin);
},
};
}9.4.6 BackgroundPlugin 背景
画布背景插件:
typescript
// 来源:infinite-renderer/src/plugins/background/background-plugin.ts
export class BackgroundPlugin extends BasePlugin {
private backgroundVm: BackgroundVm = new BackgroundVm();
private backgroundMode: BackgroundMode = 'none';
onCreated(): void {
super.onCreated();
this.viewport.addBackgroundLayer(this.backgroundVm.view);
this.viewport.on('transform', this.handleTransform, this);
this.viewport.on('resize', this.handleResize, this);
}
onDestroy(): void {
super.onDestroy();
this.backgroundVm.destroy(true);
this.viewport.off('transform', this.handleTransform, this);
this.viewport.off('resize', this.handleResize, this);
}
private updateBackground(): void {
const { position, zoom, size } = this.viewport;
const { resolution } = this.viewport.app.renderer;
this.backgroundVm.setState({
x: position.x,
y: position.y,
width: size.width,
height: size.height,
zoom,
resolution,
background: this.backgroundMode,
});
}
// 设置背景模式
setBackground(mode: BackgroundMode): void {
this.backgroundMode = mode;
this.updateBackground();
}
// 清空背景
clear(): void {
this.backgroundMode = 'none';
this.updateBackground();
}
}9.5 自定义插件开发
9.5.1 插件开发流程
开发一个自定义插件的步骤:
1. 确定插件类型
├── 需要渲染图层? → 继承 LayerPlugin
└── 只需要功能? → 继承 BasePlugin
2. 创建渲染层插件(如需要)
├── 定义 view 容器
├── 实现 render() 方法
└── 实现 clear() 方法
3. 创建业务层插件
├── 定义 commands
├── 定义 events
└── 管理渲染层插件生命周期
4. 注册插件
└── 在编辑器初始化时注册9.5.2 开发规范
命名规范:
typescript
// 渲染层插件:PascalCase + Plugin 后缀
export class MyCustomPlugin extends LayerPlugin { }
// 业务层插件:createXxxPlugin 工厂函数
export function createMyCustomPlugin(): Plugin { }生命周期规范:
typescript
// 正确:在 onCreated 中初始化资源
onCreated(): void {
super.onCreated();
this.bindEvents();
}
// 正确:在 onDestroy 中清理资源
onDestroy(): void {
this.unbindEvents();
super.onDestroy();
}性能规范:
typescript
// 避免在 render 中创建对象
const tempRect = new Rectangle(); // 复用对象
render(): void {
// 使用预分配的对象
const bounds = element.getBounds(false, tempRect);
// 视口剔除
if (!screen.intersects(bounds)) return;
}9.5.3 完整示例
创建一个显示元素信息的调试插件:
typescript
// debug-info-plugin.ts - 渲染层插件
import { LayerPlugin } from '@editor/infinite-renderer';
import { Graphics, Text, Container } from '@piso/piso-js';
export class DebugInfoPlugin extends LayerPlugin {
protected view = new Container();
private graphics = new Graphics();
private text = new Text('', { fontSize: 12, fill: 0x333333 });
private targetElement: IBaseElementVm | null = null;
onCreated(): void {
this.view.name = 'DebugInfoPlugin';
this.view.addChild(this.graphics, this.text);
super.onCreated();
}
setTarget(element: IBaseElementVm | null): void {
this.targetElement = element;
this.toggle(!!element);
}
render(): void {
if (!this.targetElement) return;
const bounds = this.targetElement.getBounds();
const model = this.targetElement.getModel();
// 绘制背景
this.graphics.clear();
this.graphics.beginFill(0xffffff, 0.9);
this.graphics.drawRoundedRect(bounds.right + 10, bounds.top, 200, 80, 4);
this.graphics.endFill();
// 显示信息
this.text.text = [
`Type: ${model.type}`,
`UUID: ${model.uuid.slice(0, 8)}...`,
`Size: ${model.width.toFixed(0)} × ${model.height.toFixed(0)}`,
`Position: (${model.left.toFixed(0)}, ${model.top.toFixed(0)})`,
].join('\n');
this.text.position.set(bounds.right + 20, bounds.top + 10);
}
clear(): void {
this.graphics.clear();
this.text.text = '';
}
}typescript
// debug-info-plugin.ts - 业务层插件
import type { Plugin } from '@editor/framework';
import { DebugInfoPlugin } from '../renderer-plugins/debug-info';
export function createDebugInfoPlugin(): Plugin {
let app: BoardSurface | null = null;
let debugInfo: DebugInfoPlugin | null = null;
return {
name: 'debug-info',
version: '1.0.0',
commands(editor) {
return {
initialize(_app: BoardSurface) {
app = _app;
debugInfo = app.plugin.registerPlugin(DebugInfoPlugin);
},
show(element: BaseElementModel) {
const vm = app?.vmEngine.getVm(element);
debugInfo?.setTarget(vm || null);
},
hide() {
debugInfo?.setTarget(null);
},
};
},
events(editor) {
return {
'element.select': (elements: BaseElementModel[]) => {
if (elements.length === 1) {
editor.plugins.invokeCommand('debug-info:show', elements[0]);
} else {
editor.plugins.invokeCommand('debug-info:hide');
}
},
};
},
destroy() {
app?.plugin.removePlugin(DebugInfoPlugin);
app = null;
debugInfo = null;
},
};
}9.6 本章小结
插件系统架构图
┌─────────────────────────────────────────────────────────────┐
│ 插件系统架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 业务层插件 │ │
│ │ │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │viewport│ │ hand │ │ hover │ │hotkeys │ │ │
│ │ │-plugin │ │-plugin │ │-plugin │ │-plugin │ │ │
│ │ └───┬────┘ └────────┘ └───┬────┘ └────────┘ │ │
│ │ │ │ │ │
│ │ ┌───┴────┐ ┌────────┐ ┌───┴────┐ ┌────────┐ │ │
│ │ │ ruler │ │ guide │ │outline │ │scrollbar│ │ │
│ │ │-plugin │ │-plugin │ │-plugin │ │-plugin │ │ │
│ │ └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘ │ │
│ │ │ │ │ │ │ │
│ └──────┼──────────┼──────────┼──────────┼─────────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PluginSystem │ │
│ │ │ │
│ │ registerPlugin() / removePlugin() / assertPlugin() │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 渲染层插件 │ │
│ │ │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │ Ruler │ │ Guide │ │Outline │ │Scrollbar│ │ │
│ │ │ Plugin │ │ Plugin │ │ Plugin │ │ Plugin │ │ │
│ │ └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘ │ │
│ │ │ │ │ │ │ │
│ │ └──────────┴──────────┴──────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ LayerPlugin / BasePlugin │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘关键代码路径
| 模块 | 文件路径 |
|---|---|
| PluginSystem | infinite-renderer/src/plugins/plugin-system.ts |
| BasePlugin | infinite-renderer/src/plugins/base/base-plugin.ts |
| LayerPlugin | infinite-renderer/src/plugins/base/layer-plugin.ts |
| OutlinePlugin | infinite-plugins/src/renderer-plugins/outline/outline-plugin.ts |
| HighlightPlugin | infinite-plugins/src/renderer-plugins/highlight/highlight-plugin.ts |
| GuidePlugin | infinite-plugins/src/renderer-plugins/guide/guide-plugin.ts |
| RulerPlugin | infinite-plugins/src/renderer-plugins/ruler/ruler-plugin.ts |
| BackgroundPlugin | infinite-renderer/src/plugins/background/background-plugin.ts |
核心要点
- 双层架构:渲染层负责绑定,业务层封装逻辑
- 依赖注入:通过
IPluginOptions注入核心依赖 - 生命周期:
onCreated()初始化,onDestroy()清理 - LayerPlugin:自动管理图层添加/移除和帧更新
- 插件协作:通过
system.assertPlugin()获取依赖插件
下一章预告
第10章将讨论性能优化策略,包括:
- 虚拟化渲染
- 纹理管理
- 渲染优化
- 内存优化
- 性能监控
