背景
嵌套层级深的问题让众多刚接触Flutter的同学感到困扰,它不仅是看起来让人感到不适,还非常影响编码体验。
老手们会告诉你应该拆分自己的嵌套代码(自定义widget或者抽取build方法)来减少嵌套层级。这确实是个行之有效的方法,但这只是解决了最终代码的视觉观感问题,编码体验还是不太理想。
嵌套过深影响代码的视觉观感
这段代码演示了什么叫做:嵌套地狱
class Test extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo'),),
body: Container(
child: Offstage(
offstage: false,
child: ListView(
children: <Widget>[
Container(
color: Colors.white,
padding: EdgeInsets.all(20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.phone),
Text("amy"),
],
),
),
Container(
color: Colors.white,
padding: EdgeInsets.all(20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.phone),
Text("billy"),
],
),
),
],
),
),
),
);
}
}
提取build方法后,情况好多了
class Test extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo'),),
body: Container(
child: Offstage(
offstage: false,
child: ListView(
children: <Widget>[
buildItem("amy"),
buildItem("billy"),
],
),
),
),
);
}
Container buildItem(String name) {
return Container(
color: Colors.white,
padding: EdgeInsets.all(20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.phone),
Text(name),
],
),
);
}
}
但,Flutter嵌套层级的问题不仅体现在代码的视觉观感上,还体现在编码过程中的体验
影响编码体验
如果想要给某个widget加一个父容器,需要三步走:
- 剪切当前widget的及其子widget的所有代码到粘贴板中
- 在剪切处编写新增的父容器代码
- 在父容器下添加child,并将刚才剪切的代码粘贴赋值给child
举个例子:想要给下面这段代码中的第2个Text
widget加上marginTop:10
属性
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10),
child: Column(
children: <Widget>[
Text('billy'),
Text('say hello'), //add margin top??
],
),
);
}
当时,我是这样写的:
- 先把
Text('say hello')
剪切 - 在剪切处编写Container并设置margin属性
- 在Container节点下添加child,粘贴刚才剪切的内容作为child的值

实际编码过程中,这种情况频繁出现,特别是代码行数比较多的时候,这是非常糟糕的体验。
此时,我内心希望可以这样写:

显然,flutter不支持这么写,幸运的是:dart2.7发布时正式宣布支持扩展函数(Extension Methods)
实际上从dart 2.6.0就开始支持扩展函数了
如果pubspec.yaml中设置的dart版本低于2.6.0则会出现警告提示
如:
environment:
sdk: ">=2.1.0 <3.0.0"
警告提示:
Extension methods weren’t supported until version 2.6.0
用扩展函数提升编码体验
先来定义一个扩展函数
extension WidgetExt on Widget {
Container intoContainer({
//复制Container构造函数的所有参数(除了child字段)
Key key,
AlignmentGeometry alignment,
EdgeInsetsGeometry padding,
Color color,
Decoration decoration,
Decoration foregroundDecoration,
double width,
double height,
BoxConstraints constraints,
EdgeInsetsGeometry margin,
Matrix4 transform,
}) {
//调用Container的构造函数,并将当前widget对象作为child参数
return Container(
key: key,
alignment: alignment,
padding: padding,
color: color,
decoration: decoration,
foregroundDecoration: foregroundDecoration,
width: width,
height: height,
constraints: constraints,
margin: margin,
transform: transform,
child: this,
);
}
}
现在,所有widget对象都多了一个intoContainer(...)
扩展函数,而且参数与Container
构造方法一致,于是,我们就可以这样写了:

除了Container
,其它容器也可以通过同样的方式来扩展。于是,编程体验大大提升,再也不用动不动就大段选择代码剪切粘贴了。
还可以支持链式调用:
Text("billy")
.intoExpanded(flex: 1)
.intoContainer(color: Colors.white)
有些widget有多个子widget (children), 可以添加如下的扩展函数:
extension WidgetExt on Widget {
//添加一个相邻的widget,返回List<Widget>
List<Widget> addNeighbor(Widget widget) {
return <Widget>[this, widget];
}
//添加各种单child的widget容器
//如:Container、Padding等...
}
extension WidgetListExt<T extends Widget> on List<T> {
//子List<Widget>列表中再添加一个相邻的widget,并返回当前列表
List<Widget> addNeighbor(Widget widget) {
return this..add(widget);
}
Row intoRow({
Key key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
}) {
return Row(
key: key,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection,
verticalDirection: verticalDirection,
textBaseline: textBaseline,
children: this,
);
}
//添加其它多child的widget容器
//如:Column、ListView等...
}
到现在,不知大家有没有发现,这个扩展函数不仅仅是能在局部优化我们的编码体验那么简单了,还能解决代码嵌套层级过深的问题。
使用扩展函数解决嵌套过深的问题
回到本文最初的嵌套地狱,现在我们的代码可以写成这样
class Test extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo'),),
body: buildItem("amy")
.addNeighbor(buildItem("billy"),)
.intoListView()
.intoOffstage(offstage: false)
.intoContainer()
);
}
Container buildItem(String name) {
return Icon(Icons.phone)
.addNeighbor(Text(name))
.intoRow(crossAxisAlignment: CrossAxisAlignment.center,)
.intoContainer(color: Colors.white, padding: EdgeInsets.all(20),);
}
}
为了让我们的代码更加符合链式编程风格,再定义一个静态方法吧
class WidgetChain {
static Widget addNeighbor(Widget widget) {
return widget;
}
}
另外,再定义一个从数据到widget的映射扩展方法
extension ListExt<T> on List<T> {
List<Widget> buildAllAsWidget(Widget Function(T) builder) {
return this.map<Widget>((item) {
return builder(item);
}).toList();
}
}
现在,代码是这样的:
class Test extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo'),),
body: ["amy", "billy"]
.buildAllAsWidget((name) =>
WidgetChain
.addNeighbor(Icon(Icons.phone))
.addNeighbor(Text(name))
.intoRow(crossAxisAlignment: CrossAxisAlignment.center,)
.intoContainer(color: Colors.white, padding: EdgeInsets.all(20),)
)
.intoListView()
.intoOffstage(offstage: false)
.intoContainer()
);
}
}
值得指出的是,扩展函数(无嵌套)跟构造方法(有嵌套)是可以混用的。上面的代码也可以写成这样:
class Test extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo'),),
body: Container(
child: Offstage(
offstage: false,
child: ["amy", "billy"]
.buildAllAsWidget((name) =>
WidgetChain
.addNeighbor(Icon(Icons.phone))
.addNeighbor(Text(name))
.intoRow(crossAxisAlignment: CrossAxisAlignment.center,)
.intoContainer(color: Colors.white, padding: EdgeInsets.all(20),)
)
.intoListView()
),
),
);
}
}
这样的扩展函数你想不想要试试呢?大量常用的Widget的into扩展函数我已经替大家封装好了,可以直接食用:
dependencies:
widget_chain: ^0.1.0
导入:
import 'package:widget_chain/widget_chain.dart';
然后就可以起飞了!
Github源码地址: widget_chain 敬请star收藏
总结
本文介绍了Flutter中的嵌套地狱,以及由于嵌套导致编码体验不佳的问题,并从解决编码体验出发,使用扩展函数解决了flutter的嵌套地狱。
由于大篇幅的扩展函数调用会影响代码阅读体验,还是需要保留部分关键嵌套层级结构以使得布局的层级结构保持清晰,文中的扩展函数支持与构造函数混用,具体使用到什么程度,就看大家自己的选择了
网友评论