当前位置:网站首页>使用 Provider 改造屎一样的代码,代码量降低了2/3!
使用 Provider 改造屎一样的代码,代码量降低了2/3!
2022-06-23 21:36:00 【岛上码农@公众号同名】
前言
之前的几篇我们写了状态管理的机制和状态管理插件,接下来几篇我们就使用官方推荐的 Provider 来改造旧的代码,你会发现改造前后具有十分大的差别。
Provider 简介
Provider 当前最新版本是5.0.0,使得组件树能够共享状态数据的方式为:
Provider (
create: (_) => Model(),
child: someWidget(),
);
Provider类本身并不会在状态改变的时候自动更新子组件,因此更常用的是使用其子类:
ListenableProvider:监听实现了Listenable的的对象,并将其暴露给下级组件。当触发一个事件后会通知子组件依赖发生变化进而实现重建。ChangeNotifierProvider:最为常用的一个方式,是ListenableProvider的子类。监听实现了ChangeNotifier接口的对象,当该对象调用notifyListeners的时候,就会通知全部的监听组件更新组件。ValueListenableProvider:监听实现了ValueListenable接口的对象。当该对象改变时,会更新其下级组件。StreamProvider:监听 Stream 对象,然后将其内容暴露给子组件。通常是向一个组件以流的方式提供大量的内容,例如电池电量监测、Firebase 查询等。
如果一个对象被多个组件共享,那么可以使用如下方式:
// 被多个组件共享的对象
MyChangeNotifier variable;
ChangeNotifierProvider.value(
value: variable,
child: ...
)
在 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的时候通知状态依赖的子组件进行更新。
class DynamicModel with ChangeNotifier {
List<DynamicEntity> _dynamics = [];
int _currentPage = 1;
final int _pageSize = 20;
List<DynamicEntity> get dynamics => _dynamics;
void refresh() {
_currentPage = 1;
_requestNewItems();
}
void load() {
_currentPage += 1;
_requestNewItems();
}
void _requestNewItems() async {
var response = await DynamicService.list(_currentPage, _pageSize);
if (response != null && response.statusCode == 200) {
List<dynamic> _jsonItems = response.data;
List<DynamicEntity> _newItems =
_jsonItems.map((json) => DynamicEntity.fromJson(json)).toList();
if (_currentPage == 1) {
_dynamics = _newItems;
} else {
_dynamics += _newItems;
}
}
notifyListeners();
}
void removeWithId(String id) {}
void add(DynamicEntity newDynamic) {}
void update() {}
}
接下来是使用 Provider 为动态模块提供状态管理,如前面的几章所述,Provider 需要处于组件的上级才能够为子组件提供状态共享,因此我们有两种方式来实现这种方式。
在构建 DynamicPage列表页面的app.dart中将DynamicPage作为Provider的下级。如下所示,这种方式的缺点是因为这是首页,如果各个模块的代码都往这里对方,会使得 app.dart 很臃肿,而且耦合度也变高。
@override
void initState() {
super.initState();
_homeWidgets = [
ChangeNotifierProvider<DynamicModel>(
create: (context) => DynamicModel(),
child: DynamicPage(),
),
MessagePage(),
CategoryPage(),
MineSliverPage(),
];
}
使用一个 Widget包裹DynamicPage以及Provider来降低代码的耦合度,避免app.dart中的代码过于臃肿。
class DynamicWrapper extends StatelessWidget {
const DynamicWrapper({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => DynamicModel(),
child: DynamicPage(),
);
}
}
之后就是对 DynamicPage进行改造,首先是将 DynamicPage 由 StatefulWidget 改为 StatelessWidget,然后移除掉相关业务代码。,最后就是在 build 方法中从 Provider 获取界面所需的数据,或调用对应的方法。改造完的 DynamicPage 就十分清爽了,如下所示:
class DynamicPage extends StatelessWidget {
DynamicPage({Key key}) : super(key: key);
final EasyRefreshController _refreshController = EasyRefreshController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('动态', style: Theme.of(context).textTheme.headline4),
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () {
RouterManager.router
.navigateTo(context, RouterManager.dynamicAddPath);
}),
],
brightness: Brightness.dark,
),
body: EasyRefresh(
controller: _refreshController,
firstRefresh: true,
onRefresh: () async {
context.read<DynamicModel>().refresh();
},
onLoad: () async {
context.read<DynamicModel>().load();
},
child: ListView.builder(
itemCount: context.watch<DynamicModel>().dynamics.length,
itemBuilder: (context, index) {
return DynamicItem(context.watch<DynamicModel>().dynamics[index],
(String id) {
context.read<DynamicModel>().removeWithId(id);
});
},
),
),
);
}
}
在 ListView.builder 中我们使用了 contxt.watch<DynamicModel>方法来获取最新的动态列表 ,从而使得当列表数据改变时能够刷新界面。而在调用方法方面,我们则使用了 context.read<DynamicModel>方法,因为这里并不需要监听状态的改变。运行一下,发现和之前的效果一样,改造完成。
改造前后对比
我们来对比改造前后的 DynamicPage 代码,如下图所示(左侧为旧代码)。可以看到,大部分代码都被移除了,实际原先的代码有120行,而现在的代码只有40行了,足足减少了2/3!

