一通百通 “声明式 UI” 扫盲干货

本文为《重学安卓》专栏《一通百通 “声明式 UI” 扫盲干货》篇的试读内容,

我们截取了原文前半部分作为试读内容,您可以免费阅读和 “全文转载” 本文。

前言

上一期《过目难忘 Android GUI 关系梳理》,我们用 “通俗易懂” 的方式解析了 “View 体系” 每个层级工具的 存在意义 及 相互间关系,鉴于 Compose 离 “普及” 还有很长一段距离,这一期我们先来介绍 Compose 框架背后的本质。

声明式 UI 的由来

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

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

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

—— React、Flutter、SwiftUI、Jetpack Compose,表面上每个都长得不一样,如何透过表象看穿它们的 “流程和机制”,从而能自行领悟代码 该怎么写、往那写、怎么改?

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

文章目录一览

声明式 UI 的本质是 “函数式编程”

声明式 UI、Java8 Stream、RxJava 等,本质上都是函数式编程。

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

事实上,“响应式编程” 是一种 额外发明的称谓,“响应式编程” 这个概念 不仅无助于我们 正确理解事实,反倒徒添困扰 —— 为什么要用响应式编程 —— 没有人真的能够 就这个脱离事实的概念 把事情给你交代清楚

所以,我们不妨回归它最真实的本质 —— 函数式编程,好从根源的根源找寻线索,从而有机会 知其所以然、而顿悟般地 知其然。

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

老规矩,先讲结论:

声明式 UI 是 “数据驱动 UI 框架” 的一个分支,声明式 UI 的实现方式是函数式编程,且函数式编程的基石是纯函数

函数式编程的存在,主要是为了 从范式层面彻底解决 过程的一致性问题

数据驱动 UI 框架 主要是为了解决 视图调用的一致性问题

如果光是阅读了以上三点,你还是不理解的话,那接下来我就分别介绍 99% 的网文都不曾介绍的真实状况,来方便你迅速地建立起感性的认识。

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

以下是我们最常见的用法:

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

这造成了什么问题呢?

一旦 TextView 成为共享变量,被分散到各个方法中,后续就不可控了,因为当执行方法 B 时,方法 A 是无法知道 方法 B 中对共享变量做了什么,却无差别地承受 共享变量被修改 所带来的影响,

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

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

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

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

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

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

什么是纯函数?为什么纯函数最终能解决这个问题?

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

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

啥意思呢,就是说:

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

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

除此之外:

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

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

—— 简言之就是,调用函数时 没有 “副作用”,且 不改变初始状态

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

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

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

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

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

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

至此,我们得以顿悟般地理解:为什么 RxJava 或 Java8 Stream 是这样书写、为什么会和常规的 “侵入式” 思维发生 “别扭” …

是的,正因为人们无意识地习惯了 “自由散漫” 的编程,而从未意识到还有 “集中管理” 的范式的存在,于是在遇到 RxJava 链式编程的第一感觉就是,“自由散漫” 在此行不通了,

而这也恰恰是 函数式编程 的存在意义:从范式层面彻底解决 过程的一致性问题 —— 有怎样的输入,就有且只有怎样的输出,关注点只有这两处,因集中管理 而从根本上杜绝了不可预期的结果。

划重点 👆 👆 👆

所以为什么会有 “数据驱动 UI 框架”?

将 “视图系统” 设计为 “数据驱动”,反映了源码设计者 对 彻底解决软工安全问题 的不懈追求。

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

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

加上我们在 《从被误解到 “真香” 的 Jetpack DataBinding》 一文中提到的,"横竖屏布局的控件存在差异" 这一经典案例,如采用 "自由散漫" 的方式,同样会埋下视图调用 的一致性问题。

那怎么办呢?

声明式 UI 的运作流程是怎样的?

试读内容完

版权声明

Copyright © 2019-present KunMinX

本文为专栏的试读内容,转载须注明作者、链接出处、并保持原文完整(即包含从 “前言” 到 “本声明” 在内的所有内容)

文中 原创的引言、切入点、思路、结论 凝聚了作者 KunMinX 本人的心血,作者本人对相应的成果享有所有权。

当您 借鉴或引用文中的引言、切入点、思路、结论 进行二次创作并打算发行时,须注明链接出处,否则我们保留追责的权利。对此如有疑虑请及时沟通,或在发行前将作品交由本人核对。

当您看见有人 洗稿、剽窃、未符合要求地转载本文内容时,请及时向本人举报。

最后感谢您对本文的阅读和喜欢。

最后更新于