跳到主要内容

非 React 集成

@ydesign/react-editor 是一套 React 组件库,默认需要 React 运行环境。但这并不意味着你的主工程必须是 React —— 即使你的主站是 Vue / Angular / Svelte / Solid / 原生 JS,只要按下面的思路操作,依然可以无缝嵌入 Ydesign 编辑器。

💡 如果你用的是 Vue 3,建议直接关注 @ydesign/vue-editor(规划中),原生 Vue 版本体验更好,不需要引入 React 运行时。 本文适用于:@ydesign/vue-editor 未发布前的 Vue 项目,或者 Angular / Svelte / 其他框架。

整体思路

主工程(Vue / Angular / Svelte / 原生 JS)

│ 调用一个全局方法 createEditor({ container, ... })

┌──────────────────────────────────────────┐
│ editor.js(独立打包产物) │
│ │
│ React + @ydesign/react-editor + 你对编辑器的定制 │
└──────────────────────────────────────────┘

核心是 把编辑器单独作为一个 React 子项目开发 + 打包,对外只暴露一个建议命名为 createEditor 的函数,主工程按"普通 JS 库"的方式去消费它。主工程完全不感知 React 的存在。

⚠️ 前置条件:虽然主工程不用 React,但定制编辑器本身仍然需要 React 的知识(自定义面板、按钮等都是 React 组件)。如果团队完全不会 React,建议等 @ydesign/vue-editor 发布,或者使用 Ydesign Button 的 iframe 方案。

本文以 parcel 为例,其他打包器(Vite / Rspack / Webpack)思路一致。

1. 建立一个独立的 editor 子项目

在主工程根目录里新建一个 editor/ 目录专门放编辑器代码:

mkdir editor
cd editor
npm init -y
npm install react react-dom @ydesign/react-editor
npm install -D parcel

2. 开发阶段:editor/index.html

这个 HTML 只在本地调试时用,最终不会被主工程引用。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>Ydesign Editor - Dev</title>
<link rel="stylesheet" href="https://unpkg.com/@ydesign/react-editor/dist/style.css" />
<style>
body {
margin: 0;
}
#root {
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="./index.js"></script>
<script>
window.onload = () => {
window.createEditor({ container: document.getElementById('root') });
};
</script>
</body>
</html>

3. 编辑器入口:editor/index.js

所有 React 相关代码只存在于这个文件里。对外只导出一个纯函数 createEditor

import React from 'react';
import ReactDOM from 'react-dom/client';
import { DesignEditorContainer, SidePanelWrap, WorkspaceWrap, createStore, SidePanel } from '@ydesign/react-editor';
import Workspace from '@ydesign/react-editor/canvas/workspace';
import Toolbar from '@ydesign/react-editor/toolbar';
import ZoomButtons from '@ydesign/react-editor/toolbar/zoom-buttons';

// 👇 可以在这里做任意定制:替换面板、添加按钮、改主题等等
const Editor = ({ store }) => (
<DesignEditorContainer style={{ width: '100%', height: '100%' }}>
<SidePanelWrap>
<SidePanel store={store} />
</SidePanelWrap>
<WorkspaceWrap>
<Toolbar store={store} />
<Workspace store={store} />
<ZoomButtons store={store} />
</WorkspaceWrap>
</DesignEditorContainer>
);

/**
* 对外的公共 API,主工程只会调用这一个函数
* @param {HTMLElement} container 挂载节点
* @param {string} [key] 你的 API Key
* @param {Function} [onReady] store 初始化完成后的回调,可用于后续主工程与编辑器通信
*/
export const createEditor = ({ container, key, onReady }) => {
const store = createStore({ key: key || 'YOUR_API_KEY' });
const root = ReactDOM.createRoot(container);
root.render(<Editor store={store} />);

onReady?.(store);

// 返回销毁方法与 store,方便主工程继续调用
return {
store,
destroy: () => root.unmount(),
};
};

