非 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 知识,适合改造成本敏感的老项目。