İstenmeyen widget oluşturma ile nasıl başa çıkılır?


143

Çeşitli nedenlerle, bazen buildwidget'larımın yöntemi tekrar çağrılır.

Bunun bir ebeveynin güncellendiği için olduğunu biliyorum. Ancak bu istenmeyen etkilere neden olur. Sorunlara neden olduğu tipik bir durum, FutureBuilderbu şekilde kullanılmasıdır:

@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: httpCall(),
    builder: (context, snapshot) {
      // create some layout here
    },
  );
}

Bu örnekte, yapı yöntemi yeniden çağrılacaksa, başka bir http isteğini tetikleyecektir. Hangi istenmeyen.

Bunu göz önünde bulundurarak, istenmeyen yapılarla nasıl başa çıkılır? Yapım çağrısını engellemenin herhangi bir yolu var mı?



4
Gelen sağlayıcı belgelerine burada "arzu edilmeyen bir husus değerleri oluşturmak için .value kurucu kullanılarak neden daha detaylı olarak açıklar bu stackoverflow yanıta bakın." Diyerek bağlantısını Ancak, burada veya cevabınızda değer oluşturucudan bahsetmiyorsunuz. Başka bir yere bağlanmak mı istediniz?
Suragch

Yanıtlar:


226

İnşa yöntemi olması gerektiği şekilde tasarlanmıştır yan etkiler olmaksızın / saf . Bunun nedeni, birçok dış faktörün yeni bir pencere öğesi derlemesini tetikleyebilmesidir, örneğin:

  • Yönlendirme / itme
  • Genellikle klavye görünümü veya yön değişikliği nedeniyle ekranı yeniden boyutlandırma
  • Üst widget altını yeniden oluşturdu
  • Widget'ın ( Class.of(context)desen) değişikliğine bağlı olduğu bir InheritedWidget

Bu demektir ki buildbir yöntem olmalıdır olmayan bir http çağrısı tetikleyecek veya durumunu değiştirmek .


Bunun soruyla nasıl bir ilişkisi var?

Karşılaştığınız sorun, oluşturma yönteminizin yan etkilere sahip olması / saf olmaması ve gereksiz derleme çağrısını zahmetli hale getirmesidir.

Derleme çağrısını önlemek yerine, derleme yönteminizi saf hale getirmelisiniz, böylece herhangi bir anda etki olmadan çağrılabilir.

Senin örnek durumda, bir widget'ınızı dönüştürmek istiyorum StatefulWidgetsonra bu HTTP çağrısı ayıklamak initState, aramalarınızdan State:

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  Future<int> future;

  @override
  void initState() {
    future = Future.value(42);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: future,
      builder: (context, snapshot) {
        // create some layout here
      },
    );
  }
}

Bunu zaten biliyorum. Buraya geldim çünkü yeniden inşaları gerçekten optimize etmek istiyorum

Çocuklarını da oluşturmaya zorlamadan yeniden inşa edebilen bir widget yapmak da mümkündür.

Bir widget örneği aynı kaldığında; Flutter kasıtlı olarak çocukları yeniden inşa etmez. Gereksiz yeniden oluşturmaları önlemek için pencere öğesi ağacınızın bölümlerini önbelleğe alabileceğiniz anlamına gelir.

En kolay yol, dart constoluşturucularını kullanmaktır :

@override
Widget build(BuildContext context) {
  return const DecoratedBox(
    decoration: BoxDecoration(),
    child: Text("Hello World"),
  );
}

Bu constanahtar kelime sayesinde , DecoratedBoxderleme yüzlerce kez çağrılsa bile örneği aynı kalacaktır.

Ancak aynı sonucu manuel olarak da elde edebilirsiniz:

@override
Widget build(BuildContext context) {
  final subtree = MyWidget(
    child: Text("Hello World")
  );

  return StreamBuilder<String>(
    stream: stream,
    initialData: "Foo",
    builder: (context, snapshot) {
      return Column(
        children: <Widget>[
          Text(snapshot.data),
          subtree,
        ],
      );
    },
  );
}

