GUI 与前端组件化

GUI 是子系统,MVVM 是内部的职责关系;前端(React/Vue)本质上是把「组件树/页面描述」提升为框架核心输入的 GUI 框架。本篇承接 04-系统角色/07、08(GUI 入口拆解)与 10-桌面端架构/04(GUI 的演化与三条流程),回答:GUI 内部怎么按组件组织,前端框架在这套体系里处在什么位置。


一、GUI 是子系统,MVVM 是内部的职责关系

GUI 适合看成一个子系统,内部可拆成两类核心职责:

1
2
3
4
5
6
7
GUI 子系统

├── View / Rendering
│ ├── Window / Layout / Widget / Painting / Event(Input)

└── Application Interaction
├── ViewModel / State / Command / Service-API 调用

MVVM 不是「GUI 的两个子系统」,而是一种职责关系

1
View → ViewModel → Model / Business API
  • View:显示、布局、控件、绘制、用户输入。
  • ViewModel:GUI 状态、状态转换、用户操作转换、调用业务接口、把业务结果转换成 GUI 可显示数据。
  • Model:在这套架构里不一定要叫 Model,可能就是「业务组件 API → 业务子系统」。

GUI 的核心闭环:

1
2
用户输入 → View → ViewModel → 业务 API → 状态变化
→ ViewModel → View → Rendering

1.1 View 与 Control 由 Application 组装(计数器例子)

View 和 Control 不是「两个类互相调用」,而是 Application 在组装时把它们连起来:

1
main → Application → { Counter, CounterControl, CounterView }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Counter {
int value() const;
void increment();
private:
int value_ = 0;
};

class CounterView {
void set_value(int value); // 只负责显示
void show();
};

class CounterControl {
CounterControl(Counter& counter, CounterView& view);
void on_increment_clicked() {
counter_.increment();
view_.set_value(counter_.value());
}
private:
Counter& counter_;
CounterView& view_;
};

点击后:用户点击 → 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
2
3
4
Counter Component
├── data/ CounterData { int value; }
├── algorithm/ void increment(CounterData& data)
└── api/ void counter_increment(CounterData& data)

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
2
3
4
todo_item/
├── todo_item.html → View / DOM结构
├── todo_item.css → View / 表现
└── todo_item.js → 状态 + 行为 + 算法 + 接口

大型前端项目结构:

1
2
3
4
5
6
src/
├── application/ main.js
├── workflow/ data/ algorithm/ api/ editor/ node/ connection/ property_panel/ execution/
├── marketplace/ data/ algorithm/ api/ product_list/ product_detail/ search/
├── account/ login/ profile/ settings/
└── shared/ button/ dialog/ table/ utils/

三、前端组件的三种组装方式

1
2
3
4
组件
├── 接口化 → 被其他组件调用
├── 框架化 → 由框架管理生命周期
└── 声明式组合 → 通过 HTML/JSX/模板嵌套组装(Web 特有)

React/Vue 典型用法是「框架化 + 接口化 + 声明式组装」同时存在:

1
2
3
4
5
<App>
<Header />
<Editor />
<PropertyPanel />
</App>

框架负责创建/挂载/更新/卸载/事件/状态更新/重新渲染;组件通过 props/events/callbacks 通信。


四、React/Vue 不是整个程序的架构,只是 UI 子系统的框架

React/Vue 只覆盖 UI/Presentation 这一部分:

1
2
3
4
5
6
整个应用
├── UI └── React/Vue
├── Network
├── Business
├── Storage
└── ...

React 不负责网络、数据库、物理、音频、进程。它把 UI 的一整套机制(组件、状态、生命周期、渲染、更新、组装)打包成了组件模型和运行框架,但工程整体怎么拆仍然是你的事

1
2
3
4
5
你的工程架构
├── Workflow / Network / Storage / Business
└── UI
└── React
├── Component / State / Render / Event / Lifecycle

五、CLI / GUI / Web 的统一

三个入口完全不同,但后面的业务组件可以完全相同:

1
2
3
4
5
6
7
┌── CLI  → Call Layer   → Business API
├── GUI → ViewModel → Business API
└── Web → Router/Handler → Business API

Business Components

Data + Algorithm + API

结论:CLI、GUI、Web 不是业务本身,而是不同的交互/调用子系统;业务功能被拆成独立组件;Application 负责最终组装。 这套结构特别适合「同一套业务能力被 CLI、GUI、Web、测试程序调用」的工程。


六、框架可以再封装:框架不是组装的终点

之前的「框架化就是组装终点」说得太绝对。框架本身也可以被当成一个更高层的组件/整体进行封装。

1
底层库 → 组件接口 → 框架 → 领域框架 → 应用框架 → 具体应用

层次可以向上,但只有「当上一层框架提供的抽象不符合你的需求时,才有必要再包一层」。

6.1 三个例子

C++(Win32 → MyUI → EditorFramework)

1
2
3
4
5
6
7
Win32

MyUI(Window / Button / TextBox / MessageLoop)

EditorFramework(组装 Menu/Toolbar/Editor/PropertyPanel/StatusBar)

