InheritedWidget เป็นวิดเจ็ตพิเศษใน Flutter ที่ส่งข้อมูลลงด้านล่างในทรีของวิดเจ็ตโดยไม่ต้องส่งผ่านคอนสตรัคเตอร์อย่างชัดเจน วิดเจ็ตลูกเข้าถึงข้อมูลผ่าน BuildContext และสมัครรับการอัปเดตโดยอัตโนมัติ เมื่อข้อมูลใน InheritedWidget เปลี่ยนแปลง วิดเจ็ตที่พึ่งพาทั้งหมดจะถูกสร้างใหม่ ตาม Flutter API Reference, 2025 InheritedWidget เป็นรากฐานของ Theme, MediaQuery, Localizations และไลบรารีการจัดการสถานะส่วนใหญ่
ประเด็นสำคัญ
InheritedWidget คือวิดเจ็ตที่ทำให้ข้อมูลของมันพร้อมใช้งานสำหรับผู้สืบทอดทั้งหมดใน Widget Tree แตกต่างจากวิดเจ็ตทั่วไปที่ส่งข้อมูลผ่านคอนสตรัคเตอร์ไปยังองค์ประกอบลูกเท่านั้น InheritedWidget อนุญาตให้วิดเจ็ตใด ๆ ในทรีย่อยเข้าถึงข้อมูลโดยไม่มีสายพารามิเตอร์ ซึ่งแก้ปัญหา “prop drilling” — การส่งข้อมูลผ่านวิดเจ็ตกลางจำนวนมากที่ไม่ได้ใช้ข้อมูลนี้ด้วยตัวเอง
Flutter มี InheritedWidget ในตัวหลายตัว: Theme (รูปแบบสีและสไตล์), MediaQuery (ขนาดหน้าจอ, การวางแนว, ความหนาแน่นพิกเซล), Localizations (สตริงที่แปล), Directionality (ทิศทางข้อความ), DefaultTextStyle (สไตล์ข้อความเริ่มต้น) วิดเจ็ตเหล่านี้ถูกตั้งค่าโดยวิดเจ็ตหลักเช่น MaterialApp และพร้อมใช้งานทั่วทั้งแอปพลิเคชัน
InheritedWidget ไม่มีสถานะของตัวเอง — มันเก็บข้อมูลที่ส่งผ่านคอนสตรัคเตอร์ เมื่อพาเรนต์ของ InheritedWidget ถูกสร้างใหม่ด้วยข้อมูลใหม่ เมธอด updateShouldNotify จะถูกเรียกเพื่อเปรียบเทียบข้อมูลเก่าและใหม่ ถ้าเมธอดคืนค่า true วิดเจ็ตที่พึ่งพาทั้งหมดจะถูกทำเครื่องหมายสำหรับการสร้างใหม่ นี่เป็นกลไกการอัปเดตแบบรีแอกทีฟที่เรียบง่ายแต่มีประสิทธิภาพ
กลไกการส่งผ่านข้อมูล ผ่าน InheritedWidget ขึ้นอยู่กับ Element Tree เมื่อวิดเจ็ตเรียก dependOnInheritedWidgetOfExactType องค์ประกอบที่เกี่ยวข้องจะลงทะเบียนการพึ่งพาบน InheritedElement เมื่อ InheritedWidget เปลี่ยนแปลง InheritedElement จะแจ้งองค์ประกอบที่พึ่งพาทั้งหมด ซึ่งจะถูกสร้างใหม่ในเฟรมถัดไป
เมธอด dependOnInheritedWidgetOfExactType ไม่เพียงแค่ค้นหา InheritedWidget ในทรี — มันสมัครรับการแจ้งเตือนให้กับองค์ประกอบปัจจุบัน ถ้าคุณใช้ findAncestorWidgetOfExactType แทน dependOn วิดเจ็ตจะได้รับข้อมูลแต่จะไม่ถูกสร้างใหม่เมื่อข้อมูลเปลี่ยนแปลง นี่เป็นความแตกต่างที่สำคัญ: dependOn คือการสมัครรับ ส่วน findAncestor คือการค้นหาเพียงครั้งเดียว
เมื่อวิดเจ็ตขอ InheritedWidget Flutter จะเดินขึ้น Element Tree จากองค์ประกอบปัจจุบันไปยังราก ตรวจสอบ InheritedElement แต่ละตัวเพื่อหาชนิดที่ตรงกัน InheritedElement ตัวแรกที่ตรงกันจะถูกคืนค่า ซึ่งหมายความว่า InheritedWidget ที่ใกล้ที่สุดในทรีมีลำดับความสำคัญ — คุณสามารถแทนที่ข้อมูลในระดับที่กำหนดโดยวาง InheritedWidget ใกล้กับผู้สืบทอดมากขึ้น
class ThemeData {
final Color primaryColor;
final TextTheme textTheme;
const ThemeData({required this.primaryColor, required this.textTheme});
}
class MyTheme extends InheritedWidget {
final ThemeData data;
const MyTheme({required this.data, required Widget child}) : super(child: child);
static MyTheme of(BuildContext context) {
final widget = context.dependOnInheritedWidgetOfExactType<MyTheme>();
assert(widget != null, "MyTheme not found in tree");
return widget!;
}
@override
bool updateShouldNotify(MyTheme oldWidget) => oldWidget.data != data;
}
ในตัวอย่างนี้ MyTheme ใช้เมธอดสแตติก of เพื่อให้ข้อมูลแก่ผู้สืบทอด เมธอด dependOnInheritedWidgetOfExactType ลงทะเบียนการพึ่งพา และ updateShouldNotify เปรียบเทียบข้อมูลเก่าและใหม่เพื่อพิจารณาว่าวิดเจ็ตที่พึ่งพาจำเป็นต้องสร้างใหม่หรือไม่
การสร้าง InheritedWidget แบบกำหนดเอง ประกอบด้วยสองขั้นตอน: การกำหนดคลาสที่ขยาย InheritedWidget และการใช้งานเมธอดสแตติก of สำหรับการเข้าถึงจากผู้สืบทอด ข้อมูลถูกส่งผ่านคอนสตรัคเตอร์ และเมธอด updateShouldNotify กำหนดว่าเมื่อใดวิดเจ็ตที่พึ่งพาควรถูกสร้างใหม่
คลาสต้องขยาย InheritedWidget และรับข้อมูลผ่านคอนสตรัคเตอร์ที่มีพารามิเตอร์ child ที่จำเป็น ข้อมูลสามารถเป็นชนิดใดก็ได้: พรีมิทีฟ, ออบเจ็กต์, ฟังก์ชัน กฎหลักคือข้อมูลต้องไม่เปลี่ยนสภาพ (immutable) เพื่อให้สามารถเปรียบเทียบค่าเก่าและใหม่ได้อย่างน่าเชื่อถือ
เมธอดสแตติก of รับ BuildContext และคืนค่าข้อมูล InheritedWidget ภายใน มันเรียก dependOnInheritedWidgetOfExactType ซึ่งค้นหา InheritedWidget ที่ใกล้ที่สุดของชนิดที่ระบุในทรี ถ้าไม่พบ InheritedWidget เมธอดจะโยนข้อยกเว้นหรือคืนค่าเริ่มต้นขึ้นอยู่กับการใช้งาน
เพื่อเข้าถึงข้อมูล วิดเจ็ตเรียก MyWidget.of(context) ภายในเมธอด build Flutter จะสมัครรับการอัปเดตให้กับวิดเจ็ตโดยอัตโนมัติ ถ้าข้อมูลเปลี่ยนแปลง วิดเจ็ตจะถูกสร้างใหม่ในเฟรมถัดไป ซึ่งช่วยให้โค้ดสะอาดและเป็น declarative โดยไม่มีพารามิเตอร์ที่ไม่จำเป็น
class UserPreferences extends InheritedWidget {
final String languageCode;
final bool darkMode;
const UserPreferences({
required this.languageCode,
required this.darkMode,
required Widget child,
}) : super(child: child);
static UserPreferences of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<UserPreferences>()!;
}
@override
bool updateShouldNotify(UserPreferences oldWidget) =>
oldWidget.languageCode != languageCode || oldWidget.darkMode != darkMode;
}
ในตัวอย่างนี้ UserPreferences เก็บการตั้งค่าผู้ใช้ เมธอด updateShouldNotify เปรียบเทียบแต่ละฟิลด์แยกกัน ซึ่งป้องกันการสร้างใหม่ที่ไม่จำเป็นเมื่อพารามิเตอร์เดียวเปลี่ยนแปลง ใช้แนวทางที่คล้ายกันสำหรับ InheritedWidget แบบกำหนดเองของคุณที่มีหลายฟิลด์
updateShouldNotify เป็นเมธอดหลักของ InheritedWidget ที่กำหนดว่าวิดเจ็ตที่พึ่งพาจำเป็นต้องได้รับการแจ้งเตือนเกี่ยวกับการเปลี่ยนแปลงข้อมูลหรือไม่ ถ้าเมธอดคืนค่า false วิดเจ็ตที่พึ่งพาจะไม่ถูกสร้างใหม่ แม้ว่า InheritedWidget เองจะได้รับอินสแตนซ์ใหม่ที่มีข้อมูลเดียวกัน นี่เป็นสิ่งสำคัญอย่างยิ่งสำหรับประสิทธิภาพ
เปรียบเทียบเฉพาะฟิลด์ที่ เปลี่ยนแปลงจริง และส่งผลต่อการแสดงผล ถ้า InheritedWidget มี 10 ฟิลด์แต่มีเพียงฟิลด์เดียวที่ส่งผลต่อ UI ให้ตรวจสอบเฉพาะฟิลด์นั้น สำหรับคอลเลกชัน ให้ใช้การเปรียบเทียบเชิงลึกหรือโครงสร้างข้อมูลที่ไม่เปลี่ยนสภาพ อย่าใช้ == สำหรับ List หรือ Map เพราะเปรียบเทียบโดยการอ้างอิง
ข้อผิดพลาดที่พบบ่อยที่สุดคือการคืนค่า true โดยไม่เปรียบเทียบ ซึ่งทำให้วิดเจ็ตที่พึ่งพาทั้งหมดถูกสร้างใหม่ทุกครั้งที่มีการอัปเดตพาเรนต์ แม้ว่าข้อมูลจะไม่เปลี่ยนแปลงก็ตาม ข้อผิดพลาดที่สองคือการคืนค่า false เมื่อข้อมูลเปลี่ยนแปลง ทำให้ UI ล้าสมัย ข้อผิดพลาดที่สามคือการเปรียบเทียบที่ซับซ้อนซึ่งทำงานทุกเฟรมและทำให้ประสิทธิภาพช้าลง
InheritedWidget และคอลแบ็ก (การส่งฟังก์ชันผ่านคอนสตรัคเตอร์) แก้ปัญหาที่แตกต่างกัน InheritedWidget เหมาะสำหรับข้อมูลที่จำเป็นโดยวิดเจ็ตจำนวนมากในระดับต่าง ๆ ของทรี คอลแบ็กสะดวกสำหรับการส่งอีเวนต์ทางเดียวจากพาเรนต์ไปยังวิดเจ็ตลูกที่เฉพาะเจาะจงหรือในทางกลับกัน การเลือกขึ้นอยู่กับสถาปัตยกรรมแอปพลิเคชันและความถี่ในการอัปเดต
ใช้ InheritedWidget เมื่อข้อมูลจำเป็นโดย วิดเจ็ตจำนวนมาก ในระดับการซ้อนที่แตกต่างกัน: ธีมแอป, การตั้งค่าผู้ใช้, ข้อมูลอุปกรณ์, ข้อมูลเซสชันปัจจุบัน InheritedWidget มีประสิทธิภาพเป็นพิเศษสำหรับข้อมูล “ทั่วโลก” ที่ไม่ค่อยเปลี่ยนแปลงแต่จำเป็นในส่วนต่าง ๆ ของ UI
คอลแบ็ก (ฟังก์ชันเรียกกลับ) เหมาะสำหรับการส่งอีเวนต์จากวิดเจ็ตลูกไปยังพาเรนต์: การกดปุ่ม, การเลือกรายการ, การส่งฟอร์ม คอลแบ็กบ่งชี้อย่างชัดเจนว่าวิดเจ็ตลูกสามารถดำเนินการใดได้บ้างและไม่สร้างการพึ่งพาที่ซ่อนอยู่ สำหรับการส่งข้อมูลลงด้านล่างในทรีผ่านระดับจำนวนน้อย การใช้พารามิเตอร์คอนสตรัคเตอร์ก็ง่ายกว่าเช่นกัน
| เกณฑ์ | InheritedWidget | คอลแบ็ก |
|---|---|---|
| ทิศทาง | จากบนลงล่าง (พาเรนต์ → ผู้สืบทอด) | จากล่างขึ้นบน (ลูก → พาเรนต์) หรือโดยตรง |
| ขอบเขต | ทรีย่อยทั้งหมด | วิดเจ็ตเฉพาะ |
| การสร้างใหม่ | อัตโนมัติเมื่อข้อมูลเปลี่ยน | ต้องใช้ setState ด้วยตนเอง |
| ความซับซ้อน | ปานกลาง (ต้องใช้คลาส InheritedWidget) | ต่ำ (แค่ฟังก์ชัน) |
Provider และ Riverpod เป็นไลบรารีการจัดการสถานะยอดนิยมใน Flutter ที่สร้างบน InheritedWidget พวกมันขยายความสามารถ: เพิ่มการสนับสนุน ChangeNotifier, การล้างข้อมูลอัตโนมัติเมื่อถอดออก, การเริ่มต้นแบบขี้เกียจ และไวยากรณ์ที่เรียบง่ายด้วยジェเนอริก
Provider ใช้ InheritedWidget เพื่อส่งออบเจ็กต์ชนิดใดก็ได้ลงด้านล่างในทรี ChangeNotifierProvider ติดตามการเปลี่ยนแปลงผ่าน ChangeNotifier และเรียก updateShouldNotify เมื่อ notifyListeners ถูกเรียก ซึ่งช่วยให้นักพัฒนาไม่ต้องสร้าง InheritedWidget ด้วยตนเองและใช้งาน updateShouldNotify
InheritedWidget โดยตรงให้การควบคุมมากขึ้นและไม่ต้องการการพึ่งพาภายนอก Provider ให้โครงสร้างพื้นฐานพร้อมใช้: Consumer, Selector, MultiProvider, ProxyProvider การเลือกขึ้นอยู่กับความซับซ้อนของแอปพลิเคชัน สำหรับโปรเจ็กต์ง่าย InheritedWidget โดยตรงก็เพียงพอ สำหรับโปรเจ็กต์ใหญ่ Provider หรือ Riverpod ลดโค้ดเทมเพลต
// InheritedWidget โดยตรง
class UserProvider extends InheritedWidget {
final UserData userData;
const UserProvider({required this.userData, required Widget child}) : super(child: child);
static UserData of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<UserProvider>()!.userData;
@override
bool updateShouldNotify(UserProvider old) => old.userData != userData;
}
// เทียบเท่า Provider
return ChangeNotifierProvider<UserData>(
create: (_) => UserData(),
child: MyApp(),
);
ทั้งสองแนวทางในตัวอย่างแก้ปัญหาเดียวกัน — การส่ง UserData ลงด้านล่างในทรี Provider ลดปริมาณโค้ดแต่ซ่อนกลไกของ InheritedWidget InheritedWidget โดยตรงให้การควบคุมเต็มที่และความเข้าใจในสิ่งที่เกิดขึ้น ซึ่งสำคัญเป็นพิเศษเมื่อเรียนรู้ Flutter และดีบักปัญหาการสร้างใหม่ที่ซับซ้อน
คำถามที่พบบ่อย
InheritedWidget ทำให้ข้อมูลพร้อมใช้งานสำหรับผู้สืบทอดทั้งหมดผ่าน BuildContext ในขณะที่วิดเจ็ตทั่วไปส่งข้อมูลผ่านคอนสตรัคเตอร์เท่านั้น InheritedWidget ยังสมัครรับการอัปเดตข้อมูลให้กับผู้สืบทอดด้วย
วิดเจ็ตที่พึ่งพาจะถูกสร้างใหม่เมื่อ updateShouldNotify คืนค่า true เท่านั้น ถ้าเมธอดถูกใช้งานอย่างถูกต้อง การสร้างใหม่จะเกิดขึ้นเมื่อข้อมูลเปลี่ยนแปลงจริงเท่านั้น ไม่ใช่ทุกครั้งที่พาเรนต์ถูกสร้างใหม่
ได้ คุณสามารถใช้ InheritedWidget จำนวนเท่าใดก็ได้ ในทรีเดียวกัน แต่ละตัวให้ข้อมูลชนิดเฉพาะ และวิดเจ็ตสามารถรับข้อมูลจาก InheritedWidget หลายตัวพร้อมกัน
dependOn สมัครรับการอัปเดตให้วิดเจ็ต — เมื่อข้อมูลเปลี่ยนแปลง วิดเจ็ตจะถูกสร้างใหม่ findAncestor ทำการค้นหาครั้งเดียวโดยไม่สมัครรับ และวิดเจ็ตจะไม่ทราบเกี่ยวกับการเปลี่ยนแปลงข้อมูล
สำหรับ สถานะง่าย (ธีม, การตั้งค่า) InheritedWidget ก็เพียงพอ สำหรับสถานะซับซ้อนที่มีตรรกะทางธุรกิจ ใช้ Provider, Riverpod หรือ BLoC — สิ่งเหล่านี้สร้างบน InheritedWidget และเพิ่มโครงสร้างพื้นฐานที่จำเป็น
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