Yeniden kullanılabilir pencere öğeleri oluşturmak için işlevler ve sınıflar arasındaki fark nedir?


125

StatelessWidget'ı alt sınıflara ayırmak yerine düz işlevler kullanarak widget oluşturmanın mümkün olduğunu fark ettim . Bir örnek şu olabilir:

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

Bu ilginç çünkü tam gelişmiş bir sınıfa göre çok daha az kod gerektiriyor . Misal:

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

Bu yüzden merak ediyordum: Widget oluşturmak için işlevler ve sınıflar arasında sözdiziminin dışında herhangi bir fark var mı? Ve fonksiyonları kullanmak iyi bir uygulama mı?


Bu konuyu anlamam açısından çok faydalı buldum. reddit.com/r/FlutterDev/comments/avhvco/…
RocketR

Yanıtlar:


173

TL; DR: Yeniden kullanılabilir parçacık ağacı yapmak için işlevler yerine sınıfları kullanmayı tercih edin .


DÜZENLEME : Bazı yanlış anlamaları telafi etmek için: Sorunlara neden olan işlevlerle değil, bazılarını çözen sınıflarla ilgili.

Bir işlev aynı şeyi yapabilseydi Flutter'da StatelessWidget olmazdı .

Benzer şekilde, yeniden kullanılmak üzere yapılan genel aletlere yöneliktir. Özel işlevlerin yalnızca bir kez kullanılması o kadar da önemli değildir - bu davranışın farkında olmak hala iyidir.


Sınıflar yerine işlevleri kullanmak arasında önemli bir fark vardır, yani: Çerçeve işlevlerin farkında değildir, ancak sınıfları görebilir.

Aşağıdaki "widget" işlevini düşünün:

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

bu şekilde kullanılır:

functionWidget(
  child: functionWidget(),
);

Ve sınıf eşdeğeri:

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

böyle kullanılır:

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

Kağıt üzerinde, her ikisi de tam olarak aynı şeyi yapıyor gibi görünüyor: ContainerBiri diğerinin iç içe geçtiği 2'yi oluşturun . Ancak gerçek biraz farklı.

İşlevler söz konusu olduğunda, oluşturulan pencere öğesi ağacı şöyle görünür:

Container
  Container

Sınıflarla birlikte, pencere öğesi ağacı:

ClassWidget
  Container
    ClassWidget
      Container

Bu önemlidir çünkü bir pencere öğesini güncellerken çerçevenin nasıl davrandığını değiştirir.

Bu neden önemli

Widget ağacınızı birden çok widget'a ayırmak için işlevleri kullanarak, kendinizi hatalara maruz bırakır ve bazı performans optimizasyonlarını kaçırırsınız.

Fonksiyonları kullanarak hatalara sahip olacağınızın garantisi yoktur , ancak sınıfları kullanarak bu sorunlarla yüzleşmeyeceğiniz garanti edilir.

Dartpad'de sorunları daha iyi anlamak için kendi kendinize çalıştırabileceğiniz birkaç etkileşimli örnek:

Sonuç

İşlevler ve sınıflar arasındaki farkların seçilmiş bir listesi:

  1. Sınıflar:
  • performans optimizasyonuna izin verin (const yapıcı, daha ayrıntılı yeniden oluşturma)
  • iki farklı düzen arasında geçişin kaynakları doğru bir şekilde imha ettiğinden emin olun (işlevler önceki durumların bazılarını yeniden kullanabilir)
  • Çalışırken yeniden yüklemenin düzgün çalışmasını sağlar (işlevlerin kullanılması, showDialogsve benzeri için çalışırken yeniden yüklemeyi kesebilir )
  • widget denetçisine entegre edilmiştir.
    • ClassWidgetDevtool tarafından gösterilen widget ağacında, ekranda ne olduğunu anlamaya yardımcı olduğunu görüyoruz .
    • Bir parçacığa geçirilen parametrelerin ne olduğunu yazdırmak için debugFillProperties'i geçersiz kılabiliriz
  • daha iyi hata mesajları
    Bir istisna meydana gelirse (ProviderNotFound gibi), çerçeve size şu anda inşa edilen parçacığın adını verecektir. Widget ağacınızı yalnızca işlevler + olarak böldüyseniz Builder, hatalarınızın yararlı bir adı olmayacaktır.
  • anahtarları tanımlayabilir
  • bağlam API'sini kullanabilir
  1. Fonksiyonlar:
  • daha az koda sahip (kod oluşturma function_widget kullanılarak çözülebilir )

Genel olarak, bu nedenlerden dolayı pencere öğelerini yeniden kullanmak için sınıflar üzerinde işlevlerin kullanılması kötü bir uygulama olarak kabul edilir.
Sen can , ancak gelecekte size ısırabilir.


Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
Samuel Liew

10

