【Flutter】Widget extensions vs Class constructorについてまとめてみた
英語圏のFlutter開発者の間で、Widget extensionsを使ってSwiftUIのmodifierのような書き方を再現する方が今のネストしていく方法より扱いやすいのではという話が挙がりました。
日本のエンジニアの方々がどう思うのかも気になったので、若干の私情を挟みつつ内容をサッとまとめてみました。
サンプルコードで説明
前提の説明はサンプルコードを見れば大体伝わると思います。
// いつもの書き方
class ClassConstructorPattern extends StatelessWidget {
const ClassConstructorPattern({
super.key,
});
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
const Center(
child: ColoredBox(
color: Colors.blue,
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
'hello',
style: TextStyle(
fontSize: 20,
),
),
),
),
),
Align(
alignment: Alignment.centerLeft,
child: GestureDetector(
onTap: () {
print('tapped');
},
child: const ColoredBox(
color: Colors.blue,
child: Text(
'world',
style: TextStyle(
fontSize: 20,
),
),
),
),
),
],
),
);
}
}
// 上のレイアウトをextensionを使って再現
class ExtensionPattern extends StatelessWidget {
const ExtensionPattern({
super.key,
});
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
const Text('hello')
.fontSize(20)
.paddingAll(16)
.backgroundColor(Colors.blue)
.centered,
const Text('world')
.fontSize(20)
.backgroundColor(Colors.red)
.align(
Alignment.centerLeft,
)
.onTap(
() {
print('tapped');
},
),
],
),
);
}
}
このようにWidgetのextensionを作ればSwiftUIみたいな記法を再現できます。
extension WidgetExtension on Widget {
Widget paddingAll(double value) => Padding(
padding: EdgeInsets.all(value),
child: this,
);
Widget get centered => Center(child: this);
Widget align(AlignmentGeometry value) => Align(
alignment: value,
child: this,
);
Widget fontSize(double value) => DefaultTextStyle(
style: TextStyle(fontSize: value),
child: this,
);
Widget backgroundColor(Color value) => ColoredBox(
color: value,
child: this,
);
Widget onTap(VoidCallback onTap) => GestureDetector(
onTap: onTap,
child: this,
);
}
Widget extensionsを使う理由
Lukeさんのこのビデオが簡潔に理由を述べてたので紹介します。
要点をまとめると、extensionが優れている点として
- 並び替え、構築が簡単
- 並び方が感覚的
- 手段が絞られる
を挙げられてました。最初に貼ったシンプルな例で考えれば概ね同意できます。
extensionさえ用意できれば簡単に作れますし、Textという目的が最初に来て、その修飾が後からくる方が感覚的であるという点も理解できます。(私たち日本語話者は修飾が先に来る構造に慣れてるので何とも言えませんが)
「手段が絞られる」という点については、そのプロジェクトのextensionに.container
みたいな全てを雑に束ねた存在がなければそうだと思います。あるいは入れ子構造で作るのと比べて記述量が少ないため、雑に束ねる動機がないってことを伝えたいのかもしれません。
Widget extensionsを使わない理由
正直私は使わない派で、Romain Rastelさんのこのツイートに共感したので紹介します。
スレッドを訳すと大体このようなことが書かれています。
- 全てをextensionで書くわけではないので読む方向が統一されない。場所によって上から下、下から上になる。
- extensionを許容することでFlutter内での記法の揺らぎが出てしまう
- extensionを自前で作らなければいけない
- modifierにするかしないかの分岐が不明瞭
- constが使えない
1についてだけ補足すると、最初に貼った例だとScaffold, Columnは上から下へ修飾関係にありましたが、Textから続いてるextensionは下から上の修飾関係にあります。
また、仮にextensionを各々のプロジェクトで自前のものを作るとなると、メソッドの名前や引数、返すWidgetの粒度なのでばらつきが生じるはずなので、プロジェクトごとの差異がより大きくなって潰しが効かなくなると思います。
こういった理由で、少なくとも私はextensionで進んで実装しようという気にはなりません。世間一般の記法から逸脱してしまう以上、余程の強い意志がないと書こうとはならないと思います。
まとめ
Flutterの実装方法としてどうだろうって観点で紹介しましたが、Flutterの文脈や開発者個人の経験を抜きにして、 これらのパターン自体の本質的な評価をするのであれば話は変わってくると思います。
私はSwiftUIやFlutterのWidget extensionでアプリを作った経験が無いのでこの点での意見はできませんが、もしこのトピックにおいて知見がある方がいればコメントなり意見をシェアしていただけると嬉しいです!
Discussion
拡張メソッドを使ったパッケージとしてはstyled_widgetなどが有名です
準標準パッケージのflutter_animateも拡張メソッドによる記法サポートしてますね
コメントありがとうございます〜!数字を見ると結構使われてますね。
このvelocity_xっていうパッケージも人気そうです👀