3.3 声明式 UI:状态驱动界面更新


3.3 声明式 UI:状态驱动界面更新

本节摘要:声明式 UI 的核心是"你告诉系统想要什么,而不是如何去做"——界面是状态的函数,状态一变界面自动重建。本节对比命令式与声明式两种范式,讲清 State 驱动 UI 更新的完整流程,以及 React Native 与 Flutter 如何用同一思想实现高效的界面更新。这是理解现代 UI 开发的思维总纲。

阅读收获

阅读完本节,你应当能够:

  1. 用"要什么 vs 怎么做"说清声明式与命令式的本质区别。
  2. 解释"UI 是状态的函数"这一核心命题。
  3. 描述状态变化到界面更新的完整链路(setState → 重新渲染 → 差异比较 → 最小更新)。
  4. 说明 React Native 与 Flutter 各自如何实现高效更新。

一、问题与直觉

先做一个思维实验。想象你在点一份萨拉米披萨。命令式点法是这样:你盯着后厨的每一步——拿面粉、加水、揉面、发酵、擀平、涂酱、撒芝士、放萨拉米、进烤箱、烤二十分钟、切片、装盒。任何一步做错,披萨就毁了。声明式点法只有一句:"给我一份萨拉米披萨"。至于后厨怎么做出它,你不用操心,结果保证是你要的。

旧式 UI 开发就是命令式点法。你用 jQuery 操作 DOM 时,代码长这样:先找到元素、改它的颜色、往父节点追加一个子节点、监听点击再手动改回颜色。状态一变,你得自己记得"该改哪些地方、怎么改"。

声明式 UI 是披萨式点法。你声明"界面在某个状态下应该长什么样",框架负责把它变成现实。状态变了,你只更新状态,界面怎么变是框架的事。

一句话点题:命令式问"怎么改界面",声明式问"界面该是什么样"。

二、核心原理

2.1 UI 是状态的函数

声明式 UI 最核心的命题是:UI = f(State)。界面是状态的函数。给定相同的状态,界面呈现永远相同;状态变了,函数重新求值,界面跟着变。

这个命题带来一个巨大的好处:可预测性。调试时你只需要关注状态对不对,界面表现是确定的,不会再出现"同样的数据、不同的界面"这种玄学问题。测试也变简单了——给定一组状态,断言界面应该是什么样。

2.2 命令式 vs 声明式:一份直观对照

对比维度 命令式 声明式
关注点 如何操作界面 界面该是什么样
代码形态 查找、修改、增删元素 声明状态与界面的映射
状态变化 手动同步到界面 自动触发重建
更新方式 逐条指令 差异比较后最小更新
可预测性 受更新顺序影响 同状态必同界面
典型代表 jQuery、原生视图操作 React Native、Flutter

命令式的痛点在状态复杂时集中爆发:手动管理 UI 更新的逻辑繁琐易错、随着应用增长难以追踪、频繁的 DOM 或视图操作引发性能问题、相似更新逻辑在多个地方重复。声明式把这些都交给了框架:你只管状态,框架管界面。

2.2.1 命令式的痛点具象化

光说"繁琐易错"不够直观,我们把命令式写法的样子摆出来看。假设页面上有一个购物车数字,用户每加一件商品要更新它。命令式大概是这样:

// 命令式:找到元素,改内容,还得记住改哪 const cartCount = document.getElementById('cart-count'); cartCount.textContent = String(totalItems);

代码本身不难,难的是维护:当页面上还有价格、运费、折扣、库存这几个数字都要跟着变时,你得在每一个操作点手动更新所有相关元素。今天加一个"满减"逻辑,就得在所有操作点再补一处更新。改一处漏一处,是最常见的线上事故来源。

声明式写法把这个负担反转了:你维护的只有一个"购物车状态",页面上的数字全部由状态推导。状态一变,所有依赖它的界面自动跟着变,不存在"漏改"的可能。这个"由状态统一推导"的思想,就是声明式可维护性的来源。

2.2.2 声明式让团队协作变简单的深层原因

还有一个常被忽视的收益:声明式让界面代码可以被审查和测试。命令式代码里,"界面更新"散落在事件处理、回调、异步逻辑各处,看代码的人很难追踪"这个界面在什么情况下会变成什么样"。声明式组件把"给定状态 → 输出界面"封装成一个确定函数,测试可以直接调用它、传入不同状态断言输出。这解释了为什么声明式框架通常测试基础设施更完善——不是因为框架厉害,而是因为范式本身就为测试铺好了路。

2.3 状态驱动更新的完整链路

以 React Native 为例,从状态变化到界面更新走完六步:

  1. 定义状态:组件里声明数据状态。
  2. 界面是状态的函数:render 方法(RN)或 build 方法(Flutter)接收状态,返回界面描述。
  3. 状态变化:用户交互或数据到达,调用 setState 修改状态。
  4. 重新渲染:框架重新调用受影响组件的 render/build,生成新的界面描述。
  5. 差异比较:RN 比较虚拟 DOM,Flutter 比较 Widget 树,找出最小差异。
  6. 最小更新:只更新真正变化的部分,不销毁重建整个界面。

03-3-fig01-2

