Flutter: จะใช้ Inherited Widget ได้อย่างไร?


111

วิธีที่ถูกต้องในการใช้ InheritedWidget คืออะไร? จนถึงตอนนี้ฉันเข้าใจแล้วว่ามันให้โอกาสคุณในการเผยแพร่ข้อมูลลงบนโครงสร้างวิดเจ็ต อย่างมากถ้าคุณใส่เป็น RootWidget มันจะสามารถเข้าถึงได้จากวิดเจ็ตทั้งหมดในแผนภูมิบนเส้นทางทั้งหมดซึ่งดีเพราะอย่างใดฉันต้องทำให้ ViewModel / Model ของฉันสามารถเข้าถึงได้สำหรับวิดเจ็ตของฉันโดยไม่ต้องหันไปใช้ globals หรือ Singletons

แต่ InheritedWidget ไม่เปลี่ยนรูปฉันจะอัปเดตได้อย่างไร และที่สำคัญกว่านั้นวิดเจ็ตสถานะของฉันถูกกระตุ้นให้สร้างทรีย่อยของพวกมันขึ้นมาใหม่ได้อย่างไร

น่าเสียดายที่เอกสารนี้ไม่ชัดเจนมากและหลังจากการพูดคุยกับหลายคนดูเหมือนจะไม่มีใครรู้ว่าวิธีการใช้ที่ถูกต้องคืออะไร

ฉันเพิ่มคำพูดจาก Brian Egan:

ใช่ฉันเห็นว่าเป็นวิธีการเผยแพร่ข้อมูลลงบนต้นไม้ สิ่งที่ฉันพบว่าสับสนจากเอกสาร API:

"วิดเจ็ตที่สืบทอดมาเมื่ออ้างอิงด้วยวิธีนี้จะทำให้ผู้บริโภคสร้างใหม่เมื่อวิดเจ็ตที่สืบทอดมานั้นเปลี่ยนสถานะ"

เมื่อฉันอ่านครั้งแรกฉันคิดว่า:

ฉันสามารถยัดข้อมูลบางอย่างใน InheritedWidget และกลายพันธุ์ได้ในภายหลัง เมื่อการกลายพันธุ์นั้นเกิดขึ้นมันจะสร้างวิดเจ็ตทั้งหมดที่อ้างอิง InheritedWidget ของฉันสิ่งที่ฉันพบ:

ในการเปลี่ยนสถานะของ InheritedWidget คุณต้องรวมไว้ใน StatefulWidget จากนั้นคุณจะกลายพันธุ์สถานะของ StatefulWidget และส่งข้อมูลนี้ไปยัง InheritedWidget ซึ่งส่งข้อมูลไปยังลูก ๆ ทั้งหมด อย่างไรก็ตามในกรณีนั้นดูเหมือนว่าจะสร้างต้นไม้ทั้งหมดใหม่ภายใต้ StatefulWidget ไม่ใช่แค่วิดเจ็ตที่อ้างอิงถึง InheritedWidget ถูกต้องหรือไม่ หรือจะรู้วิธีข้ามวิดเจ็ตที่อ้างอิง InheritedWidget ได้อย่างไรหาก updateShouldNotify ส่งคืนเท็จ

คำตอบ:


112

ปัญหามาจากคำพูดของคุณซึ่งไม่ถูกต้อง

ดังที่คุณกล่าวไว้ InheritedWidgets ก็เหมือนกับวิดเจ็ตอื่น ๆ ที่ไม่เปลี่ยนรูป ดังนั้นพวกเขาจึงไม่ได้อัปเดต พวกเขาถูกสร้างขึ้นใหม่

สิ่งนี้คือ: InheritedWidget เป็นเพียงวิดเจ็ตธรรมดาที่ไม่ทำอะไรเลยนอกจากเก็บข้อมูลไว้ มันไม่มีตรรกะของการอัพเดทหรืออะไรเลย แต่เช่นเดียวกับวิดเจ็ตอื่น ๆ มันเชื่อมโยงกับElementไฟล์. และเดาอะไร? สิ่งนี้ไม่แน่นอนและกระพือปีกจะใช้ซ้ำทุกครั้งที่ทำได้!

คำพูดที่ได้รับการแก้ไขจะเป็น:

InheritedWidget เมื่ออ้างถึงในลักษณะนี้จะทำให้ผู้บริโภคสร้างใหม่เมื่อ InheritedWidget เชื่อมโยงกับการเปลี่ยนแปลงInheritedElement

