Apa perbedaan antara fungsi dan kelas untuk membuat widget yang dapat digunakan kembali?

125

Saya telah menyadari bahwa dimungkinkan untuk membuat widget menggunakan fungsi biasa daripada subclass StatelessWidget . Contohnya seperti ini:

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

Ini menarik karena membutuhkan kode yang jauh lebih sedikit daripada kelas yang lengkap. Contoh:

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
      );
  }
}

Jadi saya bertanya-tanya: Apakah ada perbedaan selain sintaks antara fungsi dan kelas untuk membuat widget? Dan apakah praktik yang baik untuk menggunakan fungsi?

RΓ©mi Rousselet
sumber
Saya menemukan utas ini sangat berguna untuk pemahaman saya tentang masalah ini. reddit.com/r/FlutterDev/comments/avhvco/…
RocketR

Jawaban:

172

TL; DR: Lebih suka menggunakan kelas daripada fungsi untuk membuat pohon widget yang dapat digunakan kembali .


EDIT : Untuk menebus beberapa kesalahpahaman: Ini bukan tentang fungsi yang menyebabkan masalah, tetapi kelas memecahkan beberapa.

Flutter tidak akan memiliki StatelessWidget jika suatu fungsi dapat melakukan hal yang sama.

Demikian pula, ini terutama ditujukan pada widget publik, dibuat untuk digunakan kembali. Tidak masalah untuk fungsi pribadi dibuat untuk digunakan hanya sekali - meskipun menyadari perilaku ini masih bagus.


Ada perbedaan penting antara menggunakan fungsi daripada kelas, yaitu: Framework tidak mengetahui fungsi, tetapi dapat melihat kelas.

Pertimbangkan fungsi "widget" berikut:

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

digunakan dengan cara ini:

functionWidget(
  child: functionWidget(),
);

Dan itu setara dengan kelas:

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,
    );
  }
}

digunakan seperti itu:

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

Di atas kertas, keduanya tampak melakukan hal yang persis sama: Buat 2 Container, dengan satu bertumpuk ke yang lain. Namun kenyataannya sedikit berbeda.

Dalam kasus fungsi, pohon widget yang dihasilkan terlihat seperti ini:

Container
  Container

Sedangkan dengan kelas, pohon widget adalah:

ClassWidget
  Container
    ClassWidget
      Container

Ini penting karena mengubah cara framework berperilaku saat memperbarui widget.

Mengapa itu penting

Dengan menggunakan fungsi untuk membagi pohon widget Anda menjadi beberapa widget, Anda akan terkena bug dan kehilangan beberapa pengoptimalan kinerja.

Tidak ada jaminan bahwa Anda akan mendapatkan bug dengan menggunakan fungsi, tetapi dengan menggunakan kelas, Anda dijamin tidak akan menghadapi masalah ini.

Berikut beberapa contoh interaktif di Dartpad yang dapat Anda jalankan sendiri untuk lebih memahami masalahnya:

Kesimpulan

Berikut adalah daftar perbedaan antara menggunakan fungsi dan kelas:

  1. Kelas:
  • memungkinkan pengoptimalan kinerja (konstruktor const, rekondisi yang lebih terperinci)
  • memastikan bahwa beralih di antara dua tata letak yang berbeda akan membuang sumber daya dengan benar (fungsi dapat menggunakan kembali beberapa keadaan sebelumnya)
  • memastikan bahwa hot-reload berfungsi dengan baik (menggunakan fungsi dapat menghentikan hot-reload untuk showDialogs& serupa)
  • diintegrasikan ke dalam widget inspector.
    • Kita lihat ClassWidgetdi pohon widget yang ditunjukkan oleh devtool, yang membantu memahami apa yang ada di layar
    • Kita bisa mengganti debugFillProperties untuk mencetak parameter apa yang diteruskan ke widget
  • pesan kesalahan yang lebih baik
    Jika terjadi pengecualian (seperti ProviderNotFound), kerangka kerja akan memberi Anda nama widget yang sedang dibangun. Jika Anda membagi pohon widget Anda hanya dalam fungsi + Builder, kesalahan Anda tidak akan memiliki nama yang berguna
  • dapat menentukan kunci
  • dapat menggunakan API konteks
  1. Fungsi:
  • memiliki lebih sedikit kode (yang dapat diselesaikan menggunakan pembuat kode functional_widget )

Secara keseluruhan, menggunakan fungsi di atas kelas untuk menggunakan kembali widget dianggap sebagai praktik yang buruk karena alasan ini.
Anda bisa , tetapi itu mungkin menggigit Anda di masa depan.

RΓ©mi Rousselet
sumber
Komentar tidak untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Samuel Liew
10