这一链路里最值得记住的是第 5、6 步:框架不是"整页重画",而是"找差异、改差异"。这是声明式性能不差的关键,也是 RN 与 Flutter 都选择的路。

2.4 两种框架的实现差异

RN 用虚拟 DOM:组件渲染产出虚拟 DOM,状态变化后生成新虚拟 DOM,与旧树做 diff,计算出最小变更再应用到原生视图。Flutter 用 Widget 树协调:重建 Widget 树,与上一棵树比较,只重建变化的 Element。名字不同、机制相近——都是"描述界面、比较差异、最小更新"。

三、工程实践要点

3.1 声明式开发的三个习惯

习惯一:想清楚状态,而不是想清楚操作。写界面前先问"这个界面依赖哪些状态",状态列全了,界面自然清晰。

习惯二:状态放在它该在的地方。组件自己的状态放组件内部,跨组件共享的状态考虑全局管理(第 5 章展开)。状态位置错了,界面就会难维护。

习惯三:别直接改数据,产生新数据。RN 与 Flutter 都靠引用比较判断变化,原地改对象不产生新引用就不会触发更新。这是新手最容易踩的坑。

⚠️ 常见坑:在声明式代码里偷偷写命令式操作——直接操纵某个控件的实例去改它的属性。这违背声明式原则,会造成状态与界面不同步、更新混乱。正确的做法永远是"改状态,让框架去更新界面"。
💡 关键直觉:判断一段 UI 代码是否声明式,看它有没有"直接操作界面对象"。如果代码里出现"找到某元素、设置某属性"这类指令,就是命令式的;如果只是声明状态与界面的映射,就是声明式的。

3.2 什么场景用命令式反而合适

声明式不是万能钥匙。极少数场景下命令式更直观:高频逐帧动画的精细控制、需要直接操作图形上下文的自定义绘制、与第三方原生视图的深度交互。Flutter 的 AnimationController、自定义 CustomPainter 都属于这类。正确姿势是:主体用声明式,例外才用命令式,而不是反其道而行。

FAQ:声明式 UI 的常见疑问

问:声明式会不会比命令式性能差? 不会,前提是框架的差异算法在正常工作。声明式把"全量描述"与"最小更新"组合起来:描述时是全量的,更新时只动差异。相比之下,命令式"精确操作"反而容易因漏更新或多更新而出问题。真正影响性能的从来不是范式,而是对差异算法的滥用——比如每次渲染都生成新的匿名对象导致无法复用。

问:理解了声明式,对写业务代码有什么直接帮助? 直接帮助在于"设计思路":遇到界面需求,先想状态结构,再想界面如何由状态推导,而不是先想"我要操作哪些控件"。这个思路一换,组件拆分、状态提升、可复用性这些设计问题都会迎刃而解。可以说,声明式思维是后续所有组件与状态相关章节的思想地基。

问:RN 的虚拟 DOM 和 Flutter 的 Widget 树比较,哪个更快? 没有简单的胜负。两者都采用"描述、比较、最小更新"的策略,性能表现取决于具体场景与实现细节。对业务开发而言,这个差异远小于"是否合理组织状态与组件"的影响。新手完全不需要为这两者的性能差异纠结——先把声明式思维用对,比什么都重要。

3.4 声明式思维对状态设计的影响

声明式不仅改变了写法,更改变了你设计状态的方式。命令式时代你会想"界面需要哪些操作",声明式时代你会想"界面需要哪些状态"。这个转变带来一个实际好处:状态设计先行,界面自然跟随。设计新页面时先列状态清单(字段、类型、默认值),再写界面,顺序反过来往往更容易返工。这也是为什么状态管理(第 5 章)在声明式框架里如此重要——状态是界面的输入,输入设计得好,输出才稳定。

3.4.1 一个训练声明式思维的小练习

把一段命令式代码翻译成声明式,是很好的训练。拿"切换开关控制文本颜色"举例:命令式写法是"点击时把文本颜色改成红色/蓝色";声明式写法是"isOn 为真时文本红色,否则蓝色"。注意声明式里没有任何"改颜色"的操作,只有"状态到界面"的映射。多做几组这类翻译练习,声明式的"描述而非操作"就会成为本能。这个练习也可以在第 4 章做组件时顺带完成。

要点串联

  • 核心命题:UI 是状态的函数,同状态必同界面。
  • 两种范式:命令式管"怎么做",声明式管"要什么"。
  • 命令式痛点:状态复杂时更新逻辑繁琐易错、难以维护、性能不稳。
  • 六步链路:定义状态 → 渲染 → 状态变化 → 重渲染 → 差异比较 → 最小更新。
  • 性能来源:框架只更新差异部分,不是整页重画。
  • 双框架一致:RN 用虚拟 DOM,Flutter 用 Widget 树协调,机制同源。
  • 可测试性:声明式组件是"状态到界面"的确定函数,天然适合测试。
  • 三大习惯:想清楚状态、状态放对地方、产生新数据而不是改旧数据。
  • 例外场景:精细动画与自定义绘制可用命令式,主体仍应声明式。

声明式的思维内核已经建立,第 4 章我们开始真正用组件搭界面——把"描述界面"落到具体的一行行组件代码上。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U