มีการพูดคุยเกี่ยวกับวิธีการที่ดี widgets / องค์ประกอบ / renderbox จะ pluged เข้าด้วยกันเป็น แต่ในระยะสั้นมันเป็นแบบนี้ (ทางซ้ายคือวิดเจ็ตทั่วไปของคุณตรงกลางคือ 'องค์ประกอบ' และทางขวาคือ 'กล่องแสดงผล'):

ป้อนคำอธิบายภาพที่นี่

สิ่งนี้คือ: เมื่อคุณสร้างอินสแตนซ์วิดเจ็ตใหม่ ความกระพือปีกจะเปรียบเทียบกับของเก่า ใช้ "องค์ประกอบ" ซ้ำซึ่งชี้ไปที่ RenderBox และกลายพันธุ์คุณสมบัติ RenderBox


โอเค แต่สิ่งนี้จะตอบคำถามของฉันได้อย่างไร?

เมื่อสร้างอินสแตนซ์ InheritedWidget แล้วเรียกcontext.inheritedWidgetOfExactType(หรือMyClass.ofซึ่งโดยพื้นฐานแล้วจะเหมือนกัน); สิ่งที่เป็นนัยคือมันจะรับฟังสิ่งที่Elementเกี่ยวข้องกับInheritedWidgetไฟล์. และเมื่อใดก็ตามที่Elementได้รับวิดเจ็ตใหม่จะบังคับให้รีเฟรชวิดเจ็ตใด ๆ ที่เรียกว่าเมธอดก่อนหน้านี้

ในระยะสั้นเมื่อคุณแทนที่สิ่งที่มีอยู่InheritedWidgetด้วยของใหม่ กระพือจะเห็นว่ามันเปลี่ยนไป และจะแจ้งให้วิดเจ็ตทราบถึงการปรับเปลี่ยนที่อาจเกิดขึ้น

หากคุณเข้าใจทุกอย่างคุณน่าจะเดาทางออกได้แล้ว:

ห่อหุ้มInheritedWidgetภายในของคุณStatefulWidgetที่จะสร้างแบรนด์ใหม่InheritedWidgetเมื่อใดก็ตามที่มีการเปลี่ยนแปลง!

ผลลัพธ์สุดท้ายในรหัสจริงจะเป็น:

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

แต่จะไม่สร้าง InheritedWidget ใหม่สร้างต้นไม้ทั้งต้นขึ้นมาใหม่?

ไม่มันไม่จำเป็น เนื่องจาก InheritedWidget ใหม่ของคุณอาจมีลูกคนเดียวกันเหมือนเดิม และแน่นอนฉันหมายถึงอินสแตนซ์เดียวกัน วิดเจ็ตที่มีอินสแตนซ์เดียวกันก่อนหน้านี้จะไม่สร้างใหม่

และในสถานการณ์มากที่สุด (การมี inheritedWidget ที่รากของแอปของคุณ), วิดเจ็ตที่สืบทอดมาเป็นอย่างต่อเนื่อง ดังนั้นจึงไม่จำเป็นต้องสร้างใหม่


1
แต่จะไม่สร้าง InheritedWidget ใหม่สร้างต้นไม้ทั้งต้นขึ้นมาใหม่? แล้วทำไมต้องมีผู้ฟัง?
Thomas

1
สำหรับความคิดเห็นแรกของคุณฉันได้เพิ่มส่วนที่สามในคำตอบของฉัน สำหรับความน่าเบื่อ: ฉันไม่เห็นด้วย ข้อมูลโค้ดสามารถสร้างสิ่งนี้ได้ค่อนข้างง่าย MyInherited.of(context)และการเข้าถึงข้อมูลเป็นง่ายๆเป็นโทร
Rémi Rousselet

3
ไม่แน่ใจว่าคุณสนใจหรือไม่ แต่ได้อัปเดตตัวอย่างด้วยเทคนิคนี้: github.com/brianegan/flutter_architecture_samples/tree/master/… ตอนนี้การทำซ้ำน้อยลงเล็กน้อยแน่นอน! หากคุณมีข้อเสนอแนะอื่น ๆ สำหรับการใช้งานนั้นจะชอบการตรวจสอบโค้ดหากคุณมีเวลาว่างสักครู่ :) ยังคงพยายามหาวิธีที่ดีที่สุดในการแบ่งปันตรรกะข้ามแพลตฟอร์มนี้ (Flutter and Web) และตรวจสอบให้แน่ใจว่าสามารถทดสอบได้ ( โดยเฉพาะสิ่งที่ async)
brianegan