// 开发阶段:挂到全局方便 HTML 调试
if (typeof window !== 'undefined') {
window.createEditor = createEditor;
}

4. 启动本地开发

npx parcel ./editor/index.html

默认会启动 http://localhost:1234,在浏览器打开就可以看到编辑器。

推荐把命令写到 editor/package.json

{
"scripts": {
"dev": "parcel ./editor/index.html",
"build": "parcel build ./editor/index.js --no-source-maps"
},
"main": "dist/index.js"
}

5. 打包产出给主工程

npm run build

会在 editor/dist/ 下得到 index.js(包含 React、@ydesign/react-editor 等所有依赖)。

6. 在主工程里消费

无论你的主工程用什么框架,都把 editor/dist/index.js 当作一个普通 ES Module 即可。

Vue 3 示例

<template>
<div ref="containerRef" class="editor-container" />
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { createEditor } from '../editor/dist/index.js';
import '@ydesign/react-editor/dist/style.css';

const containerRef = ref(null);
let editorInstance = null;

onMounted(() => {
editorInstance = createEditor({
container: containerRef.value,
key: import.meta.env.VITE_YDESIGN_KEY,
onReady: store => {
console.log('编辑器已就绪', store);
},
});
});

onBeforeUnmount(() => {
editorInstance?.destroy();
});
</script>

<style scoped>
.editor-container {
width: 100%;
height: 100vh;
}
</style>

Angular 示例(核心片段)

// editor.component.ts
import { Component, ElementRef, AfterViewInit, ViewChild, OnDestroy } from '@angular/core';
import { createEditor } from '../../../editor/dist';

@Component({
selector: 'app-editor',
template: '<div #host class="host"></div>',
styles: [
`
.host {
width: 100%;
height: 100vh;
}
`,
],
})
export class EditorComponent implements AfterViewInit, OnDestroy {
@ViewChild('host') host!: ElementRef<HTMLElement>;
private instance: any;

ngAfterViewInit() {
this.instance = createEditor({
container: this.host.nativeElement,
key: 'YOUR_API_KEY',
});
}

ngOnDestroy() {
this.instance?.destroy();
}
}

原生 JS / Svelte / Solid

和 Vue / Angular 思路完全一致:引入 createEditor,传一个 DOM 节点进去,组件销毁时调用 destroy

7. 主工程与编辑器之间的通信

因为 createEditor 返回 store,主工程可以拿到完整的 MobX-State-Tree 实例。想做双向通信只需要:

const { store } = createEditor({ container, key });

// 主工程 → 编辑器:加载模板
await fetch('/api/designs/42')
.then(r => r.json())
.then(json => store.loadJSON(json));

// 编辑器 → 主工程:监听任何变化
import { reaction } from 'mobx';
reaction(
() => store.toJSON(),
json => {
console.log('设计稿变化', json);
// 自动保存 / 实时推送 / 触发脏标记……
}
);

// 导出图片
const url = await store.toDataURL({ multiplier: 2 });

8. 踩坑提示

  • 依赖冲突:把编辑器放在 editor/ 子目录里并维护独立的 package.json,可以避免与主工程的 React 版本、Babel 配置相互干扰。
  • 样式覆盖:务必同步引入 @ydesign/react-editor/dist/style.css,否则组件样式全部丢失。
  • SSR:编辑器必须在客户端渲染,如果主工程是 Nuxt / Next / SvelteKit 这类 SSR 框架,记得用"仅客户端"包裹(Nuxt 的 <ClientOnly>、Next 的 ssr: false 动态 import 等)。
  • 多实例:一个页面同时初始化多个编辑器是允许的,它们互相隔离。

相关示例

  • Vue 3 + Vite 接入示例:👉 仓库 apps/demo-vue
  • Angular 接入示例:👉 仓库 apps/demo-angular(规划中)

如果你的场景不在本文覆盖范围内,欢迎提 Issue 或参考 Ydesign Button —— 后者完全无需任何 React 知识,适合改造成本敏感的老项目。