具体编辑器
1
2
3
// 最终应用只写:
EditorApplication app;
app.run();

网络(ASIO → NetworkFramework → HttpServerFramework)

1
2
3
ASIO → NetworkFramework(TcpServer/Connection/Buffer/EventLoop/Timer)
→ HttpServerFramework(HTTP/Router/Middleware/Session/Handler)
→ 具体业务服务器

MFC(Win32 → MFC → 你的领域框架)

1
2
3
4
5
6
7
Win32

MFC(CWinApp / CWnd / CFrameWnd / CView / 消息循环 / 消息映射)

DebuggerUIFramework(ProcessView/MemoryView/RegisterView/DisassemblyView/LogView 的组装)

Debugger
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class DebuggerApplication {
void run() {
m_app.Init();
m_mainFrame.create();
m_processView.create(); m_memoryView.create(); ...
m_mainFrame.attach(m_processView); ...
m_app.run();
}
private:
MfcApplication m_app;
MainFrame m_mainFrame;
ProcessView m_processView; ...
};

int main() { DebuggerApplication app; app.run(); }

DebuggerUIFramework 不是单纯封装一个 MFC API,而是封装多个组件的组装关系、生命周期和协作关系

6.2 接口化 vs 框架化再封装

接口化 框架化(再封装)
目的 隐藏实现、稳定调用、方便替换 组织组件、统一生命周期/调度/组装、提供运行模型
例子 ASIO → Tcp API ASIO → Network Framework → Http Framework

6.3 关键判断:多一层类 ≠ 框架化

普通 Qt 只是调用:

1
2
3
4
5
6
7
int main() {
QApplication app(argc, argv);
QMainWindow window;
QPushButton button("Hello", &window);
window.show();
return app.exec();
}

建立 DebuggerUIFramework 后:

1
main → DebuggerUIFramework → Qt → Qt Widgets → 具体 UI

不是「多了一层类」就叫框架化,而是这一层获得了多个组件的统一组装和管理职责。 如果 DebuggerApplication::run() 只是 m_ui.run(),那只是调用层,不是新框架。


七、React 的真正特殊之处:组件树作为框架输入

React 把「组件的组装关系」提升成了框架的一等概念——Component Tree

1
2
3
4
5
6
7
<App>
<Editor>
<Toolbar />
<Canvas />
<PropertyPanel />
</Editor>
</App>
1
2
3
4
React
├── Component
├── Component Tree ← 组装模型
├── State / Lifecycle / Update / Renderer

7.1 React 的 Renderer 分离

React 核心并不等于 DOM 绘制。React 本身已经把「组件/状态/组装」和「具体怎么绘制」分开了:

1
2
3
4
5
6
UI Framework
├── Component / State / Event / Lifecycle / Composition / Reconciliation

Renderer

具体绘制

Renderer 可以换(React DOM → HTML DOM;React Native → Native UI;其他 Renderer → Canvas / WebGL / 游戏 UI)。所以可以把 React 的「页面描述/组件组织」与「运行管理」拆成两层:

1
2
3
4
5
6
7
8
9
┌─────────────────────┐
│ Page Description │ JSX / Component Tree
└──────────┬──────────┘

┌─────────────────────┐
│ UI Framework │ State / Lifecycle / Event / Update / Composition
└──────────┬──────────┘

Browser DOM

7.2 与 Qt 的对比

Qt React
框架提供 组件 + 布局 + 生命周期 + 事件 + 组装工具 组件模型 + 组装模型 + 生命周期 + 状态 + 更新
手写 具体组装代码(new / addToolBar / setCentralWidget 用组件树声明组装

React 把「组件树/页面描述」本身变成了程序的核心输入,让框架根据描述完成组装、更新和生命周期管理。 这正是普通 C++ main → new → 调用 与 React 对不上的那个差异。

7.3 C++ 也能做出完全一样的东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Node {
std::string type;
std::vector<Node> children;
};

Node editor{ "Editor", { Node{"Toolbar"}, Node{"Button"}, Node{"Canvas"} } };

class UIFramework {
void mount(const Node& node); // 根据 Node 创建组件、建立父子、管理生命周期
};

int main() {
Node page{ "Editor", { Node{"Toolbar"}, Node{"Canvas"}, Node{"PropertyPanel"} } };
UIFramework ui;
ui.mount(page);
}

对应关系:

1
2
C++:    Page Description → UIFramework → Component Tree → 具体 UI
React: JSX → React → Component Tree → DOM

再框架化(DebuggerFramework 向 UIFramework 提供更高层组装描述):

1
main → DebuggerFramework → UIFramework → Component Tree → UI

八、最终模型

1
2
3
普通组件化:  组件 → 调用者负责组装
框架化: 组件 → 组装描述 → 框架负责组装和生命周期
再框架化: 下层框架 → 上层框架定义新的组装描述 → 下层框架执行

结论:所有 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 只是把「组件树/页面描述」做成了框架的核心输入;而框架本身不是终点,可以被更高层再次框架化。