StatefulWidgetとは何か、ライフサイクルと動作原理

著者: IT Sectr 公開日: 2026-07-01 読了時間: 9 分

StatefulWidgetは、可変状態を持つFlutterウィジェットで、UIがユーザーアクション、非同期イベント、データストリームに反応できるようにします。公式Flutterドキュメント(Flutter.dev、2026)によると、StatefulWidgetはアプリケーションのすべてのインタラクティブ要素(入力フォーム、アニメーション、チェックボックス、スイッチ、ネットワークからデータをロードする画面)に使用されます。StatelessWidgetとは異なり、StatefulWidgetはライフサイクル全体を通じて永続化する個別のStateオブジェクトを作成し、ウィジェット自体を再作成せずに再構築できます。

重要なポイント

  • StatefulWidgetは実行中に状態を変更でき、setStateを介してUIの再構築をトリガーするウィジェット
  • ライフサイクル — StatefulWidgetはcreateState、initState、didChangeDependencies、build、didUpdateWidget、disposeの段階を経る
  • Stateオブジェクトは状態を保存し、ウィジェットとは独立してその全生涯にわたって存在する個別のオブジェクト
  • setStateはデータ変更後にFlutterにウィジェットの再構築が必要であることを通知する唯一の正当な方法
  • パフォーマンス — StatefulWidgetの過剰な使用はメモリ消費とレンダリング時間を増加させる

StatefulWidgetとは?

StatefulWidgetは、ユーザーアクション、システムイベント、非同期操作に応じて状態を変更できるFlutterクラスです。StatelessWidgetとは異なり、StatefulWidgetは直接レンダリングされず、レンダリングを担当するStateオブジェクトを作成します。WidgetとStateの2つのクラスに分離することで、Flutterはウィジェット自体を再作成せずにUIを再構築でき、頻繁な更新時に大幅なパフォーマンス上の利点を提供します。

StatefulWidgetのアーキテクチャは“可変と不変の分離”パターンに従います。ウィジェット自体は(StatelessWidgetのように)不変のままですが、すべての可変状態は個別のStateオブジェクトに保存されます。これにより、FlutterはタイプとKeyで比較してウィジェットを再利用でき、再構築間で実際の状態を保持できます。

Google(Flutter Architectural Overview、2026)によると、StatefulWidgetはウィジェットの存続期間中に状態が複数回変化するシナリオ(テキストフィールド、アニメーション、タイマー、データストリーム、非同期ロード)に最適です。1回限りの初期化にはStatelessWidgetで十分です。

StatefulWidgetが必要な場合

StatefulWidgetは、ウィジェットが外部イベント(ボタンクリック、HTTPリクエストの完了、データベース更新、WebSocket購読)に応答する必要がある場合に必須です。また、アニメーション付きウィジェット、コントローラー付きテキストフィールド、フォーカスを管理するコンポーネントにも必要です。ウィジェットが単にデータを表示するだけでイベントを生成しない場合は、StatelessWidgetを使用してください。

内部構造

StatefulWidgetは2つのクラスで構成されています。StatefulWidget自体(軽量、不変)とState(重量、可変)です。フレームワークは、ツリーに挿入される際に1回呼び出されるcreateState()メソッドを介してStateを作成します。Stateはwidgetプロパティを介してウィジェットへの参照を取得し、ライフサイクルの任意の時点でそのフィールドにアクセスできます。

StatefulWidgetのライフサイクル

ライフサイクルは6つの主要な段階で構成され、各段階は特定のタスクを実行するためのオーバーライド可能なメソッドを提供します。これらの段階を理解することは、適切なリソース管理とメモリリークの回避に不可欠です。

createState

createStateは、StatefulWidgetがツリーに挿入される際に呼び出される最初のライフサイクルメソッドです。このウィジェットに関連付けられた新しいStateインスタンスを返す必要があります。このメソッドは要素の全生涯で正確に1回呼び出されます。ここで重い操作を行わないことが重要です — createStateは可能な限り軽量であるべきです。

initState

initStateはStateの作成直後、最初のUI構築の前に呼び出されます。ここではコントローラー(TextEditingController、AnimationController)の初期化、データストリーム(StreamSubscription)の購読、タイマーのセットアップ、フィールドの初期化を行います。Flutterドキュメント(Flutter.dev、2026)によると、initStateではBuildContext.of()を呼び出せません — ツリーがまだ完全にマウントされていません。

didChangeDependencies

didChangeDependenciesはinitStateの後、およびInheritedWidgetの依存関係が変更されるたびに呼び出されます。これはMediaQuery.of(context)の呼び出しやThemeの購読に適した場所です — アプリケーションの実行中に変更される可能性のある値です。ウィジェットがInheritedWidgetを使用する場合、初期化ロジックはinitStateではなくここに配置する必要があります。

buildとdidUpdateWidget

