はじめに
Flutterでは、setState()
を使うと、build()
メソッドが再実行され、ウィジェットツリー内のすべてのウィジェットが再生成されます。アプリが複雑になると、こうした無駄な再ビルドがパフォーマンス低下の原因になります。
そこで、この記事では、「クラス分け」と「const
の活用」を使って、無駄な再ビルドを防ぐ最適化テクニックを解説します。
1. setState()
による再ビルドの仕組み
setState()
が呼ばれると、そのState
クラスのbuild()
メソッドが再実行され、ツリー内のすべてのウィジェットが再生成されます。
例えば、次のようにカウンターの値が変わるたびに、CounterPage
のbuild()
が再実行されます。
void _incrementCounter() {
setState(() {
_counter++;
});
}
このような無駄な再ビルドを減らすことが、Flutterアプリのパフォーマンスを向上させるために重要です。
2. const
の役割と制限
2.1 const
の役割
const
を使うと、Flutterは「このウィジェットは変わらない」と判断し、再生成をスキップします。
親ウィジェットのbuild()
が再実行されても、「このウィジェットは前回と同じ」と判断できれば、再描画がスキップされます。
2.2 const
の制限
ただし、const
は状態に依存しないウィジェットでしか使えません。
例えば、_counter
のような変数が含まれるウィジェットにはconst
を付けられません。
// ❌ エラーになります
const Text('$_counter'); // 状態に依存するためconst不可
また、同じbuild()
内にconst
を使っても、Flutterは毎回「このウィジェットが同じかどうか」を判断しなければなりません。これが、「クラス分けが必要な理由」です。
以下に詳細をまとめました。
3. なぜクラス分けが必要なのか?
クラス分けをすることで、Flutterが「このウィジェットは変わらない」という判断を1回だけで済ませられるようになります。
具体的には、クラスごとにconst
を付けることで、親ウィジェットのsetState()
が呼ばれても、そのクラス全体を再生成しなくて済むようになります。
クラス分けのメリット
-
クラス分けをしない場合:
-
setState()
が呼ばれるたびに、build()
内のウィジェットが再生成されます。 -
const
を付けていても、毎回「同じかどうか」の判断コストが発生します。
-
-
クラス分けをした場合:
-
const
付きのクラスはFlutterが1回だけ「同じ」と判断し、再描画も再生成も完全にスキップします。
-
4. コード例:クラス分けによる最適化
改善前:クラス分けしていない場合
class CounterPage extends StatefulWidget {
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
print('CounterPage build');
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$_counter', style: const TextStyle(fontSize: 48)),
const SizedBox(height: 20),
GestureDetector(
onTap: _incrementCounter,
child: Container(
padding: const EdgeInsets.all(16.0),
color: Colors.blue,
child: const Text('Tap to Increment'),
),
),
],
),
),
);
}
}
問題点:
-
setState()
を呼ぶたびに、build()
内のウィジェットがすべて再生成されます。 -
const
を付けていても、毎回「同じか」を比較する処理が発生します。
改善後:クラス分けして最適化する場合
class CounterPage extends StatefulWidget {
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
print('CounterPage build');
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CounterDisplay(counter: _counter), // 状態に依存する部分
const SizedBox(height: 20), // 再利用される
const IncrementArea(), // 再利用される
],
),
),
);
}
}
class CounterDisplay extends StatelessWidget {
final int counter;
const CounterDisplay({required this.counter});
@override
Widget build(BuildContext context) {
print('CounterDisplay build');
return Text('$counter', style: const TextStyle(fontSize: 48));
}
}
class IncrementArea extends StatelessWidget {
const IncrementArea();
@override
Widget build(BuildContext context) {
print('IncrementArea build');
return GestureDetector(
onTap: () => print('Tapped!'),
child: Container(
padding: const EdgeInsets.all(16.0),
color: Colors.blue,
child: const Text('Tap to Increment'),
),
);
}
}
改善点:
-
CounterDisplay
だけが_counter
の変化に応じて再ビルドされます。 -
IncrementArea
とSizedBox
はconst
なので、1度だけ判断され、以降は再利用されます。
5. まとめ
-
クラス分けと**
const
の活用**により、Flutterの「このウィジェットは変わらない」という判断を1回だけにできます。 - クラス分けせず、同じ
build()
内にconst
を持たせると、毎回「同じかどうか」を判断する手間がかかります。 - この最適化により、パフォーマンスが向上し、よりスムーズなアプリを提供できます。
おわりに
Flutterでの開発では、「状態に依存する部分」と「依存しない部分」をしっかり分けることが重要です。
この最適化手法を活用して、無駄な再ビルドを減らし、より効率的なアプリ開発を目指しましょう!