UI编程的发展史 : 结合命令式UI和声明式UI
聊到UI编程的发展,就好比开车从"手动挡"进化到"自动挡"。从80年代个人计算机刚兴起时的"步步指令",到如今用几行代码就能搞定的声明式开发,这几十年的变化,简直是把开发者从繁琐的重复劳动里解放了出来。
声明式UI和命令式UI的区别
首先我们来明确下概念,声明式UI和命令式UI概念到底是怎么样的呢 ?
-
命令式 UI 就是告诉框架怎么做 ,达成目的的每一步都需要开发者显式的控制,直接操控 UI 元素的创建、更新、销毁和状态变化。
-
声明式 UI 就是告诉框架做什么 ,具体怎么做我们不关心。开发者仅需描述UI的最终状态,框架会自动处理从当前状态到目标状态的更新逻辑。
这么说可能还是有点抽象,我们可以拿"开车"来做个形象的比喻 :
- 命令式UI 就像开手动挡 :你需要告诉程序每一个具体操作("挂1档"、"踩离合"、"抬离合给油")。你完全掌控过程,但也承担了所有繁琐和出错的风险,比如说可能会出现熄火。
- 声明式UI 就像开自动挡 :你只需声明意图("我要加速"),至于引擎如何升档、变速箱如何配合,全部由框架(变速箱)自动完成。你只关注结果和目标,而非实现细节。

了解了声明式UI和命令式UI的概念后,我们接着顺着时间线,好好唠唠UI编程的三个关键阶段,看看它是怎么一步步变成现在这个样子的。
第一阶段:命令式UI的诞生 (1980s)
80年代是个人计算机的"启蒙时代",之前那种对着黑屏敲DOS命令行的交互方式,普通人看着就头大,所以GUI(图形用户界面)应运而生------说白了,就是有窗口、有按钮、有图标,用鼠标点一点就能操作,这可比输命令方便多了。比如微软1985年推出的Windows 1.0,就是早期GUI的代表,一下子让计算机变得"平易近人"了。
但那时候的UI开发,可没现在这么轻松,完全是"命令式"的天下。啥叫命令式?就是你得像指挥机器人一样,一步一步告诉程序"该做什么",从创建窗口到放按钮,再到绑定点击事件,每一个动作都得写代码指令,一点都不能省。
这一阶段的核心代表技术就是Windows API(Win32),这可是当时Windows平台开发的"标配",想做个带窗口的程序,绕不开它。
具体来说,这个阶段的开发有三个很明显的特点:
- 过程化创建:代码又长又乱,耦合度拉满
开发者得从头写到尾,先注册窗口类,再创建窗口,然后显示窗口,还要给按钮绑定事件,代码写得老长了。更头疼的是,UI代码和业务逻辑搅和在一起,比如你想改个按钮的位置,可能得翻好几段代码,改完还怕影响其他功能,简直是牵一发而动全身。 - 平台绑定:跨平台?基本等于重写
UI控件长得啥样、怎么用,全看操作系统的脸色。比如你在Windows上写的按钮,放到Mac上可能就变样了,甚至连点击事件的处理方式都不一样。想跨平台开发?别想了,基本是重新写一遍的节奏。 - 关注"如何做":开发者得当"精细工匠"
你得精确控制每一步,比如窗口的大小、位置,按钮的颜色、字体,甚至窗口刷新的时机,都得自己操心。简单说,你不仅要告诉程序"做什么",还得教它"怎么做",每一个细节都得亲手打磨。
给大家看个简单的例子,你就知道当时写代码有多繁琐了:
c
// 1. 注册窗口类(命令1)
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = "MyWindowClass";
RegisterClass(&wc);
// 2. 创建窗口(命令2)
HWND hWnd = CreateWindow("MyWindowClass", "Hello UI", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
NULL, NULL, hInstance, NULL);
// 3. 显示窗口(命令3)
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
就建个空窗口,就得写这么多代码,要是再加个按钮、绑个事件,代码量直接翻倍。
但是Win32 API我们毕竟没用过,再举个Android的例子 : 用Android纯代码编写UI,
其实这种用Android纯代码编写UI就是完全命令式UI的写法,这种写法Android开发者都写过,都知道写起来有多繁琐了。
kotlin
LinearLayout rootLayout = new LinearLayout(this);
rootLayout.setOrientation(LinearLayout.VERTICAL);
rootLayout.setLayoutParams(new ViewGroup.LayoutParams());
Button button = new Button(this);
LinearLayout.LayoutParams btnParams = new LinearLayout.LayoutParams();
button.setLayoutParams(btnParams);
button.setText("点击了0次");
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
count++;
button.setText(String.format("点击了%d次", count));
}});
rootLayout.addView(button);
第二阶段:命令式UI的优化,标记语言的分离 (1990s-2000s)
到了90年代,互联网开始兴起,网页成了新的UI载体,而桌面端的开发也觉得命令式太麻烦了,于是大家开始想办法"偷懒"------先通过标记语言分离实现UI架构的解耦,再通过封装工具简化命令式操作,这就到了第二个阶段,也是UI开发的"解耦时代"。
标记语言的分离:结构、样式、行为分离
其中一项变革就是声明式标记语言的出现与分层分离,它彻底打破了早期UI开发结构、样式、行为混杂的混乱局面,构建起现代UI开发的基础架构。
这个阶段的核心代表技术就是Web三大件(HTML/CSS/JavaScript),同时桌面端也借鉴了这一思想,诞生了以XML为基础的标记语言(比如WPF的XAML):
- HTML(1993年):率先实现声明式UI结构描述,无需编写创建元素的命令式代码,直接用标签描述"需要什么UI";
- JavaScript(1995年):专门负责处理UI交互行为,与HTML结构实现初步分离;
- CSS(1996年):进一步抽离样式表现,最终形成"结构-样式-行为"的三层分离架构。
这样,HTML就可以用声明式的UI来描述网页界面了,只不过只能描述出初始的静态界面,界面的动态变更还是得借助JavaScript,来处理UI的交互行为,JavaScript这部分还是命令式的UI。

命令式UI的简化:基于标记语言架构的上层封装(jQuery)
折阶段还有一项变更是,原生JavaScript进行命令式DOM操作时,不仅代码繁琐,还需要处理大量浏览器兼容问题(比如IE和Chrome的事件绑定、AJAX请求差异),比如获取一个元素要写document.getElementById('myBtn'),绑定点击事件还要写一堆兼容代码,让人头皮发麻。
为了解决这些问题,各种基于原生JavaScript的封装工具和框架应运而生,核心就是把重复的命令式操作封装起来,让开发者少写冗余代码,提升开发效率。其中最标志性的就是2006年诞生的jQuery,它的口号是"Write Less, Do More"(写得更少,做得更多),简直说到了开发者的心坎里。
但是jQuery本质上还是命令式的写法,只不过是把重复的命令式操作封装起来了。
js
// jQuery仍需手动描述操作步骤(命令式)
let count = 0; // 定义计数变量
$('#myBtn').click(function() {
count++; // 每次点击计数累加
$(this).text(`点击了${count}次`); // 手动更新按钮文本
});
移动端的延续:布局与逻辑的半分离
2007年iPhone的发布堪称移动互联网的里程碑,移动应用开发一下子火了起来。各大厂商也顺势采纳了"标记语言分离"的思想,搞出了布局层面的声明式,不过逻辑处理还是没跳出命令式的圈子,算是一种"半分离"的状态。
Android 是这一思路的典型代表:它用XML布局文件 来描述UI结构,比如你想加个按钮,直接写<Button android:text="点击了0次" android:layout_width="wrap_content" />就行,不用再写代码手动创建控件。但这只是第一步,后续你还得在Java或Kotlin代码里,用findViewById找到这个按钮的实例,再手动绑定点击事件、更新计数和按钮文本------这些逻辑部分还是得一步步写命令式代码。
举个简单的例子,Android里实现按钮点击计数累加,得这么干:
xml
<!-- res/layout/activity_main.xml 声明式布局 -->
<Button
android:id="@+id/myBtn"
android:text="点击了0次"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
java
// MainActivity.java 命令式逻辑
Button myBtn = findViewById(R.id.myBtn);
int count = 0; // 定义计数变量
myBtn.setOnClickListener(v -> {
count++; // 每次点击计数累加
myBtn.setText("点击了" + count + "次"); // 手动更新按钮文本
});
而iOS这边,早期也用XIB或Storyboard(本质是XML格式的标记文件)来做界面布局,同样需要在Objective-C或Swift代码里手动处理控件的事件和状态,和Android的路子大同小异。
这种模式虽然比纯命令式方便了不少,把布局和逻辑拆开来了,但还是得开发者手动同步状态和UI,状态多了之后还是容易乱。
第三阶段:声明式UI的爆发与成熟
时间来到2010年代之后,随着互联网应用越来越复杂,比如电商网站、社交平台,页面元素多、交互复杂,原来的开发方式又遇到了新问题------比如状态管理混乱,UI更新不及时,大型项目维护困难。这时候,声明式UI就彻底爆发了,直接重构了UI开发的玩法。
声明式UI的核心是啥?简单说,你只需要描述"UI和状态的关系",比如"count是0的时候,按钮显示'点击了0次',count加1后,按钮就显示'点击了1次'",至于怎么更新UI、怎么渲染,全交给框架来做,开发者再也不用操心这些细节了。
这一阶段的框架层出不穷,每个都有自己的特色,咱们一个个唠:
React(2013,Facebook):声明式UI的"破局者"
Facebook当时面临一个大问题:社交平台的动态内容多,UI更新频繁,传统方式做起来性能差、维护难。于是2013年,React横空出世,它的两个核心创新------虚拟DOM 和组件化,直接解决了这个痛点。
虚拟DOM就是先在内存里建一个DOM的"副本",状态变化时,只对比新旧虚拟DOM的差异,然后只更新变化的部分,大大提升了性能。组件化则是把UI拆成一个个独立的小部件(比如按钮、导航栏),可以重复使用,大型项目维护起来就轻松多了。
React用JSX语法,把HTML和JS结合在一起,直接描述UI和状态的映射,代码特别直观。比如这个计数器按钮,你看只需要描述状态和UI的关系,点击事件改状态就行,更新的事框架全包了:
js
// 仅描述UI与状态的映射,框架自动处理更新
function ButtonCounter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
点击了{count}次
</button>
);
}
Vue.js(2014,尤雨溪):更易上手的声明式框架
React火了之后,2014年,尤雨溪推出了Vue.js,它走的是"渐进式框架"的路子------你可以先用它的核心功能,比如模板和双向绑定,然后根据需要再加路由、状态管理这些插件,不用一下子学完所有东西,对新手特别友好。
Vue早期结合了AngularJS的双向绑定和React的组件化,用的是模板式声明式语法,和传统的HTML很像,前端开发者一看就会。比如这个Vue的计数器,模板部分和HTML几乎一样,状态存在data里,点击直接改count就行,特别简单:
js
<template>
<!-- 声明式模板:UI与count状态绑定 -->
<button @click="count++">点击了{{ count }}次</button>
</template>
<script>
export default {
data() {
return { count: 0 };
}
};
</script>
Angular(2016,Google):企业级声明式框架
Google在2010年推出了AngularJS,但后来发现它在大型项目里有一些问题,于是在2016年用TypeScript重构了Angular(也就是Angular 2),放弃了原来的双向绑定主导模式,改用组件化、声明式模板、单向数据流的设计。
Angular是个"全家桶"框架,内置了路由、表单、HTTP请求、依赖注入等各种企业级功能,不用自己找第三方插件,所以特别适合开发大型的企业应用,比如银行、电商的后台系统。
Flutter(2017,Google):跨平台的纯声明式框架
前面的框架主要是针对Web的,而移动开发的跨平台一直是个痛点------比如用原生开发iOS和Android,要写两套代码;用混合开发,性能又不行。2017年,Google推出的Flutter解决了这个问题。
Flutter基于Dart语言,采用Widget树的纯声明式设计,所有UI元素都是Widget,而且它有自己的自绘引擎,不依赖操作系统的原生控件,所以iOS和Android上的UI长得一模一样,性能也和原生差不多。状态变化时,Flutter会重新构建Widget树,不过它用Diff算法优化,只更新变化的部分,效率很高。
比如这个Flutter的计数器,把UI拆成StatefulWidget,状态变化时调用setState,框架就会自动重建UI:
dart
// 声明式描述按钮的状态与UI
class CounterButton extends StatefulWidget {
@override
_CounterButtonState createState() => _CounterButtonState();
}
class _CounterButtonState extends State<CounterButton> {
int count = 0;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => setState(() => count++),
child: Text('点击了$count次'),
);
}
}
SwiftUI(2019,Apple):苹果生态的声明式革命
Apple也不甘落后,2019年推出了SwiftUI,这是专门为iOS、macOS、iPadOS等苹果平台设计的声明式UI框架。它直接用Swift语言描述UI,不用再写Storyboard或XIB文件了,而且和Combine框架结合,能轻松实现响应式状态管理。SwiftUI的出现,让苹果原生开发也进入了声明式时代,开发者不用再写一堆繁琐的UIKit代码,效率提升了不少。
Jetpack Compose(2019,Google):Android原生的声明式选择
Google在2019年推出了Jetpack Compose,作为Android的官方声明式UI框架,取代了传统的XML布局。它用Kotlin语言的函数式语法描述UI,状态变化时自动重组,代码比XML简洁多了。比如这个Compose的计数器,用remember保存状态,mutableStateOf实现状态可观察,改状态就自动更UI,特别丝滑:
kotlin
/**
* Compose 声明式的计数器按钮
* 利用 remember 持久化状态,mutableStateOf 实现状态可观察(变化时自动重组UI)
*/
@Composable
fun CounterButton() {
// 1. 用 remember 保存状态,避免重组时重新初始化
// 2. 用 by 委托简化 State.value 的访问(等价于 val count = remember { mutableStateOf(0) },使用时 count.value)
var count by remember { mutableStateOf(0) }
// Compose 的 Button 组件(对应Flutter的ElevatedButton)
Button(
onClick = {
// 点击时修改状态,Compose 会自动重组依赖该状态的UI
count++
},
modifier = Modifier.padding(16.dp) // 可选:添加内边距
) {
// 文本显示计数,状态变化时自动更新
Text(text = "点击了$count次")
}
}
ArkUI (2021,华为):鸿蒙全场景的声明式框架
华为在2021年推出的ArkUI,是鸿蒙操作系统的核心声明式开发框架,它的核心理念同样是"状态驱动UI",而且还针对鸿蒙的"全场景分布式"特性做了专门优化,能让开发者用一套代码适配手机、平板、手表、车机、智慧屏等多种设备,真正实现了"一次开发,多端部署"。
ArkUI早期支持TS/JS语言,后来又推出了基于TypeScript扩展的ArkTS语言,采用函数式组件+装饰器的语法,让状态与UI的绑定更直观。当状态发生变化时,框架会自动更新对应的UI组件,不用开发者手动操作。比如这个简单的计数器示例,就能看出它的声明式特点:
ts
// ArkTS 声明式计数器按钮
@Entry
@Component
struct CounterButton {
// 定义状态变量,状态变化自动驱动UI更新
@State count: number = 0;
build() {
Column() {
Button(`点击了${this.count}次`)
.onClick(() => {
// 修改状态,UI自动更新
this.count++;
})
.padding(16)
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%');
}
}
ArkUI的出现,不仅让鸿蒙生态的开发效率大幅提升,也让声明式UI的理念在全场景智能设备领域得到了新的落地。
Compose Multiplatform (2022, JetBrains & Google):基于Compose扩展的跨平台声明式框架
Compose Multiplatform是JetBrains 基于Compose扩展的跨平台声明式框架,不仅仅支持Android,跨平台地支持了Android、iOs、桌面端和Web网页。

ovCompose (2025,腾讯):基于Compose Multiplatform的首个支持鸿蒙的跨平台框架
甚至于2025年,腾讯还基于Compose Multiplatform推出了首个支持鸿蒙的跨平台框架,用来弥补Compose Multiplatform不支持鸿蒙平台的遗憾,便于在国内构建全跨端应用。

写在最后
回顾这几十年的UI编程发展史,其实就是一个不断"解放开发者"的过程:从命令式的步步为营,到标记语言的分离解耦,再到声明式的智能高效,每一次技术的进步,都让我们能把更多精力放在产品的逻辑和体验上,而不是纠结于UI的实现细节。
未来的UI开发会往哪走?可能会更智能,比如AI辅助生成UI代码,或者跨平台、跨端的框架更完善,但核心肯定还是让开发更简单、更高效。