当然,代码减少是因为将业务代码抽离了,但是业务代码本身是可以复用的。下一篇我们将删除、添加和编辑完成后,再来看 Provider 如何进一步提高代码复用性和简化页面代码。
总结
通过 Provider 状态管理,得到的最大的好处其实是 UI 层和业务层代码分离,精简了 UI层代码的同时,也提高了业务代码的复用性。而 Provider 的局部刷新特性,也能够提高界面渲染的的性能。 
边栏推荐
- Outlook開機自啟+關閉時最小化
- Minimisé lorsque Outlook est allumé + éteint
- How to download offline versions of Firefox and chrome
- How does the API gateway intercept requests? How does the security of the API gateway reflect?
- Xgboost implements text classification and sklearn NLP library tfidfvectorizer
- Minimize outlook startup + shutdown
- CAD图在线Web测量工具代码实现(测量距离、面积、角度等)
- Meaning of the last seven digits of wider face
- 发现一个大佬云集的宝藏硕博社群!
- 嵌入式开发:嵌入式基础——重启和重置的区别
猜你喜欢

Bluetooth chip | Renesas and Ti launch new Bluetooth chip, try Lenz st17h65 Bluetooth ble5.2 chip

Minimisé lorsque Outlook est allumé + éteint

Smart cockpit SOC competition upgraded, and domestic 7Nm chips ushered in an important breakthrough

Polar cycle graph and polar fan graph of high order histogram

Installation and use of Minio

微信小程序中发送网络请求

Find my information | Apple may launch the second generation airtag. Try the Lenz technology find my solution

Embedded development: embedded foundation -- the difference between restart and reset

ACL2022 | MVR:面向开放域检索的多视角文档表征

Intel openvino tool suite advanced course & experiment operation record and learning summary
随机推荐
Embedded development: embedded foundation -- the difference between restart and reset
Take you to understand the working process of the browser
Outlook开机自启+关闭时最小化
How to view the hard disk of ECS? How about the speed and stability of the server
How does the API gateway intercept requests? How does the security of the API gateway reflect?
[same origin policy - cross domain issues]
ZABBIX custom monitoring item (server monitoring)
How to batch generate video QR code
How to write test cases efficiently?
Experiment 5 module, package and Library
KnowDA: All-in-One Knowledge Mixture Model for Data Augmentation in Few-Shot NLP(KnowDA:用于 Few-Shot NLP 中数据增强的多合一知识混合模型)
Data visualization: summer without watermelon is not summer
Notepad++ installing the jsonview plug-in
How to set the life cycle of API gateway
大一女生废话编程爆火!懂不懂编程的看完都拴Q了
Chrome extension development Chinese tutorial-1
How API gateway extends the importance of gateway extension
How does the video platform deployment give corresponding user permissions to the software package files?
HDLBits-&gt;Circuits-&gt;Arithmetic Circuitd-&gt;3-bit binary adder
Teacher lihongyi from National Taiwan University - grade Descent 2