再利用可能なウィジェットを作成するための関数とクラスの違いは何ですか?


125

StatelessWidgetをサブクラス化する代わりに、プレーンな関数を使用してウィジェットを作成することが可能であることに気付きました。例はこれです:

Widget function({ String title, VoidCallback callback }) {
  return GestureDetector(
    onTap: callback,
    child: // some widget
  );
}

これは、本格的なクラスよりもはるかに少ないコードで済むため、興味深いものです。例:

class SomeWidget extends StatelessWidget {
  final VoidCallback callback;
  final String title;

  const SomeWidget({Key key, this.callback, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
      return GestureDetector(
        onTap: callback,
        child: // some widget
      );
  }
}

だから私は疑問に思っていました:ウィジェットを作成するための関数とクラスの構文以外に違いはありますか?そして、関数を使用することは良い習慣ですか?


このスレッドは、問題の理解に非常に役立ちました。reddit.com/r/FlutterDev/comments/avhvco/...
RocketR

回答:


171

TL; DR:再利用可能なウィジェットツリーを作成するには、関数よりもクラスを使用することをお勧めします。


編集:いくつかの誤解を補うために:これは問題を引き起こす関数についてではなく、いくつかを解決するクラスについてです。

関数が同じことを実行できる場合、FlutterにはStatelessWidgetがありません。

同様に、それは主に再利用するために作られたパブリックウィジェットを対象としています。プライベート関数を1度だけ使用することはそれほど重要ではありません。ただし、この動作を認識していることは依然として適切です。


クラスの代わりに関数を使用することには重要な違いがあります。つまり、フレームワークは関数を認識しませんが、クラスを見ることができます。

次の「ウィジェット」関数について考えてみます。

Widget functionWidget({ Widget child}) {
  return Container(child: child);
}

このように使用しました:

functionWidget(
  child: functionWidget(),
);

そしてそれは同等のクラスです:

class ClassWidget extends StatelessWidget {
  final Widget child;

  const ClassWidget({Key key, this.child}) : super(key: key);

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

そのように使用:

new ClassWidget(
  child: new ClassWidget(),
);

紙の上では、どちらもまったく同じことを行うように見えますContainer。一方をもう一方にネストして、Create 2を作成します。しかし、現実は少し異なります。

関数の場合、生成されたウィジェットツリーは次のようになります。

Container
  Container

クラスの場合、ウィジェットツリーは次のとおりです。

ClassWidget
  Container
    ClassWidget
      Container

これは、ウィジェットを更新するときのフレームワークの動作を変更するため、重要です。

なぜそれが重要なのか

関数を使用してウィジェットツリーを複数のウィジェットに分割することで、バグにさらされて、パフォーマンスの最適化を見逃してしまいます。

そこにいることを保証するものではありませんでしょうな機能を使って、バグを持っているが、しかし、クラスを使用することによって、あなたがされている保証、これらの問題に直面しないように。

Dartpadでのインタラクティブな例をいくつか示します。これを実行すると、問題をよりよく理解できます。

結論

以下は、関数とクラスの使用の違いの厳選されたリストです。

  1. クラス:
  • パフォーマンスの最適化を許可(constコンストラクター、より詳細な再構築)
  • 2つの異なるレイアウト間の切り替えがリソースを正しく破棄することを確認します(関数は以前の状態を再利用する場合があります)
  • ホットリロードが適切に機能することを保証します(関数を使用すると、ホットリロードがshowDialogs同様に壊れる可能性があります)
  • ウィジェットインスペクターに統合されています。
    • ClassWidget画面に表示されているものを理解するのに役立つ、devtoolによって表示されたウィジェットツリーを確認します。
    • debugFillPropertiesをオーバーライドして、ウィジェットに渡されるパラメーターが何であるかを出力できます。
  • より優れたエラーメッセージ
    例外が発生した場合(ProviderNotFoundなど)、フレームワークは現在構築中のウィジェットの名前を提供します。ウィジェットツリーを関数+のみで分割した場合Builder、エラーにはわかりやすい名前が付けられません
  • キーを定義できます
  • コンテキストAPIを使用できます
  1. 関数:
  • コードが少ない(コード生成function_widgetを使用して解決できます)

概して、これらの理由により、ウィジェットを再利用するためにクラスに対して関数を使用することは悪い習慣と考えられています。
あなたはできますが、それは将来的にあなたをかむことがあります。


コメントは、詳細な議論のためのものではありません。この会話はチャットに移動さました
サミュエルLiew

10

私はこの問題について過去2日間調査してきました。私は次の結論に達しました。アプリの断片を機能に分解することは問題ありません。これらの関数がを返すことが理想的です。そのStatelessWidgetため、を作成するなどの最適化を行うことができ、StatelessWidget const必要がない場合は再構築されません。たとえば、次のコードは完全に有効です。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @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,
            ),
            const MyWidgetClass(key: const Key('const')),
            MyWidgetClass(key: Key('non-const')),
            _buildSomeWidgets(_counter),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Widget _buildSomeWidgets(int val) {
    print('${DateTime.now()} Rebuild _buildSomeWidgets');
    return const MyWidgetClass(key: Key('function'));

    // This is bad, because it would rebuild this every time
    // return Container(
    //   child: Text("hi"),
    // );
  }
}

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

  @override
  Widget build(BuildContext context) {
    print('${DateTime.now()} Rebuild MyWidgetClass $key');

    return Container(
      child: Text("hi"),
    );
  }
}

