声明式 UI 解析
2023-08-04 19:29:52

注:本文节选自《重学安卓:一通百通 “声明式 UI” 扫盲干货》,本文可免费阅读和全文转载,转载时须注明本文链接出处。

Canvas 总共生两胎,一胎 View 体系,二胎 Compose 体系,

上一期《过目难忘 Android GUI 关系梳理》,我们循序渐进拆解了 “View 体系” 各工具的 存在意义 及 相互间关系,鉴于 Compose 离 “普及” 还有很长距离,这一期我们先来介绍 Compose 框架背后的本质。

声明式 UI 由来

事实上,React、Flutter、SwiftUI、Jetpack Compose,这些 UI 框架有个共同鼻祖,即一个名为 elm(不是 “饿了么”)的 UI 框架,是它最早确立并推行 “声明式 UI” 设计理念。

注:“声明式 UI” 这个名称,最初谁定义,暂未找到来源,目前各大官网都称其为 “声明式 UI”,基于我个人理解,其更精确表达是,“响应式 UI 框架的声明式编程分支”,当然,最终我们还是简称为 “声明式 UI”。

—— 那么 “声明式 UI” 到底长啥样?为何要用 “声明式 UI”?相比传统 View 有何优势?本质、存在意义又是什么?

—— React、Flutter、SwiftUI、Jetpack Compose,形态各异,如何透过表象看穿流程机制,从而自行领悟代码 该怎么写、往哪写、怎么改?

所以今天我们就来统一解析 “声明式 UI” 背后本质,相信阅读后能让你醍醐灌顶。

文章目录一览

  • 前言
  • 声明式 UI 由来
  • 声明式 UI 本质是 “函数式 + 函数式编程”
    • “纯函数” 是 “函数式编程” 基石
    • “函数式编程” 引入前的混沌世界
    • “函数式编程” 为何能 “彻底” 解决这类问题?
    • 引入 “函数式编程” 后的世界
  • 所以为什么会有 “声明式 UI 框架”?
    • 声明式 UI 运作流程是怎样
    • 声明式 UI 难以替代的好处
  • 函数式编程局限
  • Note 2020.07.27 加餐:
  • 现有条件下 “View 实例 Null 安全一致性问题” 最优解
    • 1.Java + DataBinding 严格模式
    • 2.Kotlin + ViewBinding
    • 3.Kotlin DSL 动态布局
    • Note 2022.06.06 加餐
  • Note 2020.07.31 加餐:
    • 通过 “函数式编程思想” 秒懂 “声明式 UI” 打开方式
  • 综上

声明式 UI 本质是 “函数式 + 响应式编程”

声明式 UI、Java8 Stream、RxJava 等,本质上皆是函数式编程 + 响应式编程。

许多文章,从 RxJava 时代起,就照搬官网说法,说 RxJava 是一种 “响应式编程” 框架。

事实上,“响应式编程” 是从 “整体流程数据走向” 的角度来看 —— “输入数据,响应结果”,单凭 “响应式编程” 无益于更好理解 “声明式 UI” 和其他 UI 框架的本质区别,

所以,我们不妨重点关注另一个核心本质 —— 函数式编程。

“纯函数” 是 “函数式编程” 基石

老规矩,先讲结论:

声明式 UI 的内部实现是基于函数式编程,且函数式编程基石是纯函数

函数式编程的存在,主要是通过 “流程的原子化” 实现 “结果的一致性”,

声明式 UI 框架可将多种一致性问题简化为只关注 “数据的一致性问题”,

上述三点,如暂无体会,那接下来,请跟随我脚步铺垫 “声明式 UI” 来龙去脉。

“函数式编程” 引入前的混沌世界

以下是常见用法:

通过 findViewById 拿到 TextView 实例,使其作为 Activity 内部共享成员变量,为多个方法所调用,来改变 TextView 状态。

img

这造成什么问题?

