编程思维模式:Flutter的思维模式和传统UI思维模式的区别

近期带领团队在Flutter领域高歌猛进,在此过程中发现很多同学的思维方式还是受到传统UI设计模式的影响从而走了不少弯路。因此整理了对这个问题的思考写成这篇文章,好让团队更好的发挥Flutter生产力。

话不多说,我们直入主题。

原有的思维模式

在做传统的客户端界面开发时,我们脑海里会有一颗界面树

以一个最常见的登录界面为例,我们开发登录界面的时候,一定会创建一个类似下图的界面树:

这个界面树除了在我们脑海中浮现,也真实对应者我们的代码或者布局文件,下图以Android layout为例:

这里概念(界面树)和实体(布局文件)对于客户端开发同学再熟悉不过了,几乎所有的逻辑和模块设计都会基于此进行,再经过了几年的经验加深,用刻骨铭心描述不为过。

当我们开始接触Flutter的时候,难免会在Flutter中寻找我们已经熟知的界面树和对应实体。

但是Flutter的设计思想和我们已经熟知的传统模式已经完全不同了

建模角度的改变带来思维模式的转变

接下来我们不着急讲Flutter的设计思想,我想先举个例子,让大家感受这钟思想的转变是多么巨大的。

大家都听过傅里叶变换,傅里叶变换把信号从时域转为频域,揭开了信号处理的学科分支。

无论是时域还是频域,都是对信号的描述,但是描述的角度的差异让我们对信号有了更丰富的处理手段,用时域描述信号的时候,我们能很方便的对信号进行放大和缩小,但仅限于此。对信号用频域描述后,我们能做的事情就非常多了,比如低通,高通,压限,有损压缩等等。从另一个角度建立模型去描述一个事物让我们打开了新世界的大门

Flutter的思维模式

Flutter相比传统的界面树编程模型,已经开始从另一个角度去建模我们的需求了。只是很多人没有意识到。

传统界面开发思维模式的界面树是通过静态的描述文件(layout)或者静态的代码构建出来的,界面树构建出来之后基本是不变的(大部分场景),我们通过show和hide来实现展示界面的状态,业务数据也存住在响应的节点中。到了Flutter这里,界面树不是静态layout决定的,而是执行绪决定的。这一点是底层思维模式的差别。我们用下图来解释这句话:

大家能看出差异吗?

Flutter的界面树是执行绪决定的。执行绪的特点是动态性非常强,不同的执行绪执行出来的界面树可能是差异非常大的。

就如上图的例子,Flutter的界面树根据pageState的值不同,从而执行绪也不同,构建出来的界面树是完全不一样的,是动态的。以这个例子来说,在Flutter这边界面树可能有2个的:

Flutter(所谓的声明式UI)对界面树这个概念的描述看不见摸不着的执行绪

而传统设计模式(所谓的命令式UI)对界面数的描述是摸得着看得清的 layout 布局

所以在Flutter的开发中,你的脑海还用界面树来组织构建你的逻辑就会很别扭,非常容易调到坑里。

还是举个例子,我们有个基础组件:下拉刷新的组件;其中核心代码如下:

在没有深刻理解Flutter设计模式的特点前,会很自然的写下了这段代码。

但是开发者忽略了一点,Flutter的界面树不像传统UI框架,他是动态的,用户下拉刷新,界面树这个概念在这里经历了如下的变化:

下拉时,旧的界面树被删掉了,下拉后界面树下面的节点都是新的,如果你的状态存储在这些节点上,那么你的状态都丢失了。状态包括但不限于各种Flutter的Widget的controller(TextEditController, ScrollController),还有你的DataProviders,你的业务StateFullWidget等等。

这个基础组件坑了很多人,最大的问题是不少人竟然认为这是正常的,还有人发明了黑魔法(用单例把数据存在其他地方)“解决”了下拉刷新导致的状态丢失的问题。

实际上,你只需要重新改一下下拉刷新组件的实现就好了:

这样实现的下拉刷新组件的界面树是稳定的。当然如果你关注性能,有很多技巧可以避免多一层Stack。