Bu örnekte, StreamBuilder yeni değerler konusunda bilgilendirildiğinde subtree, StreamBuilder / Column yapsa bile yeniden oluşturulmayacaktır. Bunun nedeni, kapanış sayesinde, örneğinin MyWidgetdeğişmemesidir.

Bu kalıp, animasyonlarda çok kullanılır. Tipik kullanımları AnimatedBuilderve gibi tüm geçişler AlignTransition.

subtreeÇalışırken yeniden yükleme özelliğini bozduğu için daha az tavsiye edilmesine rağmen, sınıfınızın bir alanına da depolayabilirsiniz .


2
subtreeSınıf alanında depolamanın neden çalışırken yeniden yüklemeyi durdurduğunu açıklayabilir misiniz ?
mFeinstein

4
Yaşadığım bir sorun StreamBuilder, klavye göründüğünde ekranın değişmesi, dolayısıyla rotaların yeniden oluşturulması gerektiğidir. Böylece StreamBuilderyeniden StreamBuilderoluşturuldu ve yeni bir yaratıldı ve stream. Bir StreamBuildera'ya abone olduğunda stream, snapshot.connectionStatekodumun ConnectionState.waitinga döndürmesine neden olur CircularProgressIndicatorve sonra snapshot.connectionStateveri olduğunda değişir ve kodum farklı bir pencere öğesi döndürür, bu da ekranın farklı şeylerle titremesine neden olur.
mFeinstein

1
Bir yapmaya karar verdik StatefulWidget, abone streamüzerinde initState()ve set currentWidgetile setState()olduğu gibi streamgeçen yeni veri gönderir currentWidgetiçin build()yöntemle. Daha iyi bir çözüm var mı?
mFeinstein

1
Biraz kafam karıştı. Kendi sorunuzu yanıtlıyorsunuz, ancak içerikten öyle görünmüyor.
sgon00

8
Bir yapının bir HTTP yöntemini çağırmaması gerektiğini söylemek, çok pratik bir FutureBuilder.
TheGeekZn

6

Bu şekilde istenmeyen derleme çağrılarını önleyebilirsiniz.

1) Kullanıcı arayüzünün küçük bir bölümü için alt durum sınıfı oluşturun

2) Kullanım Sağlayıcı kütüphane, böylece istenmeyen inşa yöntemi çağrı durdurmak bunu yapabilirsiniz kullanarak

durum inşa yöntemi çağrısı altında Bunlarda

  • İnitState'i çağırdıktan sonra
  • DidUpdateWidget çağrıldıktan sonra
  • zaman setState () olarak adlandırılır.
  • klavye açıkken
  • ekran yönü değiştiğinde
  • Ana pencere öğesi oluşturulur ve ardından alt pencere öğesi de yeniden oluşturulur

0

Flutter'da ayrıca ValueListenableBuilder<T> class . Amacınız için gerekli olan yalnızca bazı widget'ları yeniden oluşturmanıza ve pahalı widget'ları atlamanıza olanak tanır.

Belgeleri burada ValueListenableBuilder flutter belgelerini
veya yalnızca aşağıdaki örnek kodu görebilirsiniz:

  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:'),
        ValueListenableBuilder(
          builder: (BuildContext context, int value, Widget child) {
            // This builder will only get called when the _counter
            // is updated.
            return Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Text('$value'),
                child,
              ],
            );
          },
          valueListenable: _counter,
          // The child parameter is most helpful if the child is
          // expensive to build and does not depend on the value from
          // the notifier.
          child: goodJob,
        )
      ],
    ),
  ),
  floatingActionButton: FloatingActionButton(
    child: Icon(Icons.plus_one),
    onPressed: () => _counter.value += 1,
  ),
);
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.