การใช้ InheritedWidget เพื่อส่งข้อมูลลงไปใน Widget Tree
ในบทความก่อนหน้า เราได้เรียนรู้เกี่ยวกับการสร้าง Custom Widget ที่มี State และการจัดการ State ด้วย StatefulWidget ในบทความนี้ เราจะมาเรียนรู้วิธีการส่งข้อมูลจาก Widget ต้นทางไปยัง Widget ลูกหลานที่อยู่ใน Widget Tree โดยไม่ต้องทำการส่งผ่านข้อมูล (Prop Drilling) ผ่าน Widget แต่ละตัว ซึ่งทำได้ด้วย InheritedWidget
InheritedWidget คืออะไร?
InheritedWidget เป็น Widget พิเศษใน Flutter ที่ช่วยให้ Widget ลูกหลานสามารถเข้าถึงข้อมูลที่ถูกจัดเก็บไว้ใน InheritedWidget ได้โดยตรง โดยไม่ต้องส่งผ่านข้อมูลผ่านทาง constructor ของแต่ละ Widget (Prop Drilling) ทำให้โค้ดมีความสะอาด อ่านง่าย และบำรุงรักษาได้ง่ายขึ้น
แนวคิดเบื้องหลัง InheritedWidget คือการสร้าง "ท่อ" หรือ "ช่องทาง" ให้ Widget ลูกหลานสามารถ "แอบมอง" และดึงข้อมูลจาก Widget ต้นทางได้เมื่อต้องการ โดย Flutter จะทำการแจ้งเตือน (notify) Widget ลูกหลานที่ใช้งานข้อมูลนั้นเมื่อข้อมูลมีการเปลี่ยนแปลง ทำให้ UI สามารถอัปเดตได้อย่างถูกต้อง
โครงสร้างของ InheritedWidget
ในการสร้าง InheritedWidget เราจะต้องสร้าง class ที่ extends InheritedWidget และ override method updateShouldNotify ซึ่งเป็นตัวกำหนดว่า Widget ลูกหลานควรถูก rebuild หรือไม่เมื่อข้อมูลใน InheritedWidget มีการเปลี่ยนแปลง
class MyInheritedWidget extends InheritedWidget {
const MyInheritedWidget({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
final String data;
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return data != oldWidget.data; // rebuild ถ้า data เปลี่ยน
}
// Helper method เพื่อให้ Widget ลูกหลานเข้าถึงข้อมูลได้ง่าย
static MyInheritedWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
}
}
คำอธิบาย Code:
MyInheritedWidget: คือ class ที่ extendsInheritedWidgetdata: คือข้อมูลที่เราต้องการส่งต่อให้กับ Widget ลูกหลานchild: คือ Widget ที่InheritedWidgetครอบอยู่ ซึ่ง Widget ลูกหลานจะอยู่ใน subtree ของ child นี้updateShouldNotify: เป็น method ที่เรา override เพื่อกำหนดว่า Widget ลูกหลานควรถูก rebuild หรือไม่ เมื่อdataเปลี่ยนแปลงof(BuildContext context): เป็น helper method ที่ช่วยให้ Widget ลูกหลานสามารถเข้าถึงMyInheritedWidgetได้ง่ายขึ้น โดยใช้BuildContext.dependOnInheritedWidgetOfExactType
การใช้งาน InheritedWidget
ขั้นตอนการใช้งาน InheritedWidget มีดังนี้:
- สร้าง
InheritedWidgetclass (ตามตัวอย่างด้านบน) - วาง
InheritedWidgetไว้เหนือ Widget Tree ที่เราต้องการให้ Widget ลูกหลานเข้าถึงข้อมูลได้ - ใช้
BuildContext.dependOnInheritedWidgetOfExactTypeหรือ helper method ที่เราสร้างขึ้น (เช่นMyInheritedWidget.of(context)) ใน Widget ลูกหลานเพื่อดึงข้อมูล
ตัวอย่าง:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'InheritedWidget Example',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _data = 'Initial Data';
void _updateData() {
setState(() {
_data = 'Updated Data';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('InheritedWidget Example'),
),
body: MyInheritedWidget(
data: _data,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Data from InheritedWidget:'),
MyDataWidget(),
ElevatedButton(
onPressed: _updateData,
child: Text('Update Data'),
),
],
),
),
),
);
}
}
class MyInheritedWidget extends InheritedWidget {
const MyInheritedWidget({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
final String data;
@override
bool updateShouldNotify(MyInheritedWidget oldWidget) {
return data != oldWidget.data;
}
static MyInheritedWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
}
}
class MyDataWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final inheritedWidget = MyInheritedWidget.of(context);
return Text(
inheritedWidget?.data ?? 'No Data',
style: TextStyle(fontSize: 20),
);
}
}
คำอธิบาย Code:
MyHomePage: เป็น StatefulWidget ที่มี State คือ_dataซึ่งเป็น StringMyInheritedWidget: ครอบCenterWidget ไว้ ทำให้MyDataWidgetสามารถเข้าถึงข้อมูล_dataได้MyDataWidget: ใช้MyInheritedWidget.of(context)เพื่อดึงข้อมูล_dataจากMyInheritedWidget- เมื่อกดปุ่ม "Update Data" ค่า
_dataในMyHomePageจะถูกอัปเดต และMyDataWidgetจะถูก rebuild โดยอัตโนมัติ เนื่องจากupdateShouldNotifyในMyInheritedWidgetคืนค่าtrue
ข้อดีและข้อเสียของ InheritedWidget
ข้อดี:
- ลดความซับซ้อนของโค้ด โดยไม่ต้องส่งผ่านข้อมูลผ่าน Prop Drilling
- ทำให้ Widget สามารถเข้าถึงข้อมูลจาก Widget ต้นทางได้โดยตรง
- Flutter จะทำการ rebuild Widget ลูกหลานที่ใช้งานข้อมูลนั้นเมื่อข้อมูลมีการเปลี่ยนแปลงโดยอัตโนมัติ
ข้อเสีย:
- อาจทำให้เกิดความสับสนในการ debug หากไม่เข้าใจการทำงานของ
InheritedWidget - การ rebuild Widget ลูกหลานที่ไม่จำเป็นอาจทำให้เกิด performance issue ได้ หาก
updateShouldNotifyไม่ได้ถูก implement อย่างถูกต้อง
InheritedWidget กับ State Management Solutions
InheritedWidget เป็นพื้นฐานของ State Management Solutions หลายตัวใน Flutter เช่น Provider, Riverpod, และ BLoC โดย libraries เหล่านี้จะช่วยให้การจัดการ State ในแอปพลิเคชันขนาดใหญ่มีความซับซ้อนน้อยลง และมีประสิทธิภาพมากขึ้น
สรุป
InheritedWidget เป็นเครื่องมือที่มีประโยชน์ในการส่งข้อมูลลงไปใน Widget Tree โดยไม่ต้อง Prop Drilling ทำให้โค้ดมีความสะอาด อ่านง่าย และบำรุงรักษาได้ง่ายขึ้น อย่างไรก็ตาม ควรระมัดระวังในการใช้งานเพื่อให้มั่นใจว่า updateShouldNotify ถูก implement อย่างถูกต้อง และไม่ทำให้เกิด performance issue ในบทความถัดไป เราจะเรียนรู้เกี่ยวกับการจัดการ State อย่างมีประสิทธิภาพด้วย Provider ซึ่งเป็น State Management Solution ที่ได้รับความนิยมใน Flutter