buildはウィジェットツリーを返す主要なメソッドです。initStateの後、didChangeDependenciesの後、および各setStateの後に呼び出されます。didUpdateWidgetは親が再構築され、新しいパラメーターを持つStatefulWidgetを渡す際に呼び出されます。ここで古いウィジェットフィールドと新しいものを比較し、必要に応じて状態を更新できます。

dispose

disposeはライフサイクルの最終段階です。ここですべてのリソースが解放されます。ストリームの購読解除、コントローラーの破棄、タイマーのキャンセルなどを行います。disposeを呼び出さないとメモリリークが発生します。dispose後、Stateは死亡したと見なされ、その内部でsetStateを呼び出すと例外がスローされます。

StatefulWidgetの仕組み

StatefulWidgetの動作メカニズムは、Widget(軽量な記述)、Element(中間層)、State(データストレージ)の3つのエンティティの協調動作に基づいています。Flutterが記述内でStatefulWidgetを検出すると、StatefulElementを作成し、createStateを呼び出してStateオブジェクトへの参照を保存します。親が再構築されると、Flutterは新しいウィジェットを現在のElementと比較します — タイプとKeyが一致する場合、Elementは更新され、Stateは同じままです。

状態はsetState呼び出しを介してのみ変更され、フレームワークに再構築の必要性を通知します。重要なのは、setStateは自動的に状態を変更せず、ウィジェットを“ダーティ”としてマークするだけです。開発者はsetStateに渡されたコールバック内でStateフィールドを独立して更新します。コールバック完了後、Flutterはbuildを呼び出してUIを更新します。

Dart/Flutterチーム(Dart Language Specification、2026)によると、この分離により、buildが呼び出される前にすべての状態変更が同期的に発生し、UIが部分的に更新されたデータを表示する状況を排除します。これはFlutterにおけるインターフェース一貫性の重要なメカニズムです。

Dartコード例

簡単なStatefulWidget — ボタンクリックカウンターを見てみましょう。基本的なパターン(Stateの作成、initStateでのフィールド初期化、setStateによる変更)を示しています。

dart
class CounterScreen extends StatefulWidget {
  const CounterScreen({super.key});

  @override
  State<CounterScreen> createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: _increment,
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

非同期データ読み込みとライフサイクル管理の例。StatefulWidgetがネットワークからデータを読み込み、読み込み状態を表示します。

dart
class UserProfilePage extends StatefulWidget {
  final String userId;
  const UserProfilePage({super.key, required this.userId});

  @override
  State<UserProfilePage> createState() => _UserProfilePageState();
}

class _UserProfilePageState extends State<UserProfilePage> {
  UserModel? _user;
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    _loadUser();
  }

  Future<void> _loadUser() async {
    final user = await UserService.fetchUser(widget.userId);
    setState(() {
      _user = user;
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoading) return const CircularProgressIndicator();
    return Text('Hello, ${_user!.name}');
  }
}

2つ目の例で重要な点:initStateは非同期操作を開始しますが、メソッド自体は非同期ではありません。非同期性は別のメソッド_loadUser内でasync/awaitを介して実装され、リクエスト完了後にsetStateを介して状態を更新します。このアプローチにより、データを受信する前にウィジェットがローディングインジケーターを正しく表示できます。

StatefulWidget vs StatelessWidget

StatefulWidgetとStatelessWidgetの選択は、単に状態を持つかどうかだけの問題ではありません。StatefulWidgetはinitState、didChangeDependencies、didUpdateWidget、disposeメソッドを持つ完全なライフサイクルを提供し、コントローラー、アニメーション、ストリームの操作に必要です。一方、StatelessWidgetにはこれらのメソッドがなく、フレームワークにとって常に軽量です。

Flutterチームの推奨(Flutter docs、2026)は、状態をツリーの上位に移動(State Hoisting)するか、状態管理ソリューション(Riverpod、Bloc、Provider)を使用して、アプリケーション内のStatefulWidgetの数を最小限に抑えることです。各StatefulWidgetは要素が削除されるまで存続するStateオブジェクトを作成します — このようなウィジェットが多いほど、メモリ負荷が高くなります。

基準StatefulWidgetStatelessWidget
状態可変不変
ライフサイクル6段階buildのみ
Stateオブジェクト別途作成不要
setState利用可能利用不可
購読initState/dispose非対応
constコンストラクタ制限あり完全対応
メモリ消費高い低い

パフォーマンスと最適化

StatefulWidgetはStateオブジェクトを作成・維持する必要があるため、StatelessWidgetよりも多くのリソースを必要とします。ただし、いくつかのルールに従えば、StatefulWidgetの適切な使用はパフォーマンス問題を引き起こしません。まず、StatefulWidgetの深いネストを避けてください — 各レベルがツリー走査にオーバーヘッドを追加します。次に、複雑なStatefulWidgetを複数の単純なウィジェットに分割し、それぞれが状態の自分の部分を担当するようにします。

Flutterパフォーマンス調査(Flutter.dev、2026年2月)によると、FPS低下の最も一般的な原因は、表示が変更されていないStatelessWidgetを含むすべての子孫を再構築する親ウィジェットでのsetState呼び出しです。解決策は、UIの可変部分を個別のStatefulWidgetに抽出し、setStateが必要最小限のウィジェットのみを再構築するようにすることです。

State内でconstを使用することも重要なテクニックです。子ウィジェットがconstとして宣言されている場合、親でsetStateが呼び出されてもFlutterはそれらを再構築しません。これによりフレームワークの負荷が軽減され、フレームレンダリング時間が短縮されます。

頻繁なsetStateを避ける

setState呼び出しは完全なウィジェット再構築をトリガーします。状態が高頻度で変化する場合(アニメーションやデータストリームなど)、手動でsetStateを呼び出す代わりにAnimatedBuilder、ValueListenableBuilder、またはStreamBuilderの使用を検討してください。これらのウィジェットは再構築を最適化し、実際に変更されたUIの部分のみを更新します。

よくある間違い

StatefulWidgetに関する最初のよくある間違いは、dispose後にsetStateを呼び出すことです。ウィジェットがツリーから削除されると、Stateは死亡したと見なされ、setStateを呼び出すと“setState called after dispose”例外がスローされます。これは、ウィジェットが削除された後に非同期操作が完了した場合に最も頻繁に発生します。解決策は、setStateを呼び出す前にmountedフラグを確認するか、disposeで非同期操作をキャンセルすることです。

2つ目の間違いは、buildメソッドで重い計算を実行することです。buildはすべてのsetStateおよびすべての親の再構築時に呼び出されるため、すべての計算は可能な限り軽量であるべきです。リソース集約型の操作が必要な場合は、別のIsolateに移動するか、結果をStateフィールドにキャッシュしてください。

3つ目の間違いは、super.initState()super.dispose()を呼び出さないことです。これらのメソッドをオーバーライドする場合、開発者は親の実装を呼び出す必要があります。そうしないと、フレームワークがElementの状態を適切に管理できず、追跡が困難なバグが発生します。

間違いを避けるための推奨事項

