05-GUI与前端组件化
GUI 与前端组件化
GUI 是子系统,MVVM 是内部的职责关系;前端(React/Vue)本质上是把「组件树/页面描述」提升为框架核心输入的 GUI 框架。本篇承接 04-系统角色/07、08(GUI 入口拆解)与 10-桌面端架构/04(GUI 的演化与三条流程),回答:GUI 内部怎么按组件组织,前端框架在这套体系里处在什么位置。
一、GUI 是子系统,MVVM 是内部的职责关系
GUI 适合看成一个子系统,内部可拆成两类核心职责:
1 | GUI 子系统 |
但 MVVM 不是「GUI 的两个子系统」,而是一种职责关系:
1 | View → ViewModel → Model / Business API |
- View:显示、布局、控件、绘制、用户输入。
- ViewModel:GUI 状态、状态转换、用户操作转换、调用业务接口、把业务结果转换成 GUI 可显示数据。
- Model:在这套架构里不一定要叫 Model,可能就是「业务组件 API → 业务子系统」。
GUI 的核心闭环:
1 | 用户输入 → View → ViewModel → 业务 API → 状态变化 |
1.1 View 与 Control 由 Application 组装(计数器例子)
View 和 Control 不是「两个类互相调用」,而是 Application 在组装时把它们连起来:
1 | main → Application → { Counter, CounterControl, CounterView } |
1 | class Counter { |
点击后:用户点击 → View/GUI事件 → Control::on_increment_clicked() → Counter::increment() → View::set_value()。
View 和 Control/ViewModel 不是由彼此创建,而是由上层 Application 组装,再通过引用、指针、回调、事件连接起来。(对应 01-依赖关系/02-依赖注入:组装即注入。)
1.2 Model ≠ 数据结构
Counter 不是「Model = 数据结构」,而是「组件内部的业务对象」。MVVM 中的 Model 更接近 ViewModel 所面对的整个业务/数据模型(含 Data、Algorithm、Repository、Domain Object、Business API)。
按「Data + Algorithm + API」模型,计数器业务应该是:
1 | Counter Component |
GUI 再在上面做 View + ViewModel:
1 | 用户 → View → ViewModel → Counter API → Counter Algorithm → Counter Data |
业务能力属于组件,ViewModel/Control 属于 GUI,Data/Algorithm/API 是组件内部的实现结构。 应少用 Model 一词,直接说 GUI → View + ViewModel/Control + Business Components。
二、网页前端同样适用这套结构
纯前端(HTML+CSS+JS)仍然是「子系统 → 组件 → 数据/算法/API」的模型,只是多了一层明确的 UI/HTTP 层。
- 小型网页:文件拆职责(
index.html / style.css / main.js)。 - 中型网页:组件拆职责(
components/ todo_list / todo_item / todo_input / toolbar)。 - 大型网页:子系统 → 组件(
application → editor/ marketplace/ account/)。
HTML/CSS/JS 是实现技术,不是架构层级。 组件的「数据/算法/界面/事件/对外接口」最终分布在三种文件里,但组件边界是第一位的:
1 | todo_item/ |
大型前端项目结构:
1 | src/ |
三、前端组件的三种组装方式
1 | 组件 |
React/Vue 典型用法是「框架化 + 接口化 + 声明式组装」同时存在:
1 | <App> |
框架负责创建/挂载/更新/卸载/事件/状态更新/重新渲染;组件通过 props/events/callbacks 通信。
四、React/Vue 不是整个程序的架构,只是 UI 子系统的框架
React/Vue 只覆盖 UI/Presentation 这一部分:
1 | 整个应用 |
React 不负责网络、数据库、物理、音频、进程。它把 UI 的一整套机制(组件、状态、生命周期、渲染、更新、组装)打包成了组件模型和运行框架,但工程整体怎么拆仍然是你的事:
1 | 你的工程架构 |
五、CLI / GUI / Web 的统一
三个入口完全不同,但后面的业务组件可以完全相同:
1 | ┌── CLI → Call Layer → Business API |
结论:CLI、GUI、Web 不是业务本身,而是不同的交互/调用子系统;业务功能被拆成独立组件;Application 负责最终组装。 这套结构特别适合「同一套业务能力被 CLI、GUI、Web、测试程序调用」的工程。
六、框架可以再封装:框架不是组装的终点
之前的「框架化就是组装终点」说得太绝对。框架本身也可以被当成一个更高层的组件/整体进行封装。
1 | 底层库 → 组件接口 → 框架 → 领域框架 → 应用框架 → 具体应用 |
层次可以向上,但只有「当上一层框架提供的抽象不符合你的需求时,才有必要再包一层」。
6.1 三个例子
C++(Win32 → MyUI → EditorFramework):
1 | Win32 |
1 | // 最终应用只写: |
网络(ASIO → NetworkFramework → HttpServerFramework):
1 | ASIO → NetworkFramework(TcpServer/Connection/Buffer/EventLoop/Timer) |
MFC(Win32 → MFC → 你的领域框架):
1 | Win32 |
1 | class DebuggerApplication { |
DebuggerUIFramework 不是单纯封装一个 MFC API,而是封装多个组件的组装关系、生命周期和协作关系。
6.2 接口化 vs 框架化再封装
| 接口化 | 框架化(再封装) | |
|---|---|---|
| 目的 | 隐藏实现、稳定调用、方便替换 | 组织组件、统一生命周期/调度/组装、提供运行模型 |
| 例子 | ASIO → Tcp API | ASIO → Network Framework → Http Framework |
6.3 关键判断:多一层类 ≠ 框架化
普通 Qt 只是调用:
1 | int main() { |
建立 DebuggerUIFramework 后:
1 | main → DebuggerUIFramework → Qt → Qt Widgets → 具体 UI |
不是「多了一层类」就叫框架化,而是这一层获得了多个组件的统一组装和管理职责。 如果 DebuggerApplication::run() 只是 m_ui.run(),那只是调用层,不是新框架。
七、React 的真正特殊之处:组件树作为框架输入
React 把「组件的组装关系」提升成了框架的一等概念——Component Tree:
1 | <App> |
1 | React |
7.1 React 的 Renderer 分离
React 核心并不等于 DOM 绘制。React 本身已经把「组件/状态/组装」和「具体怎么绘制」分开了:
1 | UI Framework |
Renderer 可以换(React DOM → HTML DOM;React Native → Native UI;其他 Renderer → Canvas / WebGL / 游戏 UI)。所以可以把 React 的「页面描述/组件组织」与「运行管理」拆成两层:
1 | ┌─────────────────────┐ |
7.2 与 Qt 的对比
| Qt | React | |
|---|---|---|
| 框架提供 | 组件 + 布局 + 生命周期 + 事件 + 组装工具 | 组件模型 + 组装模型 + 生命周期 + 状态 + 更新 |
| 手写 | 具体组装代码(new / addToolBar / setCentralWidget) |
用组件树声明组装 |
React 把「组件树/页面描述」本身变成了程序的核心输入,让框架根据描述完成组装、更新和生命周期管理。 这正是普通 C++ main → new → 调用 与 React 对不上的那个差异。
7.3 C++ 也能做出完全一样的东西
1 | class Node { |
对应关系:
1 | C++: Page Description → UIFramework → Component Tree → 具体 UI |
再框架化(DebuggerFramework 向 UIFramework 提供更高层组装描述):
1 | main → DebuggerFramework → UIFramework → Component Tree → UI |
八、最终模型
1 | 普通组件化: 组件 → 调用者负责组装 |
结论:所有 GUI 框架都能组装组件;React 特殊在把「组件树/页面描述」做成了框架的核心输入。框架不是封装的终点,一个框架可以作为下层能力被更高层再次框架化。
九、和其他线的关系
- **
04-系统角色/07、08**:GUI 入口的拆解(View/ViewModel/交互)——本篇讲 GUI 内部组件怎么组织与组装,是入口拆解的下一层 - **
10-桌面端架构/04**:GUI 的演化与三条流程——演化出 MVVM 后,本篇回答「MVVM 之上组件如何组织、前端框架处在什么位置」 - **
02-拆解与组织/05**:只有组件的子系统如何被加载——框架化/接口化的落点选择(本篇的声明式组装是框架化的一种特殊形态) - **
11-模块与构建单元/01**:接口形态演化(无状态函数导出 / 有状态类封装)——组件树的 mount 机制是框架化接口形态的实现 - **
06-设计/07**:CLI 程序的设计顺序——CLI、GUI、Web 统一后的另一端(本篇第五节)
收束
GUI 是一个子系统,MVVM 是它的内部职责关系;业务能力属于组件,GUI 只负责交互。 前端组件化没有跳出「子系统 → 组件 → 数据/算法/API」的模型,React 只是把「组件树/页面描述」做成了框架的核心输入;而框架本身不是终点,可以被更高层再次框架化。
