# 非 React 界面开发
秋之盒插件的 App 和 Card 支持 自定义 DOM 挂载,不依赖 React。适用于需要直接操作 DOM 的场景:Canvas 渲染、WebGL、集成非 React 库、或追求极致性能的场景。
# 两种渲染模式
AutumnApp 和 AutumnCard 都支持两种互斥的渲染模式:
| 模式 | 字段 | 说明 |
|---|---|---|
| React | component: React.FC<AppProps> | 大多数场景,使用 React + Ant Design |
| 自定义 DOM | mount: (container: HTMLElement) => () => void | 直接操作 DOM,不依赖 React |
提供 component 或 mount,二选一。两者都提供时使用 component。
# 基本用法
import type { AutumnApp } from '@autumnbox/sdk';
export const CanvasApp: AutumnApp = {
id: 'canvas-demo',
name: 'app.name.canvas_demo',
icon: 'data:image/svg+xml,...',
mount: (container: HTMLElement) => {
// container 是一个空的 DOM 容器
const canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 600;
canvas.style.width = '100%';
canvas.style.height = '100%';
container.appendChild(canvas);
const ctx = canvas.getContext('2d')!;
ctx.fillStyle = '#6366f1';
ctx.fillRect(0, 0, 800, 600);
ctx.fillStyle = 'white';
ctx.font = '32px system-ui';
ctx.fillText('Hello from Canvas!', 50, 300);
// 返回清理函数
return () => {
canvas.remove();
};
},
};
关键点:
container是宿主提供的空<div>,你可以在里面放任何 DOM 元素- 必须返回一个清理函数
() => void,在 Tab 关闭或组件卸载时调用 - 清理函数负责移除 DOM 元素、取消定时器、断开连接等
# 实际示例:xterm 终端
xterm.js 等库不依赖 React,用 mount 模式最自然:
import type { AutumnApp } from '@autumnbox/sdk';
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
export const TerminalApp: AutumnApp = {
id: 'terminal',
name: 'app.name.terminal',
icon: 'data:image/svg+xml,...',
shallSelectAdbDevice: true,
mount: (container: HTMLElement) => {
const term = new Terminal({
theme: { background: '#1e1e2e' },
fontSize: 14,
});
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(container);
fitAddon.fit();
// 写入欢迎信息
term.writeln('Terminal ready.');
// 响应容器尺寸变化
const observer = new ResizeObserver(() => fitAddon.fit());
observer.observe(container);
return () => {
observer.disconnect();
term.dispose();
};
},
};
# 访问插件服务
mount 模式下无法使用 React Hooks,需要通过全局方式获取 Service:
import type { AutumnApp, PluginContext } from '@autumnbox/sdk';
let ctx: PluginContext;
// main.ts 中保存 context
export function main(context: PluginContext): void {
ctx = context;
}
export const MyDomApp: AutumnApp = {
id: 'dom-app',
name: 'app.name.dom_app',
icon: 'data:image/svg+xml,...',
mount: (container: HTMLElement) => {
// 通过 context 获取服务
const shell = ctx.getService(ShellService);
const title = ctx.t('app.name.dom_app');
const heading = document.createElement('h2');
heading.textContent = title.value;
container.appendChild(heading);
// 订阅翻译变化
const unsub = title.subscribe((newTitle) => {
heading.textContent = newTitle;
});
return () => {
unsub();
heading.remove();
};
},
};
如果你需要响应式地访问服务,context.t(key) 返回 IReadonlyState<string>,可以用 .subscribe() 订阅变化。其他 Service 的响应式状态也同理。
# Card 的 mount 模式
Card 同样支持 mount:
import type { AutumnCard } from '@autumnbox/sdk';
export const StatusCard: AutumnCard = {
id: 'status',
name: 'card.name.status',
order: 10,
mount: (container: HTMLElement) => {
container.innerHTML = `
<div style="padding: 12px;">
<div style="font-weight: 600;">设备状态</div>
<div style="color: #888; font-size: 13px;">运行中</div>
</div>
`;
return () => {
container.innerHTML = '';
};
},
};
# 何时选择 mount 模式
| 场景 | 推荐 |
|---|---|
| 标准表单/列表/详情 UI | component(React) |
| Canvas/WebGL 渲染 | mount |
| 集成 xterm.js 等非 React 库 | mount |
| 集成 Monaco Editor | mount |
| 需要极致 DOM 性能控制 | mount |
| 需要使用 antd 组件 | component(React) |
mount 模式无法使用 antd 组件和 React Hooks。如果需要混合使用 React 和自定义 DOM,推荐用 component 模式 + useRef 持有 DOM 引用。
# 混合模式:React 中嵌入自定义 DOM
如果你主要用 React,但局部需要直接操作 DOM(如 Canvas),推荐在 component 中使用 useRef:
import React, { useRef, useEffect } from 'react';
import type { AutumnApp } from '@autumnbox/sdk';
const CanvasAppView: React.FC = () => {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d')!;
// 绑定操作...
return () => {
// 清理
};
}, []);
return <canvas ref={canvasRef} style={{ width: '100%', height: '100%' }} />;
};
export const CanvasApp: AutumnApp = {
id: 'canvas',
name: 'app.name.canvas',
icon: 'data:image/svg+xml,...',
component: CanvasAppView,
};
这是最灵活的方式:享受 React 的生命周期管理,同时有完全的 DOM 控制权。