InheritedWidget

本文深入探讨Flutter中的状态管理机制,特别是Widget间的状态传递与更新。通过InheritedWidget实现观察者模式,详细解析如何让Widget响应状态变化,保持视图与数据同步。

在Flutter中,对应于Widget之内状态管理,Widget之间的状态是如何管理和以及传递的?
具体例子,对于Text这样的组件,它是如何根据主题配置信息,改变其默认的字体大小,颜色等?

假设存在Widget,A,B,C,依赖关系A<B<C,A中使用StatefullWidget保存一个状态,C需依据A状态变换而变化,那么当A变化时需要重建组件树 B<C,可B并不依赖A的状态,不愿因A状态的变化而重建?解决这个问题,就需要InheritedWidget了。

理解InheritedWidget,可以套用观察者模式,InheritedWidget是一个数据源(被观察者),对应上述A,观察者对应上述C,那么问题就可以理解为这个观察模式如何实现的?可分以下三个问题?

  1. Observer如何持有Observable?即,下层Widget如何获取到InheritedWidget的引用?
  2. 如何订阅?即,事件源添加订阅者。
  3. 事件发生时,事件如何传递?即,InheritedWidget保存状态发生变化时,这个变化如何传递到依赖该状态的Widget。

下层Widget如何获取到InheritedWidget的引用?

我们知道Widget仅是一个组件描述,实际上在我们概念上组件树是由Element组成的,通常一个Widget对应着一个Element,由此我们查看Element源码

// class Element

  Map<Type, InheritedElement> _inheritedWidgets;	
  void mount(){	
      _updateInheritance();
  }
  void _updateInheritance() {	
      _inheritedWidgets = _parent?._inheritedWidgets;
  }

// class InheritedElement

  void _updateInheritance() {
    _inheritedWidgets[widget.runtimeType] = this;
  }

当Element被加载到Element树时,在mount()方法中,获取上层的_inheritedWidgets Map。 而在这个 Map中的数据,在InheritedElement中填充的,Key是InheritedWidget的runtimeType,Value则是InheritedElement本身。这样,每个Widget都可以持有着上层InheiritedWiddget的引用,也可以说观察者持有事件源引用了。

如何订阅?即,事件源添加订阅者。

看事件源的实现类,存在也一个Map _dependents,Key保存着订阅该数据源的观察者Widget,

//class InheritedElement
  final Map<Element, Object> _dependents = HashMap<Element, Object>();

而如何添加添加Widget呢?

  // class BuildContext
  //  Context注册一个InheritedElement,当其变化时,导致BuildContext重建,进而使Contex对应的Widget的重建
  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect });
 // 其实质是导致
  @protected
  void setDependencies(Element dependent, Object value) {
    _dependents[dependent] = value;
  }

简答说,就是观察者订阅某个数据源时,其本质就是将该观察者添加到上述Map中。

InheritedWidget保存状态发生变化时,这个变化如何传递到依赖该状态的Widget。

当InheritedWidget发生变化时,

  void updated(InheritedWidget oldWidget) {
    if (widget.updateShouldNotify(oldWidget))
      super.updated(oldWidget);
  }

  // 通知所有的观察者
  void notifyClients(InheritedWidget oldWidget) {
    for (final Element dependent in _dependents.keys) {
      notifyDependent(oldWidget, dependent);
    }
  }
  
	//标记该观察者Widget需要重建,在我们的观察者Widget中,重写该方法针对数据源做出处理
  void didChangeDependencies() {
    markNeedsBuild();
  }

在数据源变化时,会通知所有的观察者,观察者在didChangeDependencies()方法中可以针对信息的数据做出处理,并导致该Widdget的Build再次执行,从而完成页面与数据的同步。

在 Flutter 中,`InheritedWidget` 是一种用于在 widget 树中高效地向下传递数据的机制。它允许子 widget 直接访问祖先 widget 中的数据,而无需通过构造函数层层传递。这种方式非常适合用于状态管理,尤其是在需要在多个层级之间共享状态的情况下。 ### 基本用法 要使用 `InheritedWidget`,首先需要创建一个继承自 `InheritedWidget` 的类,并重写 `updateShouldNotify` 方法以决定是否需要通知子 widget 更新。以下是一个简单的示例: ```dart class CounterProvider extends InheritedWidget { final int count; final VoidCallback increaseCount; CounterProvider({ Key? key, required this.count, required this.increaseCount, required Widget child, }) : super(key: key, child: child); static CounterProvider? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<CounterProvider>(); } @override bool updateShouldNotify(CounterProvider oldWidget) { return count != oldWidget.count; } } ``` 在这个例子中,`CounterProvider` 是一个 `InheritedWidget`,它包含了一个计数器值 `count` 和一个增加计数的方法 `increaseCount`。通过 `of` 方法,子 widget 可以轻松地访问这个 `InheritedWidget`。 ### 在 Widget 树中使用 接下来,可以在 widget 树中使用这个 `InheritedWidget`。通常,会在应用的根部或某个特定的子树中引入它: ```dart void main() { runApp( CounterProvider( count: 0, increaseCount: () {}, child: MyApp(), ), ); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } ``` ### 访问和更新状态 子 widget 可以通过 `CounterProvider.of(context)` 来访问 `InheritedWidget` 中的状态,并调用其方法来更新状态: ```dart class MyHomePage extends StatelessWidget { final String title; const MyHomePage({Key? key, required this.title}) : super(key: key); @override Widget build(BuildContext context) { final counterProvider = CounterProvider.of(context)!; return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Text('Count: ${counterProvider.count}'), ), floatingActionButton: FloatingActionButton( onPressed: counterProvider.increaseCount, child: Icon(Icons.add), ), ); } } ``` ### 性能优化 虽然 `InheritedWidget` 提供了一种高效的机制来传递数据,但在处理大量数据或频繁更新时仍需注意性能。为了优化性能,可以在 `updateShouldNotify` 方法中返回 `false`,以避免不必要的重建。此外,如果需要更复杂的状态管理,可以考虑使用 `Provider` 或 `Riverpod` 等高级状态管理库,它们基于 `InheritedWidget` 提供了更多的功能和便利性[^3]。 ### 示例:使用 MultiProvider 如果你需要管理多个状态,可以使用 `MultiProvider` 将多个 `Provider` 组合在一起: ```dart void main() { runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => Counter()), ChangeNotifierProvider(create: (_) => AnotherModel()), ], child: MyApp(), ), ); } ``` 这种方式可以让你在不同的 widget 中访问不同的状态,而不需要将所有状态都放在一个 `InheritedWidget` 中[^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值