哈喽,我是老刘
前段时间有人问我这个问题碰到没有:
Flutter - 升级3.19之后页面多次rebuild? - 技术栈 (jishuzhan.net)
说实话我们没有碰到这个问题
我先来简单解释一下这个问题,本质上是因为使用了 InheritedWidget
通过InheritedWidget向子树传递数据
InheritedWidget可以向其子树中的所有Widget提供数据。这使得无关的Widget能方便地获取同一个InheritedWidget提供的数据,实现Widget树中不直接相关Widget之间的数据共享。
Flutter SDK 中正是通过 InheritedWidget 来共享应用主题(Theme)和 Locale(当前语言环境)信息的。
其使用方法如下
实现一个InheritedWidget
class MyInheritedWidget extends InheritedWidget { // 继承InheritedWidget
final int data;
MyInheritedWidget({required this.data, required Widget child}) : super(child: child);
@override
// 这个方法定义了当数据发送变化时是否通知子树中的子Widget。
// 它返回一个布尔值,true表示通知子Widget,false表示不通知。
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return oldWidget.data != data;
}
// 子Widget可以通过调用MyInheritedWidget.of()静态方法来获取MyInheritedWidget实例,并获取其提供的数据。
static MyInheritedWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>()!;
}
}
获取InheritedWidget中的数据
class MyText extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
'${MyInheritedWidget.of(context).data}',
style: Theme.of(context).textTheme.headline4,
);
}
}
当InheritedWidget中的数据发生变化时,就会通知所有通过InheritedWidget.of()方法注册了关注数据变化的成员。
这时这些子树中的组件就会重绘。
其实前面文章中的问题就是当你使用ModalRoute.of(context)方法获取页面路由的参数时,其实也就是向一个全局级别的InheritedWidget节点注册了关注其变化。
而当这个全局的路由节点的行为发生变化后(页面退栈通知数据变化),就会出现原先没有的重绘现象出现了。
为什么我们的代码没有这个问题
我们在传递页面参数时其实是没有使用ModalRoute.of(context)的方式获取页面参数的。
我们使用的是页面类的构造参数。
举个例子,比如打开一个商品详情页,需要传递商品id作为页面参数。
代码如下
class ProductDetailPage extends StatefulWidget {
final String productId;
const ProductDetailPage({required this.productId});
@override
_ProductDetailPageState createState() => _ProductDetailPageState();
}
class _ProductDetailPageState extends State<ProductDetailPage> {
@override
void initState() {
super.initState();
// 使用productId获取商品详情
}
@override
Widget build(BuildContext context) {
// 根据productId构建UI
return Scaffold(
// ...
);
}
}
定义路由可以使用动态生成路由的方式:
MaterialApp(
onGenerateRoute: (RouteSettings settings) {
// 通过settings.name可以获取传入的路由名称,并据此返回不同的路由
final productId = settings.arguments['id'] as String;
return MaterialPageRoute(
builder: (context) => ProductDetailPage(productId: productId),
);
}
)
那为什么我们要选择这种方式传递页面参数,而不是ModalRoute.of(context)的方式呢?
这其实是一种本能,一种下意识的行为。
两个思维习惯
1、减少不可控因素
老刘写了十多年的代码了,光Flutter就写了快6年。
这么多年的实战形成的习惯就是对不可控的外部依赖心怀警惕。
InheritedWidget就是一种很典型的场景。
如果是自己写的InheritedWidget还好,但如果是外部的,比如系统SDK的。
那么你怎么保证它通知的数据变化时你想要的呢?
这次的问题不就是很典型的例子吗。
远隔千里之外的人修改了几行代码,就对你的App的行为造成了影响。
2、模块化思维
也许你觉得写一个页面就是一个页面。
但是在我看来,很有可能某一天它就是某个页面的一个组件。
假设有一天你的产品要适配pad端
那么很有可能商品列表页和商品详情页会合并成一个页面:左边是列表右边是详情。
这时候原先独立的详情页就是页面的一个组件了。
这时候是不是通过构造参数传递商品id会合理很多?
总结
我们从一个实际的bug出发,解释了为什么建议大家通过构造参数进行页面传参。
进而引出了关于日常编码中的一些很具体的思维习惯。
总之很多时候最简单直接的用法可能也是最好的选择。
好了,如果看到这里的同学有学习Flutter的兴趣,欢迎联系老刘,我们互相学习。
点击免费领老刘整理的《Flutter开发手册》,覆盖90%应用开发场景。
可以作为Flutter学习的知识地图。
覆盖90%开发场景的《Flutter开发手册》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。