第三章 项目初始化与架构设计
好,咱们正式开始动手了。
这一章,我会带你从零搭起一个低代码可视化编辑器的项目骨架。说白了,就是先把地基打好。地基稳不稳,直接决定后面盖楼会不会塌。我在好几个项目里都见过那种「先写着再说」的团队,结果写到一半发现架构撑不住了,重构成本高得吓人。嗯,咱们不干那种事。
3.1 使用 Vite 搭建项目
为什么选 Vite?
我个人习惯是:新项目首选 Vite。原因很简单——快。尤其是开发阶段的热更新,Vite 基于 ES Module,几乎秒开。你想想看,编辑器这种交互密集型的应用,开发过程中要反复调试组件,如果每次改完等两三秒才刷新,那体验太糟糕了。
初始化命令很简单:
npm create vite@latest lowcode-editor -- --template vue-ts
这里我选了 Vue 3 + TypeScript 的组合。为什么?
- Vue 3 的 Composition API 非常适合编辑器这种复杂状态管理的场景
- TypeScript 能帮我们在编译阶段就发现类型错误,避免运行时踩坑
安装完依赖后,跑一下 npm run dev,看到页面出来,就算第一步完成了。
3.2 目录结构规划
接下来是重头戏——目录怎么分?
我见过很多新手直接把所有代码扔到 src 下面,然后过两周就乱成一锅粥。咱们得从一开始就规划清楚。
这是我个人比较推荐的结构:
src/
├── components/ # 通用UI组件
│ ├── common/ # 按钮、输入框等基础组件
│ └── editor/ # 编辑器专用组件
├── core/ # 核心引擎
│ ├── engine.ts # 渲染引擎
│ ├── event-bus.ts # 事件总线
│ └── plugin-system.ts # 插件系统
├── stores/ # 状态管理
│ ├── editor.ts # 编辑器状态
│ └── project.ts # 项目状态
├── services/ # 服务层
│ ├── api.ts # 接口请求
│ └── storage.ts # 本地存储
├── utils/ # 工具函数
│ ├── dom.ts # DOM操作
│ └── validator.ts # 校验工具
├── types/ # 类型定义
│ └── editor.d.ts # 编辑器相关类型
└── views/ # 页面视图
├── Designer.vue # 设计器主页面
└── Preview.vue # 预览页面
你可能会问:为什么要把 core 单独拎出来?
因为核心引擎是整个编辑器的灵魂。它负责组件的渲染、拖拽、选中、缩放等基础能力。这部分应该和 UI 层解耦,方便后续单独测试或复用。
3.3 模块划分
模块划分这件事,说白了就是「谁该管什么」。
我把编辑器拆成了四个核心模块:
| 模块 | 职责 | 典型文件 |
|---|---|---|
| 渲染引擎 | 将组件树渲染到画布上 | core/engine.ts |
| 状态管理 | 维护组件树、选中状态、历史记录 | stores/editor.ts |
| 事件系统 | 处理拖拽、点击、键盘等交互事件 | core/event-bus.ts |
| 插件系统 | 提供扩展点,支持第三方插件 | core/plugin-system.ts |
每个模块之间通过接口通信,不直接依赖。举个例子:渲染引擎只管「怎么画」,不管「画什么」。组件数据由状态管理提供,事件由事件系统分发。这样改一个模块,其他模块不受影响。
3.4 设计模式选择:MVVM 还是 MVC?
这个问题我经常被问到。我的答案是:MVVM。
为什么?
你想想看,编辑器本质上是一个「数据驱动视图」的应用。用户拖拽一个组件,本质上是修改了组件树的数据,然后视图自动更新。这不就是 MVVM 最擅长的场景吗?
MVC 的问题在于,Controller 层容易变得臃肿。尤其是编辑器这种交互复杂的场景,Controller 里既要处理数据,又要操作 DOM,最后代码会很难维护。
MVVM 的典型分层:
- Model:组件树数据、项目配置
- View:Vue 组件模板,负责渲染
- ViewModel:Vue 的 Composition API 或 Pinia store,负责状态管理和业务逻辑
举个例子,一个简单的组件选中逻辑:
// stores/editor.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useEditorStore = defineStore('editor', () => {
const selectedId = ref(null)
const componentTree = ref([])
function selectComponent(id: string) {
selectedId.value = id
// 这里可以触发其他副作用,比如高亮、显示属性面板
}
return { selectedId, componentTree, selectComponent }
})
在 View 层,只需要绑定这个状态:
<template>
<div
v-for="node in componentTree"
:class="{ active: node.id === selectedId }"
@click="store.selectComponent(node.id)"
>
{{ node.name }}
</div>
</template>
你看,View 层只负责「展示」和「触发事件」,不关心数据怎么变。ViewModel 层管理状态和逻辑。Model 层就是纯粹的数据结构。各司其职,清清楚楚。
3.5 本章小结
这一章我们做了三件事:
- 用 Vite 搭好了项目骨架
- 规划了清晰的目录结构
- 选择了 MVVM 作为核心架构模式
嗯,地基打好了。下一章咱们开始写真正的编辑器核心——渲染引擎。到时候你会看到,今天做的这些架构决策,会让我们写代码时有多顺畅。
记住一句话:好的架构不是设计出来的,是演化出来的。但前提是,你得先有一个不差的起点。