関数がを返すため、関数の使用は完全に問題ありませんconst StatelessWidget。私が間違っていたら訂正してください。


誰かが私が言ったことが間違っている理由を説明できますか?つまり、反対票が与えられていることは間違っていると思います。
Sergiu Iacob、

私は実際にあなたに同意します。私は違いのより詳細な内訳を書くつもりでしたが、それに慣れていません。ウィジェットvメソッドの長所と短所を理解することが重要だと思うので、自由に議論を具体化してください。
TheIT

@SergiuIacob constすべてのケースでステートレスクラスの前で使用できますか?それとも特定のケースでなければなりませんか?はいの場合、それらは何ですか?
アイトゥンチ

1
@aytunch constどこでも使えるとは思いません。たとえば、変数の値を含むStatelessWidgetを返すクラスがTextあり、その変数がどこかで変更StatelessWidgetされた場合、再構築する必要があるため、その異なる値を表示できるため、にすることはできませんconst。安全な方法は次のとおりだと思います。安全な場所であれば、できる限りを使用constしてください。
Sergiu Iacob、

3
私はこの質問に自分で答えるかどうかについて議論してきました。受け入れられた回答は明らかに間違っていますが、レミはフラッターコミュニティを支援するために多くのことを行ってきたので、おそらく他の人の回答ほど精査することはないでしょう。それはすべての賛成投票から明らかかもしれません。人々は単に「真実の単一の情報源」を求めています。:-)
DarkNeuron

4

関数の機能とクラスの機能には大きな違いがありました。


最初から説明します。🙂(必須事項についてのみ)

  • プログラミングの歴史、私たちは皆、まっすぐな基本的なコマンド(eg-:Assembly)から始めたことを知っています。

  • 次の構造化プログラミングにはフロー制御が付属しています(例:if、switch、while、forなど)。このパラダイムにより、プログラマーはプログラムのフローを効果的に制御し、ループによってコード行の数を最小限に抑えることができます。

  • 次の手続き型プログラミングが登場し、命令を手続き(関数)にグループ化しました。これは、プログラマーに2つの大きな利点をもたらしました。

    1.ステートメント(操作)を個別のブロックにグループ化します。

    2.これらのブロックを再利用できます。(機能)

しかし、上記のすべてのパラダイムは、アプリケーションを管理するためのソリューションを提供しませんでした。また、手続き型プログラミングは小規模なアプリケーションでのみ使用できます。これは、大規模なWebアプリケーション(例:バンキング、google、youtube、facebook、stackoverflowなど)の開発には使用できません。androidsdk、flutter sdkなどのフレームワークを作成できません。

そのため、エンジニアはプログラムを適切に管理するために、さらに多くの調査を行います。

  • 最後に、オブジェクト指向プログラミングには、アプリケーションをあらゆる規模で管理するためのすべてのソリューションが含まれています。

  • oopでは、すべてのアプリケーションはオブジェクトを中心に構築されています。つまり、アプリケーションはこれらのオブジェクトのコレクションです。

したがって、オブジェクトはあらゆるアプリケーションの基本的な建物です。

クラス(実行時のオブジェクト)は、これらの変数(データ)に関連するデータと関数をグループ化します。したがって、データのオブジェクト作成とその関連操作。

[ここでは、oopについては説明しません]


👉👉👉OKフラッターフレームワークに参加しましょう。👈👈👈

-Dartは、手続き型とoopの両方をサポートしますが、Flutterフレームワークは、classes(oop)を使用して完全に構築します。(大規模な管理可能なフレームワークは手続き型を使用して作成できないため)

