InheritedWidget là một widget đặc biệt trong Flutter truyền dữ liệu xuống dưới trong cây widget mà không cần truyền tường minh qua các hàm tạo. Widget con truy cập dữ liệu thông qua BuildContext và tự động đăng ký nhận cập nhật. Khi dữ liệu trong InheritedWidget thay đổi, tất cả widget phụ thuộc được xây dựng lại. Theo Flutter API Reference, 2025, InheritedWidget là nền tảng của Theme, MediaQuery, Localizations và hầu hết các thư viện quản lý trạng thái.
Những điểm chính
InheritedWidget là một widget làm cho dữ liệu của nó có sẵn cho tất cả các hậu duệ trong Widget Tree. Không giống như widget thông thường chỉ truyền dữ liệu qua hàm tạo cho các phần tử con, InheritedWidget cho phép bất kỳ widget nào trong cây con truy cập dữ liệu mà không cần chuỗi tham số. Điều này giải quyết vấn đề “prop drilling” — truyền dữ liệu qua nhiều widget trung gian không tự sử dụng dữ liệu này.
Flutter bao gồm một số InheritedWidget tích hợp: Theme (bảng màu và kiểu), MediaQuery (kích thước màn hình, hướng, mật độ điểm ảnh), Localizations (chuỗi bản địa hóa), Directionality (hướng văn bản), DefaultTextStyle (kiểu văn bản mặc định). Các widget này được thiết lập bởi widget gốc như MaterialApp và có sẵn trong toàn bộ ứng dụng.
InheritedWidget không có trạng thái riêng — nó lưu trữ dữ liệu được truyền qua hàm tạo. Khi cha của InheritedWidget được xây dựng lại với dữ liệu mới, phương thức updateShouldNotify được gọi để so sánh dữ liệu cũ và mới. Nếu phương thức trả về true, tất cả widget phụ thuộc được đánh dấu để xây dựng lại. Đây là cơ chế cập nhật phản ứng đơn giản nhưng hiệu quả.
Cơ chế truyền dữ liệu qua InheritedWidget dựa trên Element Tree. Khi một widget gọi dependOnInheritedWidgetOfExactType, phần tử tương ứng đăng ký phụ thuộc vào InheritedElement. Khi InheritedWidget thay đổi, InheritedElement thông báo cho tất cả phần tử phụ thuộc, chúng được xây dựng lại trong khung hình tiếp theo.
Phương thức dependOnInheritedWidgetOfExactType không chỉ tìm InheritedWidget trong cây — nó đăng ký phần tử hiện tại nhận thông báo. Nếu bạn sử dụng findAncestorWidgetOfExactType thay vì dependOn, widget sẽ nhận được dữ liệu nhưng sẽ không được xây dựng lại khi dữ liệu thay đổi. Đây là sự khác biệt quan trọng: dependOn là đăng ký, findAncestor là tra cứu một lần.
Khi một widget yêu cầu InheritedWidget, Flutter đi lên Element Tree từ phần tử hiện tại đến gốc, kiểm tra từng InheritedElement để tìm khớp kiểu. InheritedElement khớp đầu tiên được trả về. Điều này có nghĩa là InheritedWidget gần nhất trong cây có quyền ưu tiên — bạn có thể ghi đè dữ liệu ở một cấp cụ thể bằng cách đặt InheritedWidget gần hậu duệ hơn.
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;
}
Trong ví dụ này, MyTheme sử dụng phương thức tĩnh of để cung cấp dữ liệu cho hậu duệ. Phương thức dependOnInheritedWidgetOfExactType đăng ký phụ thuộc và updateShouldNotify so sánh dữ liệu cũ và mới để xác định xem widget phụ thuộc có cần xây dựng lại hay không.
Tạo InheritedWidget tùy chỉnh gồm hai bước: định nghĩa một lớp mở rộng InheritedWidget và triển khai phương thức tĩnh of để hậu duệ truy cập. Dữ liệu được truyền qua hàm tạo và phương thức updateShouldNotify xác định khi nào widget phụ thuộc nên được xây dựng lại.
Lớp phải mở rộng InheritedWidget và chấp nhận dữ liệu qua hàm tạo với tham số child bắt buộc. Dữ liệu có thể thuộc bất kỳ kiểu nào: nguyên thủy, đối tượng, hàm. Quy tắc chính là dữ liệu phải bất biến (immutable) để có thể so sánh giá trị cũ và mới một cách đáng tin cậy.
Phương thức tĩnh of nhận BuildContext và trả về dữ liệu InheritedWidget. Bên trong, nó gọi dependOnInheritedWidgetOfExactType, tìm InheritedWidget gần nhất thuộc kiểu đã chỉ định trong cây. Nếu không tìm thấy InheritedWidget, phương thức ném ngoại lệ hoặc trả về giá trị mặc định tùy theo triển khai.
Để truy cập dữ liệu, widget gọi MyWidget.of(context) bên trong phương thức build. Flutter tự động đăng ký widget nhận cập nhật. Nếu dữ liệu thay đổi, widget được xây dựng lại trong khung hình tiếp theo. Điều này cho phép mã sạch và khai báo mà không có tham số không cần thiết.
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;
}
Trong ví dụ này, UserPreferences lưu trữ cài đặt người dùng. Phương thức updateShouldNotify so sánh từng trường riêng lẻ, ngăn chặn việc xây dựng lại không cần thiết khi chỉ một tham số thay đổi. Sử dụng cách tiếp cận tương tự cho InheritedWidget tùy chỉnh của bạn với nhiều trường.
updateShouldNotify là phương thức chính của InheritedWidget xác định xem widget phụ thuộc có cần được thông báo về thay đổi dữ liệu hay không. Nếu phương thức trả về false, widget phụ thuộc không được xây dựng lại, ngay cả khi InheritedWidget tự nhận được phiên bản mới với cùng dữ liệu. Điều này cực kỳ quan trọng cho hiệu suất.
Chỉ so sánh những trường thực sự thay đổi và ảnh hưởng đến hiển thị. Nếu InheritedWidget chứa 10 trường nhưng chỉ một trường ảnh hưởng đến UI, chỉ kiểm tra trường đó. Với tập hợp, sử dụng so sánh sâu hoặc cấu trúc dữ liệu bất biến. Không sử dụng == cho List hoặc Map vì chúng so sánh theo tham chiếu.
Lỗi phổ biến nhất là trả về true mà không so sánh. Điều này khiến tất cả widget phụ thuộc được xây dựng lại ở mỗi lần cập nhật cha, ngay cả khi dữ liệu không thay đổi. Lỗi thứ hai là trả về false khi dữ liệu đã thay đổi, dẫn đến UI lỗi thời. Lỗi thứ ba là so sánh phức tạp chạy mỗi khung hình và làm chậm hiệu suất.
InheritedWidget và callback (truyền hàm qua hàm tạo) giải quyết các vấn đề khác nhau. InheritedWidget phù hợp cho dữ liệu cần thiết bởi nhiều widget ở các cấp khác nhau trong cây. Callback thuận tiện cho việc truyền sự kiện một chiều từ cha đến một con cụ thể hoặc ngược lại. Lựa chọn phụ thuộc vào kiến trúc ứng dụng và tần suất cập nhật.
Sử dụng InheritedWidget khi dữ liệu cần thiết cho nhiều widget ở các cấp lồng nhau khác nhau: chủ đề ứng dụng, cài đặt người dùng, thông tin thiết bị, dữ liệu phiên hiện tại. InheritedWidget đặc biệt hiệu quả cho dữ liệu “toàn cầu” hiếm khi thay đổi nhưng cần thiết ở các phần khác nhau của UI.
Callback (hàm gọi lại) phù hợp để truyền sự kiện từ widget con lên cha: nhấn nút, chọn mục danh sách, gửi biểu mẫu. Callback chỉ rõ hành động mà con có thể thực hiện và không tạo phụ thuộc ẩn. Để truyền dữ liệu xuống dưới qua một số cấp nhỏ, sử dụng tham số hàm tạo cũng đơn giản hơn.
| Tiêu chí | InheritedWidget | Callback |
|---|---|---|
| Hướng | Từ trên xuống (cha → hậu duệ) | Từ dưới lên (con → cha) hoặc trực tiếp |
| Phạm vi | Toàn bộ cây con | Widget cụ thể |
| Xây dựng lại | Tự động khi dữ liệu thay đổi | Yêu cầu setState thủ công |
| Độ phức tạp | Trung bình (cần lớp InheritedWidget) | Thấp (chỉ một hàm) |
Provider và Riverpod là các thư viện quản lý trạng thái phổ biến trong Flutter được xây dựng trên InheritedWidget. Chúng mở rộng khả năng: thêm hỗ trợ ChangeNotifier, tự động hủy khi tháo khỏi cây, khởi tạo lười biếng và cú pháp đơn giản hóa với kiểu generic.
Provider sử dụng InheritedWidget để truyền đối tượng thuộc bất kỳ kiểu nào xuống dưới trong cây. ChangeNotifierProvider theo dõi thay đổi qua ChangeNotifier và gọi updateShouldNotify khi notifyListeners được gọi. Điều này giải phóng nhà phát triển khỏi việc tạo InheritedWidget thủ công và triển khai updateShouldNotify.
InheritedWidget trực tiếp cho nhiều kiểm soát hơn và không yêu cầu phụ thuộc bên ngoài. Provider cung cấp cơ sở hạ tầng sẵn có: Consumer, Selector, MultiProvider, ProxyProvider. Lựa chọn phụ thuộc vào độ phức tạp của ứng dụng. Cho dự án đơn giản, InheritedWidget trực tiếp là đủ; cho dự án lớn, Provider hoặc Riverpod giảm mã mẫu.
// InheritedWidget trực tiếp
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;
}
// Tương đương Provider
return ChangeNotifierProvider<UserData>(
create: (_) => UserData(),
child: MyApp(),
);
Cả hai cách tiếp cận trong ví dụ giải quyết cùng một vấn đề — truyền UserData xuống dưới trong cây. Provider giảm khối lượng mã nhưng ẩn cơ chế InheritedWidget. InheritedWidget trực tiếp cho kiểm soát hoàn toàn và hiểu biết về những gì đang xảy ra, điều đặc biệt quan trọng khi học Flutter và gỡ lỗi các vấn đề xây dựng lại phức tạp.
Câu hỏi thường gặp
InheritedWidget làm cho dữ liệu có sẵn cho tất cả hậu duệ qua BuildContext, trong khi widget thông thường chỉ truyền dữ liệu qua hàm tạo. InheritedWidget cũng đăng ký hậu duệ nhận cập nhật dữ liệu.
Widget phụ thuộc chỉ được xây dựng lại khi updateShouldNotify trả về true. Nếu phương thức được triển khai đúng, việc xây dựng lại chỉ xảy ra khi dữ liệu thực sự thay đổi, không phải ở mỗi lần xây dựng lại cha.
Có, bạn có thể sử dụng bất kỳ số lượng InheritedWidget trong cùng một cây. Mỗi widget cung cấp dữ liệu thuộc một kiểu cụ thể và widget có thể lấy dữ liệu từ nhiều InheritedWidget cùng lúc.
dependOn đăng ký widget nhận cập nhật — khi dữ liệu thay đổi, widget được xây dựng lại. findAncestor thực hiện tra cứu một lần mà không đăng ký và widget sẽ không biết về thay đổi dữ liệu.
Cho trạng thái đơn giản (chủ đề, cài đặt) InheritedWidget là đủ. Cho trạng thái phức tạp với logic nghiệp vụ, sử dụng Provider, Riverpod hoặc BLoC — chúng được xây dựng trên InheritedWidget và thêm cơ sở hạ tầng cần thiết.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm