前言
初学 Flutter 的时候,一个很大的感受就是组件嵌套层级很深,写下来的代码找对应的括号都找不到。比如下面这种情况,从最外层的 Scaffold
到最里层的Image.asset
,一共有7层组件嵌套。这还不算多的,最夸张是见过一个表单页面写了10多层,代码的阅读体验非常糟糕,而且如果不小心删除了一个括号要找半天才对应得上。当然,通过 VSCode 彩虹括号(Rainbow Brackets)这个插件能够一定程度上解决括号对称查找得问题,但是代码的可维护性、阅读体验还是很差。自然而然,大家会想到拆分。拆分有两种方式,一种是使用返回Widget
的函数,另一种是使用 StatelessWidget
,那这两种该如何选择呢?
拆分原则
在关于这个问题的讨论上,2年前 StackOverflow 有一个经典的回答:使用函数和使用类来构建可复用得组件有什么区别?其中提到得一个关键因素是 Flutter 框架能够检测组件树的类对象,从而提高复用性。而对于私有的方法来说 Flutter 在更新的时候并不知道该如何处理。
StatelessWidget和函数式组件.png
答主也对比了使用类和函数的优劣势。使用类构建的方式:
- 支持性能优化,比如使用 const 构造方法,更细颗粒度的刷新;
- 两个不同的布局切换时,能够正确地销毁对应得资源。这个我们在上篇讲 StatefulWidget 的时候有介绍过。
- 保证正确的方式进行热重载,而使用函数可能破坏热重载。
- 在 Widget Inspector 中可以查看得到,从而可以方便我们定位和调试问题。
- 更友好的错误提示。当组件树出现错误时,框架会给出当前构建得组件名称,而如果使用函数的话则得不到清晰得名词。
- 可以使用 key 提高性能。
- 可以使用 context 提供的方法(函数式组件除非显示地传递 context)。
使用函数构建组件唯一的优势就是代码量会更少,不过这可以通过 functional_widget 插件解决,functional_widget是一个通过注解将和函数式组件构建方式自动转换为类组件的代码生成插件)。
示例对比
下面我们看一段没有拆分的代码,这个仅仅是示例代码,没有任何实际意义。
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Row(
children: [
Text('Counter: $_counter'),
Container(
child: Column(
children: [
Text('Hello'),
Row(
children: [
Text('there'),
Text('world!'),
],
),
],
),
),
],
);
}
}
括号有点多,对吧,一眼看过去都懵圈了 —— 这也是很多初次接触 Flutter 的人吐槽地方,可以说让不少人直接放弃了! 最直接的方式就是将部分代码抽离成为一个私有方法,比如像下面这样。
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _counter = 0;
Widget _buildNonsenseWidget() {
return Container(
child: Column(
children: [
Text('Hello'),
Row(
children: [
Text('there'),
Text('world!'),
],
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Row(
children: [
Text('Counter: $_counter'),
_buildNonsenseWidget(),
],
);
}
}
将深度嵌套的组件代码单独抽成了一个返回 Widget 的私有方法,看起来确实让代码简洁不少。
那么问题就解决了吗?我们来看一下当状态改变的时候会发生什么。
我们知道,当状态变量_counter改变后,Flutter 会调用 build
方法刷新组件。这会导致 _buildNonsenseWidget
这个方法在刷新的时候每次都会被调用,意味着每次都会创建新的组件来替换旧的组件,即便两个组件没有任何改变。而事实上,我们应该只重建那些变化的组件,从而提高性能。
现在再来看使用类组件的方式,实际上有代码模板的情况下,编写一个 StatelessWidget 非常简单。使用类组件后的代码如下所示。代码确实会比函数的方式多,但是实际上大部分不需要我们手敲。
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Row(
children: [
Text('Counter: $_counter'),
// The deeply nesting widget is now refactored into a
// stateless const widget. No more needless rebuilding!
const _NonsenseWidget(),
],
);
}
}
class _NonsenseWidget extends StatelessWidget {
const _NonsenseWidget();
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Text('Hello'),
Row(
children: [
Text('there'),
Text('world!'),
],
),
],
),
);
}
}
这里注意,以为这个_NonsenseWidget
在组件得声明周期不会改变,因此使用了 const
的构造方法。这样在刷新过程中,就不会重新构建了!关于 const 可以参考之前的两篇文章。
关于 StatefulWidget,你不得不知道的原理和要点!
总结
相比使用函数构建复用的组件代码,请尽可能地使用类组件的方式,而且尽可能地将组件拆分为小一点的单元。这样一方面可以提供精确的刷新,另一方面则是可以将组件复用到其他页面中。如果你不想改变自己得习惯,那么可以考虑使用 functional_widget 这个插件来自动生成类组件。
网友评论