前端之变(六):引领式变革,从命令式UI到声明式UI

当我在2020年使用前端的技术栈去编写一个跨平台桌面App时,发现前端在UI方面其模式与我在移动端接触到的有很大的差异,那时候我意识到原来在前端,其UI使用的是另一种模式,后面我才知道它的名字:声明式UI

事实上,前端本身也经历了变革,至少在JQuery时代,它与移动端一致,其UI模式仍然属于传统的命令式UI,但到了React及Vue的时代,它变成了声明式UI。从我有限的知识来看,至少在大前端的其它两个方向:移动端与桌面开发,并未优先引领式的出现这种变革。

因此,我把前端的这种变革,称之为:引领式变革

它改变的不仅仅是自己。而且正在改变移动端,无论是Android官方自己主推的Jetpack,还是iOS官方新的UI框架SwiftUI,与之前也完全不同,都从命令式UI变为声明式UI。很难说这种变革,没有受到前端的影响或借鉴。

这说明前端技术变革不仅改变了自身,甚至在一些方面走在了更前面。

本周,继续就前端之变阐述自己的思考与分析。这是第六篇。前面几篇分别是:

  1. 前端之变(一):技术的变与不变
  2. 前端之变(二):不变的前端
  3. 前端之变(三):变革与突破
  4. 前端之变(四):进击的前端
  5. 前端之变(五):王者归来

命令式与声明式

首先,要明确一个前提,UI这个事情,只在大前端才有。所以,无论是命令式UI还是声明式UI,在后端编码是不存在这个概念的。

当然,若干年前,后端兼顾前端页面的开发,但那个时代已经过去了。现在主流的模式应该是前后端分离,由后端人员同时来开发前端,比如用JSP或FreeMarker模板技术的做法,在现在应该不多见了,不能算主流了。

UI这个事情并非只在Web前端才有,事实上,在技术的几个方向,除了后端以外,包括前端,移动端及桌面端都存在UI。

因此,无论是命令式UI,还是声明式UI,其概念是同时适应于前端,移动端以及桌面端的。

在这个前提之下,我们就可以来仔细分析下,在前端发生变革以前,事实上无论是在前端,移动端还是桌面端,其UI的编码模式都属于命令式UI

什么是命令式UI

UI的更新是由程序员使用代码主动刷新,UI与数据并无必然的映射关系,这种我们称之为命令式UI

什么是声明式UI

UI的更新并非由程序员使用代码来主动刷新,而是由后面隐藏机制来负责维护UI的刷新,UI与数据有映射关系,这种我们就称之为声明式UI

上面这种定义是我的定义,根据上述定义,区分是命令式UI还是声明式UI的两个核心点是:

  1. 程序员是否要显式的去调用代码刷新UI
  2. UI与数据是否存在映射关系

传统UI模式:命令式UI

我们回到过往的时光,在那个还是JQuery主导前端开发的时代,我们设想一个最简单的需求:记住上一次的登录用户名

我在这里用前端与移动端的代码来示例,展现命令式UI的做法:

前端

//基于JQuery的实现
const lastLoginUsername = localstorage.getItem("lastLoginUsername")
$("#username").val(lastLoginUsername);//主动刷新UI

移动端

//Android + Java
String lastLoginUsername = preferences.getString("lastLoginUsername", "");
usernameInput.setText(lastLoginUsername);//主动刷新UI

上述的这些实现,就是典型的命令式UI,它都具备几个特点:

  • 在程序中,你可以显式的引用或拿到UI组件
  • UI组件的内容是怎么样,什么时候改变内容,都是由程序员在合适的时候进行处理。UI本身与数据并无直接的映射关联,都是由程序员将数据显式的注入到UI中。

无论是传统的前端开发,还是我前些年开发原生iOS与Android,都统一属于这种模式。它们都毫不例外的属于命令式UI。

这种命令式UI的模式,是存在一些问题的,表现在:

UI维护工作较重

从上面我的描述可以看出,整体UI行为,怎么样,什么时候怎么改变,要全部由程序员使用代码来处理。可想而知,这个过程显然是非常繁重的。事实上,可以说,无论是过往的前端,还是现在的移动端,可能有相当一部分工作都是在处理UI的各种刷新上面。

易于出错

很显然,需要刷新UI的时机很多,比如下拉刷新,通知数据变更,网络不好数据加载错误,其它模块变更引发的联动UI变更等等,很多情况下需要你处理UI的刷新工作。

需要处理的事情一旦多起来,出错的概率就再所难免了。

性能不佳

通过一个UI包含很多内容与组件,但需要刷新时,你是怎么处理刷新的?

