Loading...
墨滴

岛上码农@公众号同名

2021/11/02  阅读:26  主题:橙心

从InheritedWidget深入了解 Flutter 状态管理机制(下)

本文翻译自 Flutter 官方推荐的文章:Managing Flutter Application State With InheritedWidgets。通过官网文档或推荐文章,能够让我们更好地了解 Flutter 的状态管理机制。

前言

上篇介绍了使用自定义InheritedWidget子类 ModelBinding实现子组件直接访问状态,从而达到无需沿着组件树层层传递的目的,减少了父子组件间的耦合。但是也存在两个问题:

  • ModelBinding 类不通用,每个页面都需要自己定义一个 InheritedWidget 子类。
  • 状态改变的回调函数还是需要从顶层传递到具体操作状态的组件,耦合没有完全解除。

本篇将对上一个 ModelBinding 类进行改造,实现一个更通用,耦合度更低的 ModelBinding 类。

解耦状态更新回调

InheritedWidget 方式的版本将组件和 model 进行绑定后简化了模型的更新。现在,任何 ModelBinding 的下级组件都可以获取这个模型并更新它,因此也就无需通过回调来处理了。 如前所述,通过 ViewModel.of(context)ModelBinding 的下级可以获取模型的值,从而使得依赖模型的组件可以自动跟随模型的变更进行更新。同样的,ModelBinding 的子组件也可以通过ViewModel.update(context, newModel)这个方式来更新模型数据。看起来不错哦!

为了支持使用静态的 ViewModel.update 方法来更新模型,我们需要引入额外一个额外的有状态组件。这会稍微有点复杂。

  • ModelBinding 变成了一个有状态组件,并且持有当前状态模型对象。
  • ModelBinding 构建了一个_ModelBindingScopeInheritedWidget 子组件,该子组件引用了 State<ModelBinding>——即_ModelBingingState,其实就相当于是引用了父级的状态。
  • 为了改变 ModelBinding 当前模型的值,从而在调用 setState 方法重建 ModelBinding,并重建下级的_ModelBindingScope
  • 通过_ModelBindingScope 来实现ViewModel类的静态获取模型对象及更新对象

代码如下所示:

static ViewModel of(BuildContext context) {
  _ModelBindingScope scope =
      context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
  return scope.modelBindingState.currentModel;
}

static void update(BuildContext context, ViewModel newModel) {
  _ModelBindingScope scope =
      context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
  scope.modelBindingState.updateModel(newModel);
}

现在,任何 ModelBinding 的子组件都可以使用这些方法来更新模型数据了,下面的按钮代码就同时获取和更新了模型的数据。

Widget build(BuildContext context) {
  return ElevatedButton(
    child: Text('Hello World ${ViewModel.of(context).value}'),
    onPressed: () {
      ViewModel model = ViewModel.of(context);
      ViewModel.update(context, ViewModel(value: model.value + 1));
    },
  );
}

运行一下,一切正常(完整代码:model_binding_v1.dart),是不是该庆祝一下?然而,如果我们有技术上百个状态的话我们要写几十上百个 ModelBinding 类,而且每个 Model 都要提供一个静态的 of(context)update 方法?

使用泛型构建通用的 ModelBinding 类

我们要保证 ModelBinding的通用性,那就需要使用泛型来动态绑定状态模型对象。首先从最底层改起,先修改_ModelBIndingScope类:

class _ModelBindingScope<Textends InheritedWidget {
  _ModelBindingScope({
    Key key,
    @required this.modelBindingState,
    Widget child,
  })  : assert(modelBindingState != null),
        super(key: key, child: child);

  final _ModelBindingV2State<T> modelBindingState;

  @override
  bool updateShouldNotify(_ModelBindingScope oldWidget) => true;
}

改起来很简单,只需要加上泛型参数就好了。接下来是_ModelBindV2State,这个类也许改成泛型。

class _ModelBindingV2State<Textends State<ModelBindingV2<T>> {
  T currentModel;
  @override
  void initState() {
    super.initState();
    currentModel = widget.create();
  }

  void updateModel(T newModel) {
    if (currentModel != newModel) {
      setState(() {
        currentModel = newModel;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return _ModelBindingScope<T>(
      modelBindingState: this,
      child: widget.child,
    );
  }
}

实际上,也只是增加了泛型参数,这里有个地方需要注意的是,之前我们是在_ModelBidingV2State 中直接构建初始状态了,现在由于是泛型,我们没法直接构建泛型对象,因此需要从有状态组件中获取。这里我们在 initState 中调用了ModeBinding 类的create 方法返回一个泛型对象(当然,也可以直接使用赋值,取决于 ModelBinding 类如何获取初始状态对象)。

接下来是对 ModelBinding 类进行改造,这里一个是需要传递一个获取泛型对象的方法 create 给构造函数,另外就是将之前放在 ViewModel的获取模型对象的方法和更新方法提升到 ModelBinding 类来,并且变成泛型方法,从而对外只需要 ModelBinding 类就可以完成状态模型对象的获取和更新,最大程度简化状态模型对象的实现。

class ModelBindingV2<Textends StatefulWidget {
  ModelBindingV2({Key key, @required this.create, this.child})
      : assert(create != null),
        super(key: key);
  final ValueGetter<T> create;
  final Widget child;

  @override
  _ModelBindingV2State<T> createState() => _ModelBindingV2State<T>();

  static T of<T>(BuildContext context) {
    _ModelBindingScope<T> scope =
        context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
    return scope.modelBindingState.currentModel;
  }

  static void update<T>(BuildContext context, T newModel) {
    _ModelBindingScope<T> scope =
        context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
    scope.modelBindingState.updateModel(newModel);
  }
}

改造完成之后,我们的实际 Controller 的代码就变成下面这样了:

class StateViewControllerV2 extends StatelessWidget {
  StateViewControllerV2({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('模型绑定泛型版'),
      ),
      body: Center(
        child: ModelBindingV2(
          create: () => ViewModel(),
          child: ViewController(),
        ),
      ),
    );
  }
}

class ViewController extends StatelessWidget {
  const ViewController({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      child: Text('Hello World ${ModelBindingV2.of<ViewModel>(context).value}'),
      onPressed: () {
        ViewModel model = ModelBindingV2.of<ViewModel>(context);
        ModelBindingV2.update(context, ViewModel(value: model.value + 1));
      },
    );
  }
}

可以看到,整个 ModelBinding 类完成了状态的获取和更新,而且适用于任何状态模型类。

总结

本篇代码已经上传至:状态管理示例。从上一篇和本篇来看,状态管理的核心组件其实是 InheritedWidget。借助 InheritedWidget 能够在其状态发生改变后,将依赖于该组件状态的全部下级组件进行更新。而通过泛型的使用,我们构建了一个最简单的通用状态管理组件。当然,实际应用中的状态管理远比这复杂,但是明白了其中的原理,对我们优化性能会有更大的帮助。 关注岛上码农

岛上码农@公众号同名

2021/11/02  阅读:26  主题:橙心

作者介绍

岛上码农@公众号同名