4
เนื่องจากการupdateShouldNotifyทดสอบมักอ้างถึงMyInheritedStateอินสแตนซ์เดียวกันจึงไม่ส่งคืนเสมอไปfalseหรือ แน่นอนว่าbuildวิธีMyInheritedStateการสร้าง_MyInheritedอินสแตนซ์ใหม่แต่dataฟิลด์จะอ้างถึงเสมอthis? ฉันมีปัญหา ... trueการทำงานถ้าฉันยากรหัสเพียง
cdock

2
@cdock ใช่ฉันไม่ดี จำไม่ได้ว่าทำไมฉันถึงทำอย่างนั้นเพราะเห็นได้ชัดว่ามันไม่ได้ผล แก้ไขโดยการแก้ไขเป็นจริงขอบคุณ
Rémi Rousselet

21

TL; ดร

อย่าใช้การคำนวณหนักภายในupdateShouldNotifyวิธีการและการใช้constแทน ใหม่เมื่อมีการสร้างเครื่องมือ


ก่อนอื่นเราควรทำความเข้าใจว่าอะไรคือ Widget, Element และ Render objects

  1. Render objects คือสิ่งที่แสดงบนหน้าจอจริงๆ พวกเขาไม่แน่นอนมีตรรกะการวาดภาพและเค้าโครง แผนผังการแสดงผลนั้นคล้ายกับ Document Object Model (DOM) ในเว็บมากและคุณสามารถดูวัตถุที่แสดงผลเป็นโหนด DOM ในโครงสร้างนี้
  2. วิดเจ็ต - คือรายละเอียดของสิ่งที่ควรแสดงผล พวกมันไม่เปลี่ยนรูปและราคาถูก ดังนั้นหากวิดเจ็ตตอบคำถาม "อะไร" (วิธีการสำแดง) วัตถุ Render จะตอบคำถามว่า "อย่างไร" (แนวทางที่จำเป็น) การเปรียบเทียบจากเว็บคือ "Virtual DOM"
  3. Element / BuildContext - เป็นพร็อกซีระหว่างวิดเจ็ตและอ็อบเจกต์Render มีข้อมูลเกี่ยวกับตำแหน่งของวิดเจ็ตในทรี * และวิธีการอัพเดตอ็อบเจ็กต์ Render เมื่อวิดเจ็ตที่เกี่ยวข้องมีการเปลี่ยนแปลง

ตอนนี้เราพร้อมที่จะดำน้ำในInheritedWidgetและ BuildContext ของวิธีinheritFromWidgetOfExactType

ตัวอย่างเช่นฉันแนะนำให้เราพิจารณาตัวอย่างนี้จากเอกสารของ Flutter เกี่ยวกับ InheritedWidget:

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 - เพียงแค่เครื่องมือที่นำไปปฏิบัติในวิธีการที่สำคัญกรณีหนึ่งของเรา - updateShouldNotify updateShouldNotify - ฟังก์ชันที่รับพารามิเตอร์oldWidgetหนึ่งพารามิเตอร์และส่งคืนค่าบูลีน: จริงหรือเท็จ

เช่นเดียวกับวิดเจ็ตใด ๆInheritedWidgetมีวัตถุองค์ประกอบที่เกี่ยวข้อง มันเป็นInheritedElement การเรียกใช้ InheritedElement updateShouldNotifyบนวิดเจ็ตทุกครั้งที่เราสร้างวิดเจ็ตใหม่ (เรียกใช้setStateบนบรรพบุรุษ) เมื่อupdateShouldNotifyส่งคืนจริง InheritedElement วนซ้ำผ่านการอ้างอิง (?) และเรียกเมธอดdidChangeDependenciesบนมัน

InheritedElement ได้รับการอ้างอิงที่ไหน? ที่นี่เราควรดูเมธอดinheritFromWidgetOfExactType

inheritFromWidgetOfExactType - เมธอดนี้กำหนดไว้ใน BuildContext และ ทุกอิลิเมนต์จะใช้อินเทอร์เฟซ BuildContext (Element == BuildContext) ดังนั้นทุก Element จึงมีวิธีนี้

มาดูรหัสของ inheritFromWidgetOfExactType:

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