Saya telah meneliti masalah ini selama 2 hari terakhir. Saya sampai pada kesimpulan berikut: TIDAK apa-apa untuk memecah bagian-bagian aplikasi menjadi beberapa fungsi. Idealnya fungsi tersebut mengembalikan a StatelessWidget, sehingga pengoptimalan dapat dilakukan, seperti membuat StatelessWidget const, sehingga tidak membangun kembali jika tidak perlu. Misalnya, potongan kode ini benar-benar valid:

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"),
    );
  }
}

Penggunaan fungsi di sana baik-baik saja, karena mengembalikan a const StatelessWidget. Harap perbaiki saya jika saya salah.

Sergiu Iacob
sumber
Adakah yang bisa menjelaskan mengapa apa yang saya katakan salah? Maksud saya, saya kira itu salah mengingat suara negatifnya.
Sergiu Iacob
Saya sebenarnya setuju denganmu. Saya berniat untuk menulis rincian perbedaan yang jauh lebih rinci, tetapi belum sempat melakukannya. Jangan ragu untuk menyempurnakan argumen Anda karena menurut saya penting untuk memahami pro dan kontra metode widget v.
TheIT
@SergiuIacob Bisakah kita menggunakan constdi depan kelas stateless untuk setiap kasus? Atau harus kasus tertentu? Jika ya, apakah itu?
aytunch
1
@aytunch Saya rasa Anda tidak dapat menggunakan di constmana-mana. Misalnya, jika Anda memiliki StatelessWidgetkelas yang mengembalikan nilai yang Textberisi variabel, dan variabel itu berubah di suatu tempat, maka Anda StatelessWidgetharus dibangun kembali, sehingga dapat menunjukkan nilai yang berbeda, oleh karena itu tidak mungkin const. Saya pikir cara yang aman untuk menjelaskannya adalah ini: di mana pun Anda bisa, gunakan const, jika aman untuk melakukannya.
Sergiu Iacob
3
Saya telah memperdebatkan apakah akan menjawab pertanyaan ini sendiri. Jawaban yang diterima jelas salah, tapi RΓ©mi telah melakukan banyak hal untuk mencoba dan membantu komunitas yang berdebar-debar, jadi orang-orang mungkin tidak memeriksa jawabannya sebanyak orang lain. Itu mungkin terbukti dari semua suara positif. Orang hanya menginginkan "satu-satunya sumber kebenaran" mereka. :-)
DarkNeuron
4

Ada perbedaan besar antara fungsi apa dan fungsi kelas.


Mari saya jelaskan dari awal.πŸ™‚ (hanya tentang keharusan)

  • Sejarah pemrograman, kita semua tahu dimulai dengan perintah dasar langsung (misalnya: Majelis).

  • Pemrograman terstruktur berikutnya hadir dengan kontrol Flow (misalnya: if, switch, while, for dll) Paradigma ini memberikan programmer untuk mengontrol aliran program secara efektif dan juga meminimalkan jumlah baris kode dengan loop.

  • Pemrograman prosedural berikutnya datang dan yang mengelompokkan instruksi ke dalam prosedur (fungsi). Ini memberi dua manfaat utama bagi programmer.

    1. Pernyataan grup (operasi) ke dalam blok terpisah.

    2. Dapat menggunakan kembali blok ini. (Fungsi)

Tetapi di atas semua paradigma tidak memberikan solusi untuk mengelola aplikasi. Pemrograman prosedural juga hanya dapat digunakan untuk aplikasi skala kecil. Itu tidak dapat digunakan untuk mengembangkan aplikasi web besar (misalnya: banking, google, youtube, facebook, stackoverflow dll), tidak dapat membuat kerangka kerja seperti android sdk, flutter sdk dan banyak lagi ......

Jadi, para insinyur melakukan lebih banyak penelitian untuk mengelola program dengan cara yang tepat.

  • Akhirnya Pemrograman Berorientasi Objek hadir dengan semua solusi untuk mengelola aplikasi dalam skala apa pun. (Dari hello world hingga Triliun orang menggunakan pembuatan sistem misalnya-google, amazon, dan saat ini 90% aplikasi).

  • Di OOP semua aplikasi dibangun di sekitar Objek. Artinya aplikasi adalah kumpulan dari objek-objek ini.

jadi objek adalah bangunan dasar untuk aplikasi apa pun.

class (objek saat runtime) mengelompokkan data dan fungsi yang terkait dengan variabel tersebut (data). jadi objek terdiri dari data dan operasi terkaitnya.

[Di sini saya tidak akan menjelaskan tentang oop]


πŸ‘‰πŸ‘‰πŸ‘‰Ok Sekarang Mari datang untuk kerangka flutter.πŸ‘ˆπŸ‘ˆπŸ‘ˆ

-Dart mendukung prosedural dan oop Tapi, kerangka Flutter sepenuhnya dibangun dengan menggunakan kelas (oop). (Karena kerangka besar yang dapat dikelola tidak dapat dibuat menggunakan prosedural)

Di sini saya akan membuat daftar alasan mereka menggunakan kelas, bukan fungsi untuk membuat widget.πŸ‘‡πŸ‘‡πŸ‘‡


1 - Sering kali metode build (widget anak) memanggil jumlah fungsi sinkron dan asinkron.

Ex:

  • Untuk mengunduh citra jaringan
  • dapatkan masukan dari pengguna dll.

jadi metode build perlu disimpan dalam widget kelas yang terpisah (karena semua metode lain yang dipanggil oleh metode build () dapat disimpan dalam satu kelas)


2 - Menggunakan kelas widget Anda dapat membuat nomor dari kelas lain tanpa menulis kode yang sama berulang kali (** Use Of Inheritance ** (extends)).

Dan juga menggunakan warisan (memperpanjang) dan polimorfisme (menimpa) Anda dapat membuat kelas khusus sendiri. (Di bawah contoh, Di sana saya akan menyesuaikan (Menimpa) animasi dengan memperluas MaterialPageRoute (karena transisi defaultnya saya ingin menyesuaikan) .πŸ‘‡

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 - Fungsi tidak dapat menambahkan kondisi untuk parameternya, Tetapi menggunakan konstruktor widget kelas Anda dapat melakukan ini.

Di bawah contoh KodeπŸ‘‡ (fitur ini banyak digunakan oleh widget kerangka kerja)

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 - Fungsi tidak dapat menggunakan const dan widget Kelas dapat menggunakan const untuk konstruktornya. (yang mempengaruhi kinerja utas utama)


5 - Anda dapat membuat sejumlah widget independen menggunakan kelas yang sama (instance dari kelas / objek) Tetapi fungsi tidak dapat membuat widget independen (instance), tetapi dapat menggunakan kembali.

[setiap instance memiliki variabel instance sendiri-sendiri dan yang sepenuhnya independen dari widget (objek) lain, Tetapi variabel lokal fungsi bergantung pada setiap pemanggilan fungsi * (yang berarti, ketika Anda mengubah nilai variabel lokal, itu mempengaruhi semua bagian lain dari aplikasi yang menggunakan fungsi ini)]


Ada banyak Keuntungan di kelas dibandingkan fungsi .. (di atas hanya beberapa kasus penggunaan)


🀯 Pikiran Terakhir Saya

Jadi jangan gunakan Functions sebagai blok penyusun aplikasi Anda, gunakan hanya untuk melakukan Operasi. Jika tidak, ini menyebabkan banyak masalah yang tidak dapat ditangani saat aplikasi Anda menjadi skalabel .

  • Gunakan fungsi untuk melakukan sebagian kecil tugas
  • Gunakan kelas sebagai blok penyusun aplikasi (Mengelola aplikasi)

πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“ πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“

ANDA TIDAK DAPAT MENGUKUR KUALITAS PROGRAM DENGAN JUMLAH LAPORAN (atau baris) YANG DIGUNAKAN OLEHnya

πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“ πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“πŸ“

Terima kasih sudah membaca

TDM
sumber
Selamat datang di Stackoverflow! Saya tidak begitu yakin apa yang Anda coba ungkapkan dengan jawaban Anda. Anda dapat menggunakan fungsi dengan baik untuk membuat widget. shrinkHelper() { return const SizedBox.shrink(); }sama seperti menggunakan const SizedBox.shrink()sebaris di pohon widget Anda, dan dengan menggunakan fungsi pembantu Anda dapat membatasi jumlah penumpukan di satu tempat.
DarkNeuron
@DarkNeuron Terima kasih telah berbagi. Saya akan mencoba menggunakan fungsi pembantu.
TDM
2

Saat Anda memanggil widget Flutter, pastikan Anda menggunakan kata kunci const. Sebagai contohconst MyListWidget();

pengguna4761410
sumber
9
Bolehkah saya tahu bagaimana ini menjawab pertanyaan OP?
CopsOnRoad
2
Sepertinya saya menjawab saya bagian yang salah. Saya mencoba menjawab pertanyaan Daniel bahwa metode build widget stateless yang direfraktorisasi masih dipanggil. Dengan menambahkan constkata kunci saat memanggil widget stateless yang difaktorkan ulang, itu hanya boleh dipanggil sekali.
pengguna4761410
1
Baik. Mengerti. Orang mungkin tidak menyukai jawaban ini karena tidak ada hubungannya dengan pertanyaan OP. Jadi Anda harus menghapusnya. Bagaimanapun pilihan ada di tangan Anda.
CopsOnRoad