一旦 TextView 成为 Activity 类的成员变量,分散到各方法中,后续便不可控,因为执行方法 B 时,方法 A 无法知道 方法 B 中对成员变量做了什么,却无差别承受 “成员变量被修改” 所带来影响,

例如当 方法 B 将 TextView 置空,那么 方法 A 调用 TextView 实例时 将面临 Null 安全问题。

可能有人会问,这种问题通过 “手动判空” 不就可以?

事实上,在软件工程背景下,任何微小隐患都可能被 “指数级” 放大

一个 App 页面可能有数十个,每个页面控件也可能多达十数个,而每个控件都可能分散在多个方法中,这种情况下,一味寄希望于 “手动判空”,是成本极高且存在 一致性风险 —— 总会有疏忽时候,总会有 “方法 A 记得判空,而方法 B 忘记”。

“函数式编程” 为何能 “彻底” 解决这类问题?

因为函数式编程基于纯函数。

什么是纯函数?为何纯函数最终能解决该问题?

简单来说,纯函数相比普通函数特征是:

只有一个入口 & 只有一个出口

也即

函数只从 参数列表 这唯一入口 接收外来初值,

并且只从 返回值 这唯一出口 返回结果数据。

除此之外:

不在函数内部执行与运算本身无关其他操作,

不在函数内部调用外部变量、不修改从外部传入的变量,

抛开上述 TextView 的案例,我们先来举个例子看看纯函数本身:

img

如此一来,在调用该函数时,关注点就只有 “入口和出口” 这两处,不至于蔓延到整个程序,从而 “不可预期情况” 发生概率从 99.9% 骤减为 0 —— 调用者无须了解细节即可放心调用

引入 “函数式编程” 后的世界

函数式编程,除了单个纯函数,也可是多个纯函数链式编程,

即,上一个函数输出 作为下一个函数输入

整个链同样只有 “开头入参” 这一入口,和 “末尾回调” 这一出口,

img

至此,我们得以顿悟:为何 RxJava 或 Java8 Stream 是这样写、为何会与常规 “侵入式” 思维发生 “别扭” …

是的,正由于人们习惯了 “自由散漫” 的命令式编程,从未意识到还有 “集中管理” 式,乃至遇到 RxJava 链式编程第一感觉即是,“自由散漫” 在此行不通了,

而这恰恰也是 “函数式编程” 存在意义:通过将过程原子化,来实现结果的一致性 —— 有怎样输入,就有且只有怎样输出,关注点只有这两处,因 “集中管理” 而从根本上杜绝不可预期结果。

划重点 👆 👆 👆

所谓原子化即 “像原子一样不可再分、内部结构稳定、不受外力影响发生改变”。这和 “一致性” 都是大学《软件工程》课提到的概念,读书时不理解没关系,工作多年后还不理解就不对了。

所以为何会有 “声明式 UI 框架”?

将 UI 系统设计为 “函数式”,反映源码设计者对 彻底解决软工安全问题 不懈追求。

正是由于这锐意进取 **”死磕精神”**,使软件开发得以不断优化和改进。

通过 “混沌世界” 一节分析,我们已确知,在 “自由散漫” 环境下,实例分散可使不可预期风险被大幅扩散

加上 《从被误解到 “真香” Jetpack DataBinding》“横竖屏布局控件存在差异” 这一经典案例,如采用 “自由散漫” 方式,同会埋下视图实例 Null 安全一致性问题,

而如今,函数式、响应式,意味着多种一致性问题被简化为 “数据的一致性” 这一种,开发者只需确保数据的来源是唯一且可靠的,即可通过这唯一来源的数据去驱动声明式 UI,从而得到始终一致、符合预期的结果。

声明式 UI 运作流程是怎样

试读内容完。

相关资料

《重学安卓:一通百通 “声明式 UI” 扫盲干货》

版权声明

Copyright © 2019-present KunMinX 原创版权所有。

如需 转载本文,或引用、借鉴 本文 “引言、思路、结论、配图” 进行二次创作发行,须注明链接出处,否则我们保留追责权利。