ที่นี่เราพยายามค้นหาบรรพบุรุษใน_inheritedWidgets ที่แมปตามประเภท ถ้าบรรพบุรุษที่พบเราแล้วโทรinheritFromElement

รหัสสำหรับinheritFromElement :

  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
  1. เราเพิ่มบรรพบุรุษเป็นการพึ่งพาขององค์ประกอบปัจจุบัน (_dependencies.add (บรรพบุรุษ))
  2. เราเพิ่มองค์ประกอบปัจจุบันในการอ้างอิงของบรรพบุรุษ (บรรพบุรุษ.updateDependencies (นี้, ด้าน))
  3. เราส่งคืนวิดเจ็ตของบรรพบุรุษอันเป็นผลมาจากinheritFromWidgetOfExactType (return บรรพบุรุษ. widget )

ตอนนี้เรารู้แล้วว่า InheritedElement ได้รับการอ้างอิงที่ไหน

ตอนนี้ให้ดูที่ วิธีdidChangeDependencies ทุกองค์ประกอบมีวิธีนี้:

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

ดังที่เราเห็นวิธีนี้เพียงแค่ทำเครื่องหมายองค์ประกอบว่าสกปรกและควรสร้างองค์ประกอบนี้ใหม่ในเฟรมถัดไปสร้างใหม่หมายถึงวิธีการโทรที่สร้างบนองค์ประกอบวิดเจ็ต coresponding

แต่สิ่งที่เกี่ยวกับ "ต้นไม้ย่อยทั้งต้นสร้างใหม่เมื่อฉันสร้าง InheritedWidget ใหม่" ที่นี่เราควรจำไว้ว่าวิดเจ็ตไม่เปลี่ยนรูปและหากคุณสร้างวิดเจ็ตใหม่ Flutter จะสร้างแผนผังย่อยขึ้นมาใหม่ เราจะแก้ไขได้อย่างไร?

  1. แคชวิดเจ็ตด้วยมือ (ด้วยตนเอง)
  2. ใช้constเนื่องจาก const สร้างค่า / คลาสเพียงอินสแตนซ์เดียว

1
คำอธิบายที่ดี maksimr สิ่งที่ทำให้ฉันสับสนมากที่สุดก็คือถ้าต้นไม้ย่อยทั้งหมดถูกสร้างขึ้นมาใหม่เมื่อได้รับการแทนที่ด้วยสืบทอด Widget จุดของ updateShouldNotify () คืออะไร?
Panda World

3

จากเอกสาร :

[BuildContext.inheritFromWidgetOfExactType] ได้รับวิดเจ็ตที่ใกล้ที่สุดของประเภทที่กำหนดซึ่งต้องเป็นประเภทของคลาสย่อย InheritedWidget ที่เป็นรูปธรรมและลงทะเบียนบริบทการสร้างนี้กับวิดเจ็ตดังกล่าวเมื่อวิดเจ็ตนั้นเปลี่ยนไป (หรือมีการแนะนำวิดเจ็ตใหม่ของประเภทนั้น หรือวิดเจ็ตหายไป) บริบทการสร้างนี้ถูกสร้างขึ้นใหม่เพื่อให้สามารถรับค่าใหม่จากวิดเจ็ตนั้นได้

โดยทั่วไปจะเรียกโดยปริยายจาก () วิธีการแบบคงที่เช่น Theme.of

ตามที่ OP ระบุไว้InheritedWidgetอินสแตนซ์ไม่เปลี่ยนแปลง ... แต่สามารถแทนที่ด้วยอินสแตนซ์ใหม่ที่ตำแหน่งเดียวกันในแผนผังวิดเจ็ต เมื่อเป็นเช่นนั้นอาจเป็นไปได้ว่าต้องสร้างวิดเจ็ตที่ลงทะเบียนใหม่ InheritedWidget.updateShouldNotifyวิธีการทำให้ความมุ่งมั่นนี้ (ดู: เอกสาร )

แล้วอินสแตนซ์จะถูกแทนที่ได้อย่างไร? InheritedWidgetเช่นอาจจะอยู่ด้วยStatefulWidgetซึ่งอาจแทนที่อินสแตนซ์เก่ากับตัวอย่างใหม่


-1

InheritedWidget จัดการข้อมูลส่วนกลางของแอพและส่งต่อไปยังเด็กเช่นเดียวกับที่เราสามารถจัดเก็บที่นี่จำนวนรถเข็นตามที่อธิบายไว้ที่นี่ :

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.