  • 非同期コールバックでsetStateの前に常にmountedを確認する
  • super.initState()super.dispose()を呼び出すことを忘れない
  • build内で直接HTTPリクエストを行わない — initStateを使用する
  • disposeですべての購読を解除する
  • プロジェクトでStatefulWidgetを最小限に使用する

よくある質問

StatefulWidgetとStatelessWidgetの違いは何ですか?

StatefulWidgetはsetStateを介して状態を変更でき、ライフサイクル(initState、dispose)を持ち、個別のStateオブジェクトを作成します。StatelessWidgetは状態を変更できず、ライフサイクルメソッドもありません — 単に渡されたデータを表示するだけです。

createStateは何回呼び出されますか?

createStateは各StatefulElementインスタンスに対して正確に1回呼び出されます。親が複数回再構築されても、ウィジェットのタイプとKeyが変更されない限り、createStateは呼び出されず、既存のStateオブジェクトが使用されます。

disposeを呼び出さないとどうなりますか?

リソースが解放されません。コントローラーはバックグラウンドで動作し続け、ストリーム購読はアクティブなまま、タイマーはキャンセルされません。これによりメモリリークが発生し、dispose後のsetState呼び出しが例外をスローする可能性があります。

StatefulWidgetをconstにできますか?

はい、StatefulWidgetのコンストラクタはconstにできます。ただし、StatelessWidgetほどメリットはありません — Stateオブジェクトは最初の挿入時に依然として作成されます。constはウィジェット自体(軽量ラッパー)にのみ影響し、Stateには影響しません。

didUpdateWidgetメソッドはなぜ必要ですか?

didUpdateWidgetは親が新しいパラメーターでStatefulWidgetを渡す際に呼び出されます。これは状態を新しいデータと同期させるために必要です — 例えば、パラメーターのuserIdが変更された場合、新しいユーザーのプロファイルをロードする必要があります。

まとめ

  • StatefulWidgetは可変状態を持つウィジェットで、データ保存とライフサイクル管理に個別のStateオブジェクトを使用する
  • ライフサイクルはcreateState、initState、didChangeDependencies、build、didUpdateWidget、disposeで構成され、それぞれに独自の目的がある
  • setStateはフレームワークに状態変更を通知する唯一の正当な方法であり、その後buildが自動的に呼び出される
  • mountedはdispose後の例外を避けるために非同期操作でsetStateを呼び出す前に確認すべきフラグ
  • パフォーマンス — StatefulWidgetはStatelessWidgetより多くのリソースを必要とする。状態を外部レイヤーに移動して数を最小限に抑えることを推奨
  • State内のconst子ウィジェットはsetState呼び出し時の再構築量を減らし、パフォーマンスを向上させる
  • 正しい選択 — StatefulWidgetはウィジェットが可変データや非同期操作を管理する必要がある場合にのみ使用する

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください