🙇‍♂️

[Flutter] StatefulWidgetとStatelessWidgetの違いについての備忘録

2023/01/28に公開

執筆背景

  • Flutterでの実務の開発を通して、ConsumerWidgetを扱っているが「そもそもWidgetsのそれぞれがどんなもので、どんな働きを持っているか」を全然理解出来ていないなと思ったため、自らのアウトプットも兼ねて執筆をした。
  • 今回は、StatefulWidget及びStatelessWidgetの2つにフォーカスして、執筆を行う。InheritedWidgetsや本題であるConsumerWidgetについても、今後アウトプットしていきたい。

開発環境

  • Flutter 3.3.10
  • Dart 2.18.6

結論

Widget名 どんなWidgetか
StatelessWidget 状態を変更する必要のないウィジェット
StatefulWidget 変更可能な状態をもつウィジェット

あまり正確な表現ではないが、静的なウィジェットを描画したければStatelessWidgetを、何かしら動的なウィジェットを描画したければStatefulWidgetを使用する必要がある。

Stateless Widgetとは

StatelessWidgetは、UIをより具体的に描画するためのプロセスを構築し、UIの部分的な描画を実現するためのウィジェットである。ビルドプロセスの構築はUIが完全に描画されるまで、再帰的に継続される。

StatelessWidgetは、UIがWidget内のBuildContextやオブジェクト自体の設定情報に依存しない際に、有用である。システムの状態に依存したり、Clockを内部的に使うことによって、動的な変更が発生する場合はStatelessWidgetではなく、StatefulWidgetを使用しなければならない。

親Widgetが定期的にWidgetの情報設定を変更する場合であったり、InheritedWidgetに依存する場合は、レンダリングパフォーマンスを維持するbuildメソッドのパフォーマンスを最適化する必要がある。

Stateful Widgetとは

StatefulWidgetは、StatelessWidgetと同様に、UIをより具体的に描画するためのプロセスを構築し、UIの部分的な描画を実現するためのウィジェットである。ビルドプロセスの構築はUIが完全に描画されるまで、再帰的に継続される。

StatefulWidgetは、記述されているUIが動的に変更される場合に有用である。UIがWidget内のBuildContextやオブジェクト自体の設定情報に依存しない際は、StatelessWidgetの使用を推奨されている。

StatefulWidgetのインスタンスは可変で、それらの可変な状態を保存することが可能である。StateオブジェクトやStreamオブジェクト、ChangeNotifierオブジェクトなどに状態を格納し、その参照はStatefulWidgetのfinalに格納される。

StatefulWidgetでは、createState()メソッドや、setState()メソッドを使用する必要がある。createState()では、使用したいStateを指定し、setState()のコードブロック内では動的に変更したい処理を実装することで状態の変更ができる。

Stateとは

Widgetがビルドされるときに同期的に読み取ることができ、Widgetのライフタイム中に変更される可能性がある情報である。Widgetの状態が変更されたとき、State.setStateを使用して速やかに通知されるようにすることが必要である。

サンプルコード

Flutterの公式サンプルである、カウントアプリのコードをそのまま持ってきた。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

上記のコードでは、StatelessWidgetとStatefulWidget、それぞれが使用されていることが確認できる。

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

上記コード内のStatelessWidgetでは、画面が動的に変更される必要がなく、かつ一回だけ呼び出せばそれで十分であるため、StatelessWidgetを継承する。

また、MaterialApp Widget内のhome:でMyHomePageメソッドを呼び出す。

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

createState()内で、このMyHomePage内で用いるStateがどのStateになるのかをきちんと指定してあげる必要がある。その際、@overrideをする必要がある。

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

Stateを継承した _MyHomePageState Class。動的に変更したい場所(今回はFloatingButtonが押された際に+1ずつインクリメントする処理)についてはsetState()で囲ってあげる必要がある。

最後に

簡単にStatelessWidgetとStateFulWidgetの違いについて、初心者なりに勉強してまとめてみた。元ページの英語を上手に日本語で翻訳できているか自信がないが、誰かの参考になると幸いだ。


今回初めてZennへ記事を投稿してみましたが、もし至らない点などございましたら、お気軽にコメントなどでご指導いただけると幸いです。よろしくお願いいたします。

参考ページ

https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html
https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html

Discussion