Son 2 gündür bu konuda araştırma yapıyorum. Şu sonuca vardım: Uygulamanın parçalarını işlevlere ayırmakta TAMAM. Bu işlevlerin a döndürmesi idealdir StatelessWidget, böylece yapmak gibi optimizasyonlar yapılabilir StatelessWidget const, böylece gerekli değilse yeniden oluşturulmaz. Örneğin, bu kod parçası tamamen geçerlidir:

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

Orada işlevin kullanımı mükemmel bir şekilde iyidir, çünkü a döndürür const StatelessWidget. Yanılıyorsam lütfen beni düzeltin.


Biri söylediklerimin neden yanlış olduğunu açıklayabilir mi? Demek istediğim, olumsuz oylar verildiğinde yanlış olduğunu düşünüyorum.
Sergiu Iacob

Aslında size katılıyorum. Farklılıkların çok daha ayrıntılı bir dökümünü yazmak niyetindeydim, ama buna alışamadım. Tartışmalarınızı açıklığa kavuşturmaktan çekinmeyin, çünkü gereçlere karşı yöntemlerin artılarını ve eksilerini anlamanın önemli olduğunu düşünüyorum.
TheIT

@SergiuIacob constHer durum için durumsuz sınıfın önünde kullanabilir miyiz ? Yoksa belirli durumlar mı olmalı? Varsa, bunlar nedir?
aytunch

1
@aytunch Her constyerde kullanabileceğini sanmıyorum . Örneğin, bir değişkenin değerini içeren bir StatelessWidgetsınıf döndüren bir sınıfınız varsa Textve bu değişken, sizin StatelessWidgetyeniden oluşturulması gerekenden bir yerde değişirse, bu farklı değeri gösterebilir, bu nedenle olamaz const. Sanırım bunu ifade etmenin güvenli yolu şudur: Yapabildiğiniz her yerde const, güvenli ise kullanın .
Sergiu Iacob

3
Bu soruya kendim cevap verip vermemeyi tartışıyordum. Kabul edilen cevap kesinlikle yanlış, ancak Rémi flutter topluluğuna yardım etmek için çok şey yaptı, bu yüzden insanlar muhtemelen cevaplarını başkalarınınki kadar incelemiyorlar. Bu, tüm olumlu oylardan açıkça anlaşılabilir. İnsanlar sadece "tek gerçek kaynağını" istiyorlar. :-)
DarkNeuron

4

Hangi fonksiyonların yaptığı ve hangi sınıfın yaptığı arasında büyük bir fark vardı.


Bunu en baştan açıklayayım. Only (sadece emir ile ilgili)

  • Programlama geçmişi, hepimizin bildiği gibi düz temel komutlarla başladık (örneğin: Assembly).

  • Sonraki Yapılandırılmış programlama Akış kontrolleri ile geldi (örneğin: if, switch, while, for vb.) Bu paradigma, programcılara program akışını etkili bir şekilde kontrol etme ve ayrıca döngülerle kod satırı sayısını en aza indirme olanağı sağlar.

  • Sonraki Prosedürel programlama geldi ve hangi komutları prosedürler (işlevler) halinde grupladı. Bu, programcılar için iki büyük fayda sağladı.

    1. Grup deyimleri (işlemler) ayrı bloklar halinde.

    2. Bu blokları yeniden kullanabilir. (Fonksiyonlar)

Ama bütün paradigmaların şeyden vermedi değil uygulamaları yönetme için bir çözüm sağlar. Prosedürel programlama da yalnızca küçük ölçekli uygulamalar için kullanılabilir. Bu, büyük web uygulamaları geliştirmek için kullanılamaz (örn: bankacılık, google, youtube, facebook, stackoverflow vb.), Android sdk, flutter sdk ve çok daha fazlası gibi çerçeveler oluşturamaz ......

Bu nedenle mühendisler, programları doğru şekilde yönetmek için çok daha fazla araştırma yapar.

  • Son olarak, Nesneye Yönelik Programlama , her ölçekte uygulamaları yönetmek için tüm çözümlerle birlikte gelir (merhaba dünyasından Trilyonlarca insana, örneğin google, amazon ve bugün uygulamaların% 90'ı gibi sistem oluşturmayı kullanan).

  • Oop'ta tüm uygulamalar Nesneler etrafında oluşturulur. Bu, uygulamanın bu nesnelerin bir koleksiyonu olduğu anlamına gelir.

yani nesneler herhangi bir uygulama için temel yapıdır.

sınıf (çalışma zamanında nesne) grup verileri ve bu değişkenlerle (veriler) ilgili işlevler. böylece nesne verilerden ve bunlarla ilgili işlemlerden oluşur.

[Burada oop hakkında açıklama yapmayacağım]


Tamam Şimdi flutter çerçevesi için gelelim.

-Dart hem yordamsal hem de oop'u destekler. Ancak, Flutter çerçevesi, sınıfları (oop) kullanarak tamamen oluşturulur. (Çünkü büyük yönetilebilir çerçeve prosedür kullanarak oluşturamaz)

Burada, widget yapmak için işlevler yerine sınıfları kullanmalarının nedenlerinin bir listesini oluşturacağım.


1 - Çoğu zaman build yöntemi (alt pencere öğesi) senkron ve asenkron işlevlerin numarasını çağırır .

Ör:

  • Ağ görüntüsünü indirmek için
  • kullanıcıdan girdi al vb.

bu nedenle derleme yönteminin ayrı bir sınıf parçacığında tutulması gerekir (çünkü diğer tüm yöntemler build () yöntemiyle çağrılar tek bir sınıfta tutabilir)


2 - Widget sınıfını kullanarak, aynı kodu tekrar tekrar yazmadan başka bir sınıfın sayısını oluşturabilirsiniz (** Kalıtımın Kullanımı ** (genişler)).

Ayrıca kalıtım (genişletme) ve çok biçimlilik (geçersiz kılma) kullanarak kendi özel sınıfınızı oluşturabilirsiniz. (Aşağıda, aşağıda, orada, MaterialPageRoute'u genişleterek animasyonu özelleştireceğim (Geçersiz kılacağım) (çünkü varsayılan geçişini özelleştirmek istiyorum).

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 - Fonksiyonlar, parametreleri için koşullar ekleyemez, Ancak sınıf parçacığının yapıcısını kullanarak bunu yapabilirsiniz.

Aşağıda Kod örneği👇 (bu özellik ağırlıklı olarak çerçeve widget'ları tarafından kullanılmaktadır)

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 - Fonksiyonlar const kullanamaz ve Sınıf widget'ı oluşturucuları için const'ı kullanabilir. (ana iş parçacığının performansını etkileyen)


5 - Aynı sınıfı (bir sınıfın / nesnelerin örneklerini) kullanarak istediğiniz sayıda bağımsız pencere öğesi oluşturabilirsiniz, ancak işlev bağımsız parçacıklar (örnek) oluşturamaz, ancak yeniden kullanarak yapabilir.

[her örneğin kendi örnek değişkeni vardır ve bu diğer widget'lardan (nesne) tamamen bağımsızdır, ancak işlevin yerel değişkeni her işlev çağrısına bağlıdır * (bu, yerel bir değişkenin bir değerini değiştirdiğinizde, diğer tüm parçaları bu işlevi kullanan uygulama)]


İşlevlere göre sınıfta birçok Avantaj vardı. (Yukarıda yalnızca birkaç kullanım durumu vardır)


🤯 Son Düşüncem

Bu nedenle, İşlevleri uygulamanızın yapı taşı olarak kullanmayın, yalnızca İşlemler yapmak için kullanın. Aksi takdirde, uygulamanız ölçeklenebilir hale geldiğinde birçok yönetilemeyen soruna neden olur .

  • Görevin küçük bir bölümünü yapmak için işlevleri kullanın
  • Sınıfı bir uygulamanın yapı taşı olarak kullanın (Uygulamayı yönetme)

📍📍📍📍📍📍📍📍Temel 📍📍📍📍📍📍

PROGRAMIN KALİTESİNİ KULLANIMIYLA BEYAN SAYISI (veya satır) İLE ÖLÇEMEZSİNİZ

📍📍📍📍📍📍📍📍Temel 📍📍📍📍📍📍

Okuduğunuz için teşekkürler


Stackoverflow'a hoş geldiniz! Cevabınızla neyi ifade etmeye çalıştığınızdan gerçekten emin değilim. Widget oluşturmak için gayet iyi bir işlevi kullanabilirsiniz. pencere öğesi ağacınızda satır içi shrinkHelper() { return const SizedBox.shrink(); }kullanmakla aynıdır const SizedBox.shrink()ve yardımcı işlevleri kullanarak tek bir yerde iç içe geçme miktarını sınırlayabilirsiniz.
DarkNeuron

@DarkNeuron Paylaştığınız için teşekkürler. Yardımcı işlevleri kullanmaya çalışacağım.
TDM

2

Flutter parçacığını çağırırken const anahtar sözcüğünü kullandığınızdan emin olun. Örneğinconst MyListWidget();


9
Bunun OP sorusuna nasıl cevap verdiğini öğrenebilir miyim?
CopsOnRoad

2
Görünüşe göre yanlış bölüme cevap verdim. Daniel'in yeniden düzenlenmiş durumsuz pencere öğesi oluşturma yönteminin hala çağrıldığı sorusuna yanıt vermeye çalışıyordum. constYeniden düzenlenen durum bilgisiz pencere öğesi çağrılırken anahtar sözcük ekleyerek, yalnızca bir kez çağrılmalıdır.
user4761410

1
Tamam. Anladım. OP sorusuyla hiçbir ilgisi olmadığı için insanlar bu yanıtı olumsuz etkileyebilir. Yani onu silmelisin. Her neyse, seçim sizin.
CopsOnRoad
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.