ここでは、ウィジェットを作成するために関数ではなくクラスを使用する理由のリストを作成します。👇👇👇


1-ほとんどの場合、ビルドメソッド(子ウィジェット)は同期関数と非同期関数の数を呼び出します。

例:

  • ネットワークイメージをダウンロードするには
  • ユーザーからの入力など

そのため、ビルドメソッドは別のクラスウィジェットに保持する必要があります(build()メソッドによって呼び出される他のすべてのメソッドは1つのクラスに保持できるため)


2-ウィジェットクラスを使用すると、同じコードを何度も書くことなく、別のクラスの数を作成できます(**継承の使用**(拡張))。

また、inheritance(extend)とpolymorphism(override)を使用して、独自のカスタムクラスを作成できます。(下の例では、MaterialPageRouteを拡張してアニメーションをカスタマイズ(オーバーライド)します(デフォルトの遷移をカスタマイズしたいため)。👇

class MyCustomRoute<T> extends MaterialPageRoute<T> {
  MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
      : super(builder: builder, settings: settings);

  @override                                      //Customize transition
  Widget buildTransitions(BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
      Widget child) {
    if (settings.isInitialRoute)
      return child;
    // Fades between routes. (If you don't want any animation, 
    // just return child.)
    return new FadeTransition(opacity: animation, child: child);
  }
}

3-関数はパラメーターの条件を追加できませんが、クラスウィジェットのコンストラクターを使用してこれを行うことができます。

下のコード例👇(この機能はフレームワークウィジェットで頻繁に使用されます)

const Scaffold({
    Key key,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.extendBodyBehindAppBar = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
  }) : assert(primary != null),
       assert(extendBody != null),
       assert(extendBodyBehindAppBar != null),
       assert(drawerDragStartBehavior != null),
       super(key: key);

4-関数はconstを使用できず、クラスウィジェットはコンストラクターにconstを使用できます。(メインスレッドのパフォーマンスに影響する)


5-同じクラス(クラス/オブジェクトのインスタンス)を使用して任意の数の独立したウィジェットを作成できますが、関数は独立したウィジェット(インスタンス)を作成できませんが、再利用はできます。

[各インスタンスには独自のインスタンス変数があり、他のウィジェット(オブジェクト)から完全に独立していますが、関数のローカル変数は各関数呼び出しに依存しています*(つまり、ローカル変数の値を変更すると、他のすべての部分に影響します)この機能を使用するアプリケーション)]


関数よりもクラスに多くの利点がありました。(上記はいくつかのユースケースのみです)


🤯私の最後の考え

そのため、アプリケーションのビルディングブロックとして関数を使用しないでください。操作を行うためにのみ使用してください。そうしないと、アプリケーションがスケーラブルになると、多くの扱いにくい問題が発生します。

  • タスクのごく一部を実行するための関数を使用する
  • アプリケーションのビルディングブロックとしてクラスを使用する(アプリケーションの管理)

📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍 📍📍📍📍📍📍📍

ステートメントの数(または行)によってプログラムの品質を測定することはできません

📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍📍 📍📍📍📍📍📍📍

読んでくれてありがとう


Stackoverflowへようこそ!私はあなたがあなたの答えで何を表現しようとしているのか本当にわかりません。ウィジェットを作成するために、関数をうまく使用できます。ウィジェットツリーでインラインshrinkHelper() { return const SizedBox.shrink(); }を使用するのと同じconst SizedBox.shrink()です。ヘルパー関数を使用することで、1つの場所でのネストの量を制限できます。
DarkNeuron

@DarkNeuron共有してくれてありがとう。ヘルパー関数を使ってみます。
TDM

2

Flutterウィジェットを呼び出すときは、必ずconstキーワードを使用してください。例えばconst MyListWidget();


9
これがOPの質問にどのように回答するか知っていますか?
CopsOnRoad

2
間違ったセクションに返信したようです。私は、リファクタリングされたステートレスウィジェットのビルドメソッドがまだ呼び出されているというダニエルの質問に答えようとしていました。constリファクタリングされたステートレスウィジェットを呼び出すときにキーワードを追加することにより、一度だけ呼び出す必要があります。
user4761410

1
OK。とった。この質問はOPの質問とは関係がないため、人々はこの回答に反対票を投じることがあります。したがって、それを削除する必要があります。とにかく選択はあなた次第です。
CopsOnRoad 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.