是不管三七二十一,将所有UI内容全部设置一下,还是先对比下,有改变的再刷新,没改变的不再刷新?

可能有相当一部分比例,是属于全部设置一下的做法。这种的性能肯定不会太好,产生了许多不必要刷新。

当然,如果你比对然后只尽量做必要性的刷新,那这个事也有相当的复杂度的,而且可能易于出错。

UI与数据易出现不一致

想像一下吧,你的代码中有一份数据,这份数据决定了UI的展现,但事实上程序员是分开处理这两个部分的,由一些代码来调用刷新数据,再由一些方法现刷新UI,无论你做的多么周到,出现数据变更 ,UI却忘记刷新的可能性仍然是非常高的。

因此,UI与数据出现不一致的可能性极高。

所幸,声明式UI出现了,它极大的改善了这些问题。

变革之道:声明式UI

声明式UI与命令式UI的最核心的区别在于:

  1. UI是数据的映射与描述,甚至一些框架中,程序员是无法持有UI组件的。更谈不上去调用这个组件的方法刷新UI了。
  2. 程序员关心的只是数据,只需要在合适的时机刷新数据就行了。UI则根据映射,由技术背后的机制帮你去刷新处理。

很显然,这是对命令式UI做了根本性的改变。

我在这举一个简单的例子,仍然以记住上一次登录用户这个需求为例。

//代码做了删减,只保留了有关的部分
export const LoginView = observer(() => {

  const [username, setUsername] = useState(localStorage.getItem('login_username'));

  return (
     setUsername(e.target.value)} />
  );
});

这是一个React代码,你可以看到,input_username这个输入框的值是{username}这个变量,而要修改这个输入框的值的方式,也不是调用UI的方法去设置值,而是通过改变username这个变量来实现。

所以,修改这个UI的内容的方法是

onChange={e => setUsername(e.target.value)}

一旦你改变了username的值,input_username的内容自动的刷新改变了,并不需要程序员去介入UI的刷新工作。

这就是声明式UI

声明式UI如果要论述,可以说的很多,我这篇文章的目的不在于此。就不详细去解释它了。

当然,很明显,与命令式UI相比,上述的几个缺点都有所改善:

程序员没有复杂的UI操作

在声明式UI中,程序员要做的就是定义数据与UI的映射关系而已,一旦定义好后,后面只需要关心数据的维护,不需要再关心UI的刷新了,这极大的减轻了程序员的在UI上的工作。

事实上,以我个人编写移动端与前端的经历来看,前端的UI编写的确更快,更有效率

难以出错

显而易见的是吧,UI刷新是由框架或技术背后实现的,你只需要刷新数据就可以了,框架或技术的可靠性保证了不太可能出现数据刷新了,UI却没刷新或刷新出错的情况。

极高的性能

由于对数据的映射与刷新是框架在背后处理的,通过大部分框架都不会数据一变就全量刷新,这就太low了。

比如,React就有一个diff算法,这个算法保证了只进行必要的刷新,这是非常高效的做法。

UI与数据的一致性

你只需要关心数据,变更数据。并不需要担心数据与UI出现不一致的情况。

在框架质量有所保证的前提下,这种可能几乎为小的可以忽略不计。(框架也可能有BUG,不能期望它为0)

趋势,大前端UI的未来

当然,『后』前端阶段,无论是React或Vue,都已经是这种声明式UI的做法了,它已经是前端的事实与主流了。

而在移动端,Android现在本身主推的是Jetpack,而iOS主推的是SwiftUI,这些也都是声明式UI了。但在移动端,它们仍然只是趋势,移动端现在绝大部分主流可能仍然是过往的命令式UI。

但可想而知,就算移动端,未来也必然会转向声明式UI。

至于移动端非原生的技术,类似Flutter,React Native等就不用说了,这些已经是声明式UI的实现了。

至于桌面端,由于我只有基于Electron开发桌面软件的经验,这是个前端技术,当然也是声明式UI,至于原生Window或Linux桌面开发,我并未有相关经验,但我相信借鉴声明式UI也绝对是正确的趋势。

所以,做为一个大前端的程序员,无论你是前端或是移动端,还是桌面端,你都要做好迎接声明式UI的未来的准备。

前端之困

前端发生了巨大的变革,如我所言,这种变化是革命性的,颠覆性的。

但从我在前端的经验来看,无论是前端语言的生态,还是质量,与后端仍存在一些差距,这就非常值得我的思考,如果理念与技术并没有问题,那问题究竟在哪?

下一篇,前端之变(七):前端之困,继续就前端阐述我的思考与分析。

你可能感兴趣的:(编码之道,javascript,typescript,前端,大前端,react)