这个例子从另一方面也告诉我们,DataProviders也要尽可能靠近根节点坑会少一些。

举这个例子是希望加深大家对Flutter的界面树不稳定的理解,和传统UI开发中非常容易写出稳定的界面树不同,深刻认识Flutter的框架里对界面树这个抽象概念的描述是build函数的执行绪

读到这里,你会发现这似乎是Flutter的缺点,我们程序员用稳定的界面树来组织我们的思维逻辑既符合习惯也便于思考,为什么Flutter这么反人类高出难以捉摸的界面树,让我们似乎无从下手。

这时我们回到刚刚傅里叶变换的例子中,如果你看习惯了信号的时域图,给你看信号的频域图一定是不习惯的,但是频域图让另一个我们容易忽略的信息直观的呈现给了我们(频率)。

Flutter也是一样,把我们之前容易忽略了的一个重要信息呈现给了我们,那是什么信息我们忽略了呢?

这个容易忽略的信息就是程序的状态!Flutter把之前传统UI开发隐藏很深的程序状态直观的呈现在我们面前。

接下来我们解释为什么状态在传统UI开发隐藏很深,而在Flutter就直观呈现出来的原因。

传统的界面开发中,界面树中的节点对应的是运行程序中的一个对象,这个对象指针是可以随意传递的,因此我们接下来以一个loading逻辑为例,假设我们的程序需要在好几种状态下显示loading,在传统UI框架开发时,你的代码可能会长这样:

随着需求在迭代和增长,其他模块在StateC可能也可能会需要控制界面的loading状态,于是其他模块的同学给你加了个接口:

于是其他模块开始用你的这个接口可以“方便(随意)”的控制loading的展示与隐藏。

如果你是一名经验丰富的程序员,就会发现这会导致代码很难读懂,控制loading的隐藏和显示的逻辑被分散了到处都是,而且多个条件组合下很容易把loading状态搞乱了。于是你计划重构,但是又随着需求的增长,更多的人想要控制loading,最后有可能出现的问题已经超出了你能控制的范围,你还会继续重构吗?

这就是传统UI开发中,没有强迫开发者去抽象自己的程序状态,而强迫开发者去抽象界面树的弊端。因为界面树和我们实现需求是割裂的。

所以强迫程序员抽象自己的程序状态,用程序状态控制UI,是一件非常有价值的事情。当程序员只能通过自己抽象的程序状态来控制界面展示时,你的程序的可读性必然比直接操作UI对象更直观!

还是以上面loading的例子为例,在Flutter的世界了,无论后续需求如何变换,都只是增加了程序状态,而你的loading是否展示,都逃不出下面看到这行代码,一切都在你的控制和理解之中:

这就是Flutter吸引我的魅力!

Flutter为了避免你直接操作UI设置了非常多的精心的设计,小心翼翼的不让你掉进传统界面开发的坑中。比如你不能存储一个ImageWidget的指针;或者通过指针访问ImageWidget去修改他的资源Url,再如每个widget没有onHide,onMouseEvent那些传统界面框架中常见的虚函数或者事件通知;再如抽象出了无状态的Widget,让你对状态的管理更加清晰,特别地,在一些场景下Widget还可以多次使用,这是传统的界面框架是无法想象的:

从这个角度也能看出,Flutter的Widget并不是传统UI界面的界面树节点,而Flutter的执行绪才是对应传统界面开发中界面树,对于Flutter来说,build函数进来3次,在界面上就能出现3个imagePlaceHolder,而不是需要3个imagePlaceHolder对象在内存中。这又回到我们刚开始提出的,你一直想寻找的界面树这个概念对应的实体是Flutter的执行绪,充满了动态性和不确定性,所以忘掉界面树吧,转而思考你的程序状态,当你对你的程序状态做好了抽象,剩下的代码就非常直观且简洁了

结语

总而言之,深刻理解Flutter的思维模式,就很容易对Flutter的其他设计更容易理解并掌握,能更好的从传统UI开发思维模式中转变过来,充分发挥出Flutter的生产力,实现编码两小时摸鱼一整天的理想生活,走向技术时间自由的程序员人生。

  • 18
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值