45
Help us understand the problem. What are the problem?

posted at

そのWidget、methodで分けるか?classで分けるか?

はじめに

Flutterで開発をしていて、Widgetの構造が複雑になった際にある程度の意味のあるまとまりでWidgetを切り分けることがよくあると思います。宣言的UI自体、ネストが深くなりがちで1つのクラス内で複雑なUIをネストさせると可読性も下がってメンテナンスがしにくくなることが考えられます。

たとえば、TwitterのこのUIをFlutterでつくるとします。

皆さんだったらどのようにWidgetを切り分けて作っていくでしょうか?

たとえば、こんな分け方が考えられます。

そのときに、この枠で囲ったWidgetたちはそれぞれmethodとして分けるかStatelessWidgetとしてclassで分けるか、どちらで分ければいいのか。というのが今回の話です。

TL;DR

こちらの和訳のような記事です。基本的にはclassで分けるのがよさそうです。

本題

さきほどのUIを作るとき、意味のあるまとまりでWidgetを返すprivate methodとしてWidgetを切り分けるでしょうか?それともprivateなStatelessWidgetのclassとして切り分けるでしょうか?どちらでも同じUIを作ることができます。
それぞれざっくりWidgetを組んだ例を載せようと思います。

methodで分ける例

class TweetItem extends StatelessWidget {
  const MyHomePage();

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Image.network(''),  // アイコン
        _tweetContent(), // Tweet情報
      ],
    );
  }
}

Widget _tweetContent() {
  return Column(
    children: [
      _header(), // 名前、id、日時
      _body(), // 内容
      _bottom(), // リプライアイコン、リツイートアイコン、...
    ],
  );
}

Widget _header() {
  return Container();
}

Widget _body() {
  return Container();
}

Widget _bottom() {
  return Container();
}

classで分ける例

class TweetItem extends StatelessWidget {
  const MyHomePage();

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Image.network(''), // アイコン
        _TweetContent(), // Tweet情報
      ],
    );
  }
}

class _TweetContent extends StatelessWidget {
  const _TweetContent({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        _Header(), // 名前、id、日時
        _Body(), // 内容
        _Bottom(), // リプライアイコン、リツイートアイコン、...
      ],
    );
  }
}

class _Header extends StatelessWidget {
  const _Header({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

class _Body extends StatelessWidget {
  const _Body({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

class _Bottom extends StatelessWidget {
  const _Bottom({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container();
  }

これらは見た目は同じになりますが、生成されるWidget Treeは異なります。これが重要な違いです。

methodで分けた場合

Row
  Image
  Column
    Container
    Container
    Container

classで分けた場合

Row
  Image
  _TweetContent
    Column
      _Header
        Container
      _Body
        Container
      _Bottom
        Container

こちらのstackoverflowでは、methodでWidgetを分けることでバグが発生したりパフォーマンスの最適化ができなかったりするということが書かれています。
methodを使えば必ずバグが発生するということは無いようですが、classを使えばこれらの問題の影響を受けない保証がされるみたいです。

いくつかのmethodとclassで分けるときに起こる挙動の違いの例が紹介されています。

まとめ

stackoverflowの回答ではそれぞれの場合で以下のメリットがまとめられています。

  • classで分けたとき
    • パフォーマンスの最適化が可能(constコンストラクタ、より細かい範囲でのrebuild)
    • 異なるWidgetを切り替えるときmethodは以前の状態を再利用することがあるが、classにすることでリソースが正しく処理されるようになる
    • ホットリロードが最適に動く
    • devtoolでWidget TreeにClass Widgetが表示されるので画面に描画されているレイアウトがわかりやすくなる
    • debugFillPropertiesをオーバーライドして、Widgetに渡されたパラメータを表示することができる
    • ProviderNotFoundのようなエラーが発生した場合に、classで分けた場合は現在build中のWidget名を表示するが、methodでWidget Treeをbuildした場合、エラーに役立つ名前が表示されない
    • keyを指定できる
    • context APIが使える
  • methodで分けたとき
    • ボイラープレートを減らしてコード量が減る

ミスリーディングしてはいけないのは、methodでわけることで問題が起こるというわけではなく、classを使えば起こり得る問題を解決できるということみたいです。

そもそも、Widgetを返すmethodで全て事足りるのであればStateless Widgetは存在しないですよね。

上記で書いたように、特にmethodでもclassでもどっちでもいい場合は最優先でclassでWidgetを分けるのがよさそうです。

methodで分けるときがどういうときがあるかというと、複数WidgetをColumnRowで分けるのではなく、List<Widget>で返したいときなどかなと思います。
あとは、Widgetを分けてバケツリレーさせるプロパティが多くなってきたとき少量ながらもclassで分けたときは新しくclass内でコンストラクタの引数に渡ってきた値を受け取るプロパティを宣言する必要があり、メモリ使用量が増えそうなのでprivate methodで定義するとかもあるっちゃありそうです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
45
Help us understand the problem. What are the problem?