使用 Provider 改造屎山代码,代码量降低了 2/3!
前言
之前的几篇我们写了状态管理的机制和状态管理插件,接下来几篇我们就使用官方推荐的 Provider 来改造旧的代码,你会发现改造前后具有十分大的差别。
Provider 简介
Provider 是 Flutter 一个入门级的状态管理插件,基于 InheritedWidget 实现。Provider 能够沿着组件树共享状态数据,示例代码如下:
Provider
类本身并不会在状态改变的时候自动更新子组件,因此更常用的是使用其子类:
ListenableProvider
:监听实现了Listenable
的的对象,并将其暴露给下级组件。当触发一个事件后会通知子组件依赖发生变化进而实现重建。ChangeNotifierProvider
:最为常用的一个方式,是ListenableProvider
的子类。监听实现了ChangeNotifier
接口的对象,当该对象调用notifyListeners
的时候,就会通知全部的监听组件更新组件。ValueListenableProvider
:监听实现了ValueListenable
接口的对象。当该对象改变时,会更新其下级组件。StreamProvider
:监听 Stream 对象,然后将其内容暴露给子组件。通常是向一个组件以流的方式提供大量的内容,例如电池电量监测、Firebase 查询等。
如果一个对象被多个组件共享,那么可以使用如下方式:
在 Widget 中使用状态数据有三种方式:
使用
context.read<T>()
方法:该方法返回 T 类型的状态数据对象,但不会监听该对象的改变,适用于只读的情况;使用
context.watch<T>()
方法:该方法返回 T 类型状态数据对象,并且会监听它的变化,适用于需要根据状态更新的状况。使用
context.select<T,R>(R cb(T value))
方法:返回 T 对象中的 R 类型对象,这可以使得Widget
只监听状态对象的部分数据。
详细内容建议大家去看 Provider 的官方文档,我们后续的篇章也会涉及其中的内容。
代码分析
我们在前面的篇章介绍了一个动态模块的管理,包括了整个 CRUD 过程。具体可以从专栏阅读之前网络请求相关的篇章。首先我们来改造一下列表的代码,回头再来看之前的代码,就会知道为什么说直接使用 setState 的方式更新界面的开发者会被评为“草包”了!
之前代码一看就很乱,首先是在列表里包括了添加、编辑、删除的回调代码,是想要是业务复杂一点,岂不是回调要满屏飞了!其次是业务代码和 UI 代码混用,一个是代码又臭又长——俗称💩一样的代码,另外一个是业务代码的复用性降低了。比如说,我们在别的地方可能也会用到动态的增改删查业务,总不能再复制、粘贴再来一遍吧?
代码改造
现在我们来使用Provider
将业务和 UI 分离。将业务相关的代码统一放到状态管理中,UI 这边只处理界面相关的代码。首先抽取一个 DynamicModel
类,文件名为 dynamic_model.dart
,把列表的相关业务代码放进来:
列表数据:使用一个
List<DynamicEntity>
对象存储列表数据,默认为空数组。分页数据:当前页码
_currentPage
,固定每页大小为 20。刷新方法:
refresh
,将当前页码置为 1,重新请求第一页数据。加载方法:
load
,将当前页码加 1,请求第 N 页的数据。获取分页数据:根据当前页面和分页大小请求动态数据,并更新列表数据。
预留
delete
、add
和update
方法,以便后面的删除、添加和更新使用。
整个DynamicModel
类的代码如下,这里关键的一点是使用 with ChangeNotifier
使得 DynamicModel
混入ChangeNotifer
的特性,以便 ChangeNotifierProvider
能够为其添加监听器,并且在调用 notiferListeners
的时候通知状态依赖的子组件进行更新。
接下来是使用 Provider
为动态模块提供状态管理,如前面的几章所述,Provider
需要处于组件的上级才能够为子组件提供状态共享,因此我们有两种方式来实现这种方式。
在构建
DynamicPage
列表页面的app.dart
中将DynamicPage
作为Provider
的下级。如下所示,这种方式的缺点是因为这是首页,如果各个模块的代码都往这里对方,会使得 app.dart 很臃肿,而且耦合度也变高。
使用一个
Widget
包裹DynamicPage
以及Provider
来降低代码的耦合度,避免app.dart
中的代码过于臃肿。
之后就是对 DynamicPage
进行改造,首先是将 DynamicPage
由 StatefulWidget
改为 StatelessWidget
,然后移除掉相关业务代码。,最后就是在 build
方法中从 Provider
获取界面所需的数据,或调用对应的方法。改造完的 DynamicPage 就十分清爽了,如下所示:
在 ListView.builder
中我们使用了 contxt.watch<DynamicModel>
方法来获取最新的动态列表 ,从而使得当列表数据改变时能够刷新界面。而在调用方法方面,我们则使用了 context.read<DynamicModel>
方法,因为这里并不需要监听状态的改变。运行一下,发现和之前的效果一样,改造完成。
改造前后对比
我们来对比改造前后的 DynamicPage
代码,如下图所示(左侧为旧代码)。可以看到,大部分代码都被移除了,实际原先的代码有120
行,而现在的代码只有40
行了,足足减少了 2/3!
当然,代码减少是因为将业务代码抽离了,但是业务代码本身是可以复用的。下一篇我们将删除、添加和编辑完成后,再来看 Provider
如何进一步提高代码复用性和简化页面代码。
总结
通过 Provider 状态管理,得到的最大的好处其实是 UI 层和业务层代码分离,精简了 UI 层代码的同时,也提高了业务代码的复用性。而 Provider 的局部刷新特性,也能够提高界面渲染的的性能。
版权声明: 本文为 InfoQ 作者【岛上码农】的原创文章。
原文链接:【http://xie.infoq.cn/article/64ec645fad65ce37957b77f12】。文章转载请联系作者。
评论