稀有猿诉

十年磨一剑,历炼出锋芒,说话千百句,不如码二行。

降Compose十八掌之『潜龙勿用』| Thinking in Compose

Jetpack Compose是Android平台的现代化的声明式UI框架。它提供了一套声明式API,可以不必再机械式的操作View就能构建 出UI,从而更容易的构建出应用的UI,并且易于维护,易于扩展。今天我们重点理解一下Jetpack Compose背后的思想,学会以声明式思维来思考,进而写出更加专业的声明式UI代码。

从过程式UI到声明式UI

通过前面的学习,我们已经知道声明式UI的特点了,它是一坨一坨的函数。状态(要显示给用户的数据)作为参数,再写出使用这些状态的函数,然后Compose会运行这些函数最终生成UI。不必关心函数具体的执行,以及执行的结果,我们只需要描述好需要显示什么就可以了,简言之就是定义好如何使用数据的函数即可。这就是声明式UI的核心思想,它让开发者把精力放在你想要展示什么,而非繁杂的如何展示。

实际上这是函数式编程范式的一种,时刻记着我们都是在定义函数就可以了。

Compose的工作原理

传统的过程式UI(比如Android中的View和XML)是基于OO(面向对象)的,每个元素都是一个对象,它有一些属性,要构建出一颗View tree,然后当有事件变化或者有数据更新时,我们会具体的刷新具体的View。但像声明式UI,比如Compose,工作方式并不是这样的,开发者写出的并不是View tree,而是一系列描述着要展示数据的函数,其余的事情都是Comose负责。

Compose会执行这些函数,收集它们的结果,生成一颗虚拟的View tree,这一步要叫做Compositon;然后再把虚拟的View tree,生成真实的与平台相关的View tree,这一步叫做渲染。当有状态变化的时候,代表数据有更新,需要刷新UI,这时Compose会重新执行一遍函数,这就叫做重组Recomposition。因为重组会发生很多次,如果每次重组都直接去更新目标平台View tree开销太大了,因此就有了虚拟View tree这一层,每次重新会重新生成一颗虚拟View tree,然后比较两颗虚拟View tree,只当差异时,再用差异去更新目标平台的View tree。

有同学举手了,说这费二遍事,性能肯定会变得更差吧?这位同学先坐下。确实多费了一道工序,但随着CPU性能越来越高,以及像virtual dom diff技术的应用,Compose本身性能的差异已经追上传统方式了。但它的优势,比如开发效率以及可扩展性却无限放大,总的来说收益还是很大的。

说到底这是函数式编程方式,那么就会有函数式编程的特点,比如说这些函数的执行顺序不一定就是开发者写的那样,再比如说这些函数可能会并行的执行,甚至有些函数会被跳过。并且呢,重组可能随时发生,而且发生的很频繁。Compose这样做是为了保证底层UI能够及时得到刷新。

那么,在使用Compose时,就有一些注意事项,比如尽可能的使用Stateless的函数,尽可能的减少副作用。以及千万不要依赖函数的执行顺序。

拆分为细粒度的函数,加强复用

Compose是以函数来搭建UI,这相比于xml方式的一个最大的好处就是这非常方便的复用,因为可以像重构代码那样,把重复的代码抽成可复用函数。要以「自上而下」函数调用的方式来构建UI。

推荐的方式是,先把整个页面划分为不同的区域,每个区域是一个函数(Composable);再把每个区域细分成为更为细节的函数;这些细节函数如果是共性的就复用。这样做的方式,不但能够做到代码结构清晰,可读性很强,且易于维护,方便扩展。非常容易找到与UI的对应,而且容易复用,如果有新的页面,把不同的细节函数组合起来就可以了。

其实以前用XML也应该这样,但毕竟XML不像代码这样方便的管理和复用,拆成过多的XML文件不但难以管理,也会影响编译速度。

可以做一下Basic layouts in Compose,这个CodeLab非常好的演示了如何「自顶而下」的用Compose来构建UI。

Preview局部而非整体

Compose的一个强大功能就是即时Preview,不必非要到设备上运行就可以看到UI效果。不过呢Preview需要数据,而且要是静态的数据,也就是需要Mock。这对于复杂的数据来说是致命的缺点。比如说像字段非常之多的列表,或者有很多特殊字段的对象,Mock起来相当的麻烦。

一个可行的办法就是要尽可能的Preview局部,数据复杂,必然对应着复杂的页面,把复杂的页面拆开,变成一系列的简单的Composable的组合,这时每个Composable对应的数据都是相对简单的,只有几个参数,这时就是可Preview的了,Mock起来就容易得多了。

具体的来说,对于像集合性UI,我们只需要预览它的一个单元项就可以了,只要一个单元项没啥问题,组合起来看集合地无非就是重复很多个;再比如对于复杂的多字段对象,划分成为几个不同的子区域,每个子区域对应一个函数,这个函数经过Preview后没啥问题,那把这些组合起来成为一个整体后也不会有太大的问题。

Preview的作用是快速预览,减少编译运行的次数,毕竟编译运行一次要慢得多,所以要以简单快捷方便为主。而并不是真的当成运行结果,最终肯定要以在手机上运行结果为准,并进行最终的调试。

总结

对于习惯了View和XML方式的同学来说,开始用Compose肯定会略有不习惯,就需要理解一下它的原理,转变成声明式UI的思维,以函数为核心来思考问题,这样才能写出比较专业的Compose代码。

References

Comments