Flutter で build メソッドをリファクタするとき Widget は class に分割するべし

www.youtube.com

この Youtube で述べられている内容だが、Flutter で build メソッドがでかくなってきて component に分割するか〜と思う時がある。
その時、 Helper Methods と呼ばれる方法と、 class で Widget を作る方法があるがパフォーマンスの観点から class Widget を利用した方がよい。

どう違うのか?

こういう Widget があったとする。ボタンをタップすると state が更新されるだけの Widget
これを Helper Methods と class Widget の2つのパターンで確認してみる。

class WidgetSample extends StatefulWidget {
  const WidgetSample({Key? key}) : super(key: key);

  @override
  State<WidgetSample> createState() => WidgetSampleState();
}

class WidgetSampleState extends State<WidgetSample> {
  var count = 0;

  @override
  Widget build(BuildContext context) {
    print('build');

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('count $count'),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  count += 1;
                });
              },
              child: const Text('tap'),
            ),
            const Text('very heavy widget.'),
          ],
        ),
      ),
    );
  }
}

Helper Methods

Helper Methods と呼ばれるのは以下のようにメソッドとして widget を切り出す方法である。

@@ -90,12 +90,17 @@ class WidgetSampleState extends State<WidgetSample> {
               },
               child: const Text('tap'),
             ),
-            const Text('very heavy widget.'),
+            _buildVeryHeavyWidget(),
           ],
         ),
       ),
     );
   }
+
+  Text _buildVeryHeavyWidget() {
+    print('very heavy widget.');
+    return const Text('very heavy widget.');
+  }
 }

このコードでボタンをタップすると、state が更新されるたびに print('very heavy widget.'); が呼ばれることを確認できる。

Class Widget

では次に class で Widget を作成し、 const で呼び出す。

               },
               child: const Text('tap'),
             ),
-            const Text('very heavy widget.'),
+            const _VeryHeavyWidget(),
           ],
         ),
       ),
@@ -98,6 +98,18 @@ class WidgetSampleState extends State<WidgetSample> {
   }
 }
 
+class _VeryHeavyWidget extends StatelessWidget {
+  const _VeryHeavyWidget({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    print('very heavy widget.');
+    return const Text('very heavy widget.');
+  }
+}

このコードでボタンをタップすると、state が更新されても print('very heavy widget.'); は呼び出されない。
必要がなければ build method が呼ばれないようになっている。

おわり

簡単な画面構成だったら Helper Methods 方式でも大きな影響は出ないが、動画にも述べられているようにアニメーションが走る画面であったり、地図アプリでユーザがスクロールするたびに state が更新されるうような画面は class にしておかないとチラつきや遅延が目立ってしまうので普段から class で Widget を分割するように心がけておくと良い。