Flutter嵌套深?扩展函数了解一下

背景

嵌套层级深的问题让众多刚接触Flutter的同学感到困扰,它不仅是看起来让人感到不适,还非常影响编码体验。

大佬们会告诉你应该拆分自己的嵌套代码(自定义widget或者抽取build方法)来减少嵌套层级。这确实是个行之有效的方法,除此之外,还有没有别的方法呢,本文将向您介绍另一种减少嵌套层级的方法。

嵌套过深影响代码的视觉观感

这段代码演示了什么叫做:嵌套地狱

<code>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"),
],
),
),


],
),
),
),
);
}
}/<widget>/<widget>/<widget>/<code>

提取build方法后,嵌套层级得到了明显的改善

<code>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),
],
),
);
}
}/<widget>/<widget>/<code>

还能不能继续优化呢?

自定义扩展函数

举个例子:想要给下面这段代码中的第2个Textwidget加上marginTop:10属性

<code> @override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10),
child: Column(
children: <widget>[
Text('billy'),
Text('say hello'), //add margin top??
],
),
);
}/<widget>/<code>

此时,我内心希望可以这样写:

显然,flutter不支持这么写,幸运的是:dart2.7发布时正式宣布支持扩展函数(Extension Methods)

<code>实际上从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/<code>

先来定义一个扩展函数

<code>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,
);
}
}/<code>

现在,所有widget对象都多了一个intoContainer(...)扩展函数,而且参数与Container构造方法一致,于是,我们就可以这样写了:

除了Container,其它容器也可以通过同样的方式来扩展。于是,编程体验大大提升,再也不用动不动就大段选择代码剪切粘贴了。

还可以支持链式调用:

<code>Text("billy")
.intoExpanded(flex: 1)
.intoContainer(color: Colors.white)/<code>

有些widget有多个子widget (children), 可以添加如下的扩展函数:

<code>extension WidgetExt on Widget {
//添加一个相邻的widget,返回List<widget>
List<widget> addNeighbor(Widget widget) {
return <widget>[this, widget];
}

//添加各种单child的widget容器
//如:Container、Padding等...
}

extension WidgetListExt on List {
//子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等...
}/<widget>/<widget>/<widget>/<widget>/<widget>/<code>

使用扩展函数解决嵌套过深的问题

回到本文最初的嵌套地狱,现在我们的代码可以写成这样

<code>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),);
}
}/<code>

为了让我们的代码更加符合链式编程风格,再定义一个静态方法吧

<code>class WidgetChain {
static Widget addNeighbor(Widget widget) {
return widget;
}
}/<code>

另外,再定义一个从数据到widget的映射扩展方法

<code>extension ListExt on List {

List<widget> buildAllAsWidget(Widget Function(T) builder) {
return this.map<widget>((item) {
return builder(item);
}).toList();
}

}/<widget>/<widget>/<code>

现在,代码是这样的:

<code>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()
);
}
}/<code>

值得指出的是,扩展函数(无嵌套)跟构造函数(有嵌套)是可以混用的。上面的代码也可以写成这样(Container和Offstage这2层改成了构造函数):

<code>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()
),
),
);
}
}/<code>

这样的扩展函数你想不想试试呢?我已经替大家封装好了常用Widget对应的into扩展函数,可以直接食用:

<code>dependencies:
widget_chain: ^0.1.0/<code>

导入:

<code>import 'package:widget_chain/widget_chain.dart';/<code>

然后就可以起飞了!

Github源码地址: widget_chain 敬请star收藏

总结

本文介绍了Flutter中的嵌套地狱,并使用扩展函数的方式来解决flutter的嵌套地狱问题。

由于大篇幅的扩展函数调用会影响代码阅读体验,还是需要保留部分关键嵌套层级结构以使得布局的层级结构保持清晰,文中的扩展函数支持与构造函数混用,具体使用到什么程度,就看大家自己的选择了