Flutter: Devralınan Widget nasıl doğru kullanılır?


111

InheritedWidget'ı kullanmanın doğru yolu nedir? Şimdiye kadar bunun size verileri Widget ağacında yayma şansı verdiğini anladım. Uç noktalarda, RootWidget olarak koyarsanız, tüm Rotalarda ağaçtaki tüm Widget'lardan erişilebilir olacaktır, bu iyi bir durumdur çünkü bir şekilde ViewModel / Modelimi, globallere veya Singleton'lara başvurmak zorunda kalmadan Widget'larım için erişilebilir hale getirmem gerekiyor.

ANCAK InheritedWidget değişmez, peki onu nasıl güncelleyebilirim? Ve daha da önemlisi, Durum Bilgili Pencere Öğelerim alt ağaçlarını yeniden oluşturmak için nasıl tetiklenir?

Maalesef dokümantasyon burada çok net değil ve birçok tartışmadan sonra kimse onu kullanmanın doğru yolunu bilmiyor gibi görünüyor.

Brian Egan'dan bir alıntı ekliyorum:

Evet, bunu verileri ağaçta yaymanın bir yolu olarak görüyorum. API belgelerinden kafa karıştırıcı bulduğum şey:

"Devralınan pencere öğeleri, bu şekilde başvurulduğunda, devralınan pencere aracının durumu değiştiğinde tüketicinin yeniden oluşturmasına neden olur."

Bunu ilk okuduğumda düşündüm:

InheritedWidget'ta bazı verileri doldurabilir ve daha sonra değiştirebilirim. Bu mutasyon gerçekleştiğinde, bulduğum InheritedWidget'ıma referans veren tüm Widget'ları yeniden oluşturacak:

Bir InheritedWidget'in Durumunu mutasyona uğratmak için, onu bir StatefulWidget içine sarmalısınız, daha sonra StatefulWidget'in durumunu gerçekten değiştirip bu verileri InheritedWidget'e aktarmanız gerekir, bu da verileri tüm alt öğelerine aktarır. Ancak, bu durumda, yalnızca InheritedWidget'e başvuran Widget'lar değil, StatefulWidget'ın altındaki tüm ağacı yeniden oluşturuyor gibi görünüyor. Bu doğru mu? Yoksa updateShouldNotify false döndürürse InheritedWidget'a başvuran Widget'ları nasıl atlayacağını bir şekilde bilecek mi?

Yanıtlar:


112

Sorun, yanlış olan alıntılarınızdan kaynaklanmaktadır.

Dediğiniz gibi, InheritedWidgets, diğer gereçler gibi, değişmezdir. Bu nedenle güncelleme yapmazlar . Yeniden yaratıldılar.

Mesele şu ki: InheritedWidget, verileri tutmaktan başka hiçbir şey yapmayan basit bir widget . Herhangi bir güncelleme mantığı ya da benzeri yok. Ancak, diğer tüm gereçler gibi, bir Element. Ve tahmin et ne oldu? Bu şey değiştirilebilir ve flutter onu mümkün olduğunca yeniden kullanacak!

Düzeltilmiş alıntı şöyle olacaktır:

InheritedWidget, bu şekilde referans verildiğinde, bir InheritedElement ile ilişkili InheritedWidget değiştiğinde tüketicinin yeniden oluşturmasına neden olacaktır .

Widget'ların / elementlerin / renderbox'ın nasıl bir araya getirildiği hakkında harika bir konuşma var . Ancak kısacası, bunlar şuna benzer (solda tipik pencere öğeniz, ortada "öğeler" ve sağda "oluşturma kutuları"):

görüntü açıklamasını buraya girin

Mesele şudur: Yeni bir widget oluşturduğunuzda; flutter bunu eskisiyle karşılaştırır. RenderBox'a işaret eden "Element" i yeniden kullanın. Ve mutasyona RenderBox özelliklerini.


Tamam ama bu sorumu nasıl cevaplıyor?

Bir InheritedWidget'i başlatırken ve sonra çağırırken context.inheritedWidgetOfExactType(veya MyClass.oftemelde aynı olan); ima edilen şey, Elementsizin ile ilişkili olanı dinleyeceğidir InheritedWidget. Ve bu Elementyeni bir widget aldığında, önceki yöntemi çağıran tüm widget'ların yenilenmesini zorlayacaktır.

Kısacası var InheritedWidgetolanı yenisiyle değiştirdiğinizde ; flutter bunun değiştiğini görecektir. Ve olası bir değişikliğin bağlı widget'larını bilgilendirir.

Her şeyi anladıysanız, çözümü zaten tahmin etmiş olmalısınız:

InheritedWidgetBir şey değiştiğinde StatefulWidgetyepyeni bir InheritedWidgetşey yaratacak bir içini sarın !

Gerçek koddaki nihai sonuç şöyle olacaktır:

class MyInherited extends StatefulWidget {
  static MyInheritedData of(BuildContext context) =>
      context.inheritFromWidgetOfExactType(MyInheritedData) as MyInheritedData;

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

  final Widget child;

  @override
  _MyInheritedState createState() => _MyInheritedState();
}

class _MyInheritedState extends State<MyInherited> {
  String myField;

  void onMyFieldChange(String newValue) {
    setState(() {
      myField = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MyInheritedData(
      myField: myField,
      onMyFieldChange: onMyFieldChange,
      child: widget.child,
    );
  }
}

class MyInheritedData extends InheritedWidget {
  final String myField;
  final ValueChanged<String> onMyFieldChange;

  MyInheritedData({
    Key key,
    this.myField,
    this.onMyFieldChange,
    Widget child,
  }) : super(key: key, child: child);

  static MyInheritedData of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedData>();
  }

  @override
  bool updateShouldNotify(MyInheritedData oldWidget) {
    return oldWidget.myField != myField ||
        oldWidget.onMyFieldChange != onMyFieldChange;
  }
}

Ancak yeni bir InheritedWidget oluşturmak tüm ağacı yeniden inşa etmez mi?

Hayır, gerekli olmayacak. Yeni InheritedWidget'ınız potansiyel olarak öncekiyle aynı çocuğa sahip olabilir. Ve tam olarak, aynı durumu kastediyorum. Daha önce sahip oldukları örneğe sahip widget'lar yeniden oluşturulmaz.

Ve çoğu durumda (uygulamanızın kökünde bir miras alınan pencere öğesi olması), devralınan pencere öğesi sabittir . Yani gereksiz yeniden inşa yok.


1
Ancak yeni bir InheritedWidget oluşturmak tüm ağacı yeniden inşa etmez mi? O halde neden Dinleyicilere ihtiyaç var?
Thomas

1
İlk yorumunuz için cevabıma üçüncü bir bölüm ekledim. Sıkıcı olmaya gelince: Katılmıyorum. Bir kod parçacığı bunu oldukça kolay bir şekilde oluşturabilir. Ve verilere erişmek, aramak kadar basit MyInherited.of(context).
Rémi Rousselet

3
İlgilenip ilgilenmediğinizden emin değilim, ancak Örneği bu teknikle güncellediniz: github.com/brianegan/flutter_architecture_samples/tree/master/… Kesinlikle şimdi biraz daha az çoğaltma! Bu uygulama için başka önerileriniz varsa, ayıracak birkaç vaktiniz varsa bir kod incelemesini çok isterim :) Hala bu mantık çapraz platformu (Flutter ve Web) paylaşmanın en iyi yolunu bulmaya ve test edilebilir olduğundan emin olmaya çalışıyorum ( özellikle eşzamansız şeyler).
brianegan

4
Yana updateShouldNotifytesti hep aynı bahsediyor MyInheritedStateÖrneğin, her zaman geri dönmeyecek false? Kesinlikle buildyöntemi MyInheritedStateyeni _MyInheritedörnekler yaratmaktır , ancak dataalan her zaman thishayır mı? Sorun yaşıyorum ... Sadece kodlama yaparsam işe yarar true.
cdock

2
@cdock Evet benim hatam. Açıkçası işe yaramayacağı için bunu neden yaptığımı hatırlamayın. True olarak düzenleyerek düzeltildi, teşekkürler.
Rémi Rousselet

21

TL; DR

UpdateShouldNotify yöntemi içinde yoğun hesaplama kullanmayın ve bir pencere öğesi oluştururken yeni yerine const kullanın


Öncelikle Widget, Element ve Render nesnelerinin ne olduğunu anlamalıyız.

  1. Render nesneleri, gerçekte ekranda gösterilen nesnelerdir. Bunlar değişken , resim ve düzen mantığı içerir. Oluşturma ağacı, web'deki Belge Nesne Modeline (DOM) çok benzer ve bir işleme nesnesine bu ağaçta bir DOM düğümü olarak bakabilirsiniz.
  2. Widget - işlenmesi gereken şeyin bir açıklamasıdır. Bunlar değişmez ve ucuz. Öyleyse, bir Widget "Ne?" (Bildirime dayalı yaklaşım) sorusunu yanıtlarsa, bir Render nesnesi "Nasıl?" (Zorunlu yaklaşım) sorusuna yanıt verir. Web'den bir benzetme "Sanal DOM" dur.
  3. Element / BuildContext - Widget ve Render nesneleri arasında bir proxy'dir . Ağaçta * bir widget'in konumu ve karşılık gelen bir widget değiştirildiğinde Render nesnesinin nasıl güncelleneceği hakkında bilgiler içerir.

Şimdi InheritedWidget ve BuildContext'in inheritFromWidgetOfExactType yöntemine dalmaya hazırız .

Örnek olarak, Flutter'ın InheritedWidget ile ilgili belgelerinden bu örneği dikkate almamızı tavsiye ederim:

class FrogColor extends InheritedWidget {
  const FrogColor({
    Key key,
    @required this.color,
    @required Widget child,
  })  : assert(color != null),
        assert(child != null),
        super(key: key, child: child);

  final Color color;

  static FrogColor of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(FrogColor);
  }

  @override
  bool updateShouldNotify(FrogColor old) {
    return color != old.color;
  }
}

InheritedWidget - sadece bizim durumumuzda önemli bir yöntemi uygulayan bir widget - updateShouldNotify . updateShouldNotify - bir parametre oldWidget'i kabul eden ve bir boole değeri döndüren bir işlev : true veya false.

Herhangi bir widget gibi, InheritedWidget'in de karşılık gelen bir Element nesnesi vardır. Öyle InheritedElement . InheritedElement , her yeni pencere öğesi oluşturduğumuzda pencere öğesinde updateShouldNotify çağrısı yapar ( bir ataya setState'i çağırın ). Ne zaman updateShouldNotify döndürür true aracılığıyla InheritedElement yineler bağımlılıkları (?) Ve çağrı yöntemi didChangeDependencies üzerinde.

InheritedElement bağımlılıkları nereden alır ? Burada inheritFromWidgetOfExactType yöntemine bakmalıyız .

mirasFromWidgetOfExactType - BuildContext'te tanımlanan bu yöntem ve her Element BuildContext arabirimini (Element == BuildContext) uygular. Yani her Element bu yönteme sahiptir.

HeritageFromWidgetOfExactType koduna bakalım:

final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
  assert(ancestor is InheritedElement);
  return inheritFromElement(ancestor, aspect: aspect);
}

Burada, türe göre eşlenen _inheritedWidgets içinde bir ata bulmaya çalışıyoruz . Eğer ata bulunursa, o zaman inheritFromElement'i çağırırız .

İnheritFromElement kodu :

  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
  1. Mevcut elemanın (_dependencies.add (ancestor)) bağımlılığı olarak atayı ekliyoruz
  2. Mevcut öğeyi atanın bağımlılıklarına ekliyoruz (ancestor.updateDependencies (this, angle))
  3. MirasFromWidgetOfExactType (ancestor.widget döndür ) sonucunda atanın parçacığını döndürürüz

Artık InheritedElement'in bağımlılıklarını nereden aldığını biliyoruz.

Şimdi didChangeDependencies yöntemine bakalım . Her Eleman bu yönteme sahiptir:

  void didChangeDependencies() {
    assert(_active); // otherwise markNeedsBuild is a no-op
    assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
    markNeedsBuild();
  }

Gördüğümüz gibi, bu yöntem yalnızca bir öğeyi kirli olarak işaretler ve bu öğenin bir sonraki çerçevede yeniden oluşturulması gerekir. Yeniden oluşturma , karşılık gelen bileşen öğesinde çağrı yöntemi oluşturma anlamına gelir .

Peki ya "InheritedWidget'ı yeniden oluşturduğumda tüm alt ağaç yeniden oluşturuluyor?" Burada Widget'ların değişmez olduğunu ve yeni widget oluşturursanız Flutter'ın alt ağacı yeniden oluşturacağını hatırlamalıyız. Nasıl düzeltebiliriz?

  1. Widget'ları elinizle önbelleğe alın (manuel olarak)
  2. Const kullanın çünkü const , değer / sınıfın tek bir örneğini oluşturur

1
harika açıklama maksimr. Beni en çok şaşırtan şey, miras alınanWidget değiştirildiğinde tüm alt ağaç yine de yeniden inşa edilirse, updateShouldNotify () işlevinin amacı nedir?
Panda World

3

Gönderen docs :

[BuildContext.inheritFromWidgetOfExactType], somut bir InheritedWidget alt sınıfının türü olması gereken belirli türdeki en yakın widget'ı alır ve bu yapı bağlamını, o widget değiştiğinde (veya bu türden yeni bir widget tanıtıldığında, veya pencere öğesi kaybolur), bu yapı bağlamı o pencere öğesinden yeni değerler alabilmesi için yeniden oluşturulur.

Bu genellikle () statik yöntemlerden örtük olarak çağrılır, örneğin Theme.of.

OP'nin belirttiği gibi, bir InheritedWidgetörnek değişmez ... ancak pencere öğesi ağacında aynı konumda yeni bir örnekle değiştirilebilir. Bu olduğunda, kayıtlı widget'ların yeniden oluşturulması gerekebilir. InheritedWidget.updateShouldNotifyYöntem bu belirlenmesini kolaylaştırır. (Bakınız: dokümanlar )

Öyleyse bir örnek nasıl değiştirilebilir? Bir InheritedWidgetörnek, StatefulWidgeteski bir vakayı yeni bir vakayla değiştirebilen a tarafından kapsanabilir.


-1

InheritedWidget, uygulamanın merkezileştirilmiş verilerini yönetir ve çocuğa iletir. Burada açıklandığı gibi sepet sayısını burada saklayabileceğimiz gibi :

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.