Flutter 状态管理scoped model源码解读

目录
  • 一、什么是 scoped_model
  • 二、用法
  • 三、实现原理
  • 四、结束

一、什么是 scoped_model

本文主要从 scoped_model 的简单使用说起,然后再深入源码进行剖析(InheritedWidget、Listenable、AnimatedBuilder),不会探讨 Flutter 状态管理的优劣,单纯为了学习作者的设计思想。

scoped_model 是一个第三方 Dart 库,可以让您轻松的将数据模型从父 Widget 传递到子 Widget。此外,它还会在模型更新时重新构建所有使用该模型的子 Widget。

它直接来自于 Google 正在开发的新系统 Fuchsia 核心 Widgets 中对 Model 类的简单提取,作为独立使用的独立 Flutter 插件发布。

二、用法

class CounterModel extends Model {
  int _counter = 0;
  int get counter => _counter;
  static CounterModel of(BuildContext context) =>
      ScopedModel.of<CounterModel>(context, rebuildOnChange: true);
  void increment() {
    _counter++;
    notifyListeners();
  }
}
class ScopedModelDemoPage extends StatelessWidget {
  const ScopedModelDemoPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('scopedModel'),
        centerTitle: true,
      ),
      body: ScopedModel(
        model: CounterModel(),
        child: ScopedModelDescendant<CounterModel>(
          builder: (context, child, model) => Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('${model.counter}'),
                OutlinedButton(
                  onPressed: ScopedModel.of<CounterModel>(context).increment,
                  // onPressed: model.increment,
                  child: Text('add'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

从上面的代码可以看出 scoped_model 的使用非常简单,只需要以下三步:

  • 定义 Model 的实现,如 CounterModel,这里需要注意的是 CounterModel 一定要继承自 Model(为什么一定要继承 Model 我们后面细说)并且在状态改变的时候执行 notifyListeners。
  • 使用 ScopedModel Widget 包裹需要用到 Model 的 Widget。
  • 使用 ScopedModelDescendant 或者 ScopedModel.of<CounterModel>(context) 来进行获取数据。

三、实现原理

在 scoped_model 中的整个实现中,它很巧妙的借助了 AnimatedBuilder、Listenable、InheritedWidget 等 Flutter 的基础特性。

scoped_model 使用了观察者模式,将数据放在父 Widget,子 Widget 通过找到父 Widget 的 model 进行数据渲染,最后改变数据的时候再将数据传回,父 Widget 再通知所有用到了该 model 的子 Widget 去更新状态。

我们首先从 ScopedModel 入手,通过源码我们不难发现,ScopedModel 是一个 StatelessWidget 最后返回一个 AnimatedBuilder,在 AnimatedBuilder 中在通过 builder 返回 _InheritedModel

我们再从 Model 入手,可以看出 Model 是一个 继承自 Listenable 的抽象类,主要有一个 _listeners 变量用 Set 来进行存储,复写了 addListenerremoveListenernotifyListeners 方法。在这里不知道大家有没有想过 Model 为什么要继承 Listenable? 在这里先卖个关子,在后面会详细讲解。

如果只是单单看 ScopedModelModel 好像也看不出来什么巧妙之处,但是如果把 ScopedModel 中返回的 AnimatedBuilderModel 所继承的 Listenable 结合起来进行思考就会发现,AnimatedBuilder 继承自 AnimatedWidget,在 AnimatedWidget 的生命周期中会对 Listenable 添加监听,而 Model 正好就实现了 Listenable 接口。

Model 实现了 Listenable 接口,内部刚好有一个 Set<VoidCallback> _listeners 用来保存接收者。当 Model 赋值给 AnimatedBuilder 中的 animation 时,Listenable 的 addListener 就会被调用,然后添加一个 _handleChange 方法,_handleChange 内部只有一行代码 setState((){}),当调用 notifyListeners 时,会从创建一个 Microtask,去执行一遍 _listeners 中的 _handleChange ,当 _handleChange 被调用时就会进行更新 UI 界面。其实这里也就解释了 Model 为什么要继承 Listenable

不知道大家发现没有,讲了这么多,还没有讲到 ScopedModelDescendant 到底是干什么的?那我就不得不先说起 InheritedWidget 了。

InheritedWidget 是 Flutter 中非常重要的一个功能型组件,它提供了一种在 Widget 树中从上到下共享数据的方式,比如我们在应用的根 Widget 中通过 InheritedWidget 共享了一个数据,那么我们便可以在任意子 Widget 中来获取该共享的数据。它主要有以下两个作用:

  • 子 Widget 可以通过 Inherited Widgets 提供的静态 of 方法拿到离他最近的父Inherited widgets 实例。
  • Inherited Widgets 改变 state 之后,会自动触发 state 消费者的 rebuild 行为。

scoped_model 中我们可以通过 ScopedModel.of<CountModel>(context) 来获取 我们的 model,最主要的就是在 ScopedModel 中返回了 AnimatedBuilder,而 AnimatedBuilder 中 builder 又返回了 _InheritedModel_InheritedModel 又继承了 InheritedWidget

言归正传,我们一起回到 ScopedModelDescendant 的主题,不知道大家有没有尝试过,不用 ScopedModelDescendant 来获取 model 会发生什么样的情况?通过窥探源码我们发现有 ScopedModelError 这样一个异常类,说的已经很明确了,必须要提供 ScopedModelDescendant。what ?其实它主要做了以下 2 件事情:

  • 隐式调用 ScopedModel.of<T>(context) 来获取 model。
  • 明确语义化,不然我们每次都需要用 Builder 来进行构建,不然将获取不到 model,还会抛出异常。

// 不使用 ScopedModelDescendant 使用 Builder 的用法
class ScopedModelDemoPage extends StatelessWidget {
  const ScopedModelDemoPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('scopedModel'),
        centerTitle: true,
      ),
      body: ScopedModel(
        model: CounterModel(),
        child: Builder(
          builder: (context) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('${CounterModel.of(context).counter}'),
                OutlinedButton(
                  onPressed: CounterModel.of(context).increment,
                  child: Text('add1'),
                ),
              ],
            );
          },
        ),
        // child: ScopedModelDescendant<CounterModel>(
        //   builder: (context, child, model) => Center(
        //     child: Column(
        //       mainAxisAlignment: MainAxisAlignment.center,
        //       children: [
        //         Text('${model.counter}'),
        //         OutlinedButton(
        //           onPressed: ScopedModel.of<CounterModel>(context).increment,
        //           // onPressed: model.increment,
        //           child: Text('add'),
        //         ),
        //       ],
        //     ),
        //   ),
        // ),
      ),
    );
  }
}

除了上面这种使用 Builder 的方式,当然我们还可以使用下面的方法,把它单独提取出一个 Widget,代码如下:

// 单独提取 Widget 的方式
class ScopedModelDemoPage extends StatelessWidget {
  const ScopedModelDemoPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('scopedModel'),
        centerTitle: true,
      ),
      body: ScopedModel<CounterModel>(
        model: CounterModel(),
        // 不使用 ScopedModelDescendant 的用法
        // child: Builder(
        //   builder: (context) {
        //     return Column(
        //       mainAxisAlignment: MainAxisAlignment.center,
        //       children: [
        //         Text('${CounterModel.of(context).counter}'),
        //         OutlinedButton(
        //           onPressed: CounterModel.of(context).increment,
        //           child: Text('add1'),
        //         ),
        //       ],
        //     );
        //   },
        // ),
        child: NewWidget(),
      ),
    );
  }
}
class NewWidget extends StatelessWidget {
  const NewWidget({
    Key? key,
  }) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('${CounterModel.of(context).counter}'),
          OutlinedButton(
            onPressed: CounterModel.of(context).increment,
            // onPressed: model.increment,
            child: Text('add'),
          ),
        ],
      ),
    );
  }
}
class CounterModel extends Model {
  int _counter = 0;
  int get counter => _counter;
  static CounterModel of(BuildContext context) =>
      ScopedModel.of<CounterModel>(context, rebuildOnChange: true);
  void increment() {
    _counter++;
    notifyListeners();
  }
}

是不是很意外?主要起作用的是下面这一段代码:

单独提取出来 Widget,可以获取到正确的 context,从而可以获取到离他最近的父 Inherited widgets 实例。

四、结束

以上就是我对 scoped_model 的使用以及部分源码的解读,如果有不足之处,还请指教。 最后,大家也可以思考一下,我们是如何通过 context 就能获取到共享的 Model 呢?

以上就是Flutter 状态管理scoped model源码解读的详细内容,更多关于Flutter状态管理 scoped model的资料请关注我们其它相关文章!

(0)

相关推荐

  • flutter自定义InheritedProvider实现状态管理详解

    目录 InheritedWidget简单数据驱动模型 1. 数据存储 2. 变更通知 3. 使用方法 InheritedWidget简单数据驱动模型 基于InheritedWidget,实现简单的数据驱动模型,模型结构如下: 1. 数据存储 使用 InheritedWidget,新建 InheritedProvider import 'package:flutter/material.dart'; class InheritedProvider<T> extends InheritedWidg

  • Flutter状态管理Provider的使用示例详解

    目录 前言 计数器 全局状态 总结 前言 Provider是三大主流状态管理框架官方推荐使用的框架,它是基于官方数据共享组件InheritedWidget实现的,通过数据改变调用生命周期中的didChangeDependencies()方法,来实现状态的通知改变. InheritedWidget的使用可以参考我之前的这篇Flutter中几种数据传递的应用总结. 计数器 还是以计数器为例,这次通过Provider实现,provider相较于bloc并没有那么强制性分层,所以这里我们自己分为数据层(

  • flutter状态管理Provider的使用学习

    目录 1. provider的使用 2. 控制Widget的刷新颗粒度 3. 小结 本文主要介绍flutter中状态管理组件provider,provider: ^6.0.3主要是用于我们系统InheritedWidge的封装,用于数据的传递和管理. 1. provider的使用 网上也有很多关于provider说明,也可以看下官方的provider的 README.这里我记录一下我自己学习.我们对于简单的数据共享可以设置参数,之后子页面进行数据方法回调,从而完成数据间的通信.但是比较麻烦,下面

  • Flutter 包管理器和资源管理使用学习

    目录 什么叫包管理器 Flutter 包管理器 Pub仓库 资源管理 配置图片资源 配置全局字体资源 结束语 什么叫包管理器 包管理器就是用来管理程序运行依赖的一个配置应用.在程序运行中,我们会用到各种各样的第三方程序包,若我们手动管理这些程序,他将变得十分臃肿.这时候便诞生了包管理器,类似于手机中的AppStore.比如说我们手机中的某个程序更新了,我们就可以通过AppStore来更新.同样的我们可以通过包管理来更新我们程序中用到的依赖包. Flutter 包管理器 Flutter包管理器是Y

  • Flutter状态管理Bloc使用示例详解

    目录 前言 两种使用模式 Cubit模式 最后 前言 目前Flutter三大主流状态管理框架分别是provider.flutter_bloc.getx,三大状态管理框架各有优劣,本篇文章将介绍其中的flutter_bloc框架的使用,他是bloc设计思想模式在flutter上的实现,bloc全程全称 business logic ,业务逻辑的意思,核心思想就是最大程度的将页面ui与数据逻辑的解耦,使我们的项目可读性.可维护性.健壮性增强. 两种使用模式 首先第一步引入插件: flutter_bl

  • 详解Flutter 响应式状态管理框架GetX

    目录 一.状态管理框架对比 Provider BLoC GetX 二.基本使用 2.1 安装与引用 2.2 使用GetX改造Counter App 2.3 GetX代码插件 三.其他功能 3.1 路由管理 3.2 依赖关系管理 3.3 工具 3.4 改变主题 3.5 GetConnect 3.6 GetPage中间件 Priority Redirect onPageCalled OnBindingsStart OnPageBuildStart 3.7 全局设置和手动配置 3.8 StateMix

  • 封装flutter状态管理工具示例详解

    目录 引言 RxBinder 代码实现 Demo 完美运行 引言 关于 Flutter 状态管理,公司项目使用的是Bloc方案.Bloc 其实本质上是 provider 的封装扩展库,整体通过 InheritedWidget .Notifier 外加 Stream中转实现状态变更通知. 关于 Bloc 实现原理,有兴趣的同学可以观看这篇文章 Bloc原理解析 RxBinder 撇开Bloc内部实现策略,小轰尝试基于数据驱动模型,自定义一套状态管理工具.构思如下: 主要成员如下: RxBinder

  • Flutter状态管理Provider示例解析

    目录 什么是状态管理 常见的状态管理框架有哪些 Provider Redux GetX Provider 使用 添加依赖 导入应用 定义需要共享的数据 在应用程序入口初始化 使用共享数据 状态管理的好处 结束语 什么是状态管理 状态管理是一个十分广泛的概念,因为状态无处不在.从广义角度讲状态管理就是页面跟代码.跟服务器进行数据同步.例如:你在某购物应用内,添加了一件商品,你需要在购物车页面进行展示.这就是一种状态.相反的,你在商品页面点击了添加到购物车,却在购物车页面没有发现或者过段时间它才展示

  • 深入理解Java线程池从设计思想到源码解读

    线程池:从设计思想到源码解析 前言初识线程池线程池优势线程池设计思路 深入线程池构造方法任务队列拒绝策略线程池状态初始化&容量调整&关闭 使用线程池ThreadPoolExecutorExecutors封装线程池 解读线程池execute()addWorker()Worker类runWorker()processWorkerExit() 前言 各位小伙伴儿,春节已经结束了,在此献上一篇肝了一个春节假期的迟来的拜年之作,希望读者朋友们都能有收获. 根据穆氏哲学,投入越多,收获越大.我作此文时

  • spring事务之事务挂起和事务恢复源码解读

    目录 事务挂起和事务恢复源码解读 事务挂起源码 suspend(transaction) newTransactionStatus() doBegin() 事务恢复 所以 事务挂起和事务恢复源码解读 在学习spring事务的时候,一定会涉及到一个概念,无法避免的,就是事务挂起和事务恢复 对于事务挂起和事务恢复,可以简单的描述一下,是这样的 1.首先我们假设有两个类,A类和B类,两个类中的字段是一模一样的,A类表示当前事务,B类表示备份事务 2.如果我开启一个事务,会把当前事务信息,存入到A类中,

  • Ajax::prototype 源码解读

    AJAX之旅(1):由prototype_1.3.1进入javascript殿堂-类的初探  还是决定冠上ajax的头衔,毕竟很多人会用这个关键词搜索.虽然我认为这只是个炒作的概念,不过不得不承认ajax叫起来要方便多了.所以ajax的意思我就不详细解释了. 写这个教程的起因很简单:经过一段时间的ajax学习,有一些体会,并且越发认识到ajax技术的强大,所以决定记录下来,顺便也是对自己思路的整理.有关这个教程的后续,请关注http://www.x2design.net 前几年,javascri

  • Bootstrap源码解读排版(1)

    源码解读Bootstrap排版 粗体 可以使用<b>和<strong>标签让文本直接加粗. 例如: <p>我在学习<strong>Bootstrap</strong></p> 源码 b, strong { font-weight: bold; } 斜体 使用标签<em>或<i>来实现. 例如: <p>我在学<i>Bootstrap</i>.</p> 强调相关的类

  • Bootstrap源码解读下拉菜单(4)

    源码解读Bootstrap下拉菜单 基本用法 在使用Bootstrap框架的下拉菜单时,必须调用Bootstrap框架提供的bootstrap.js文件.因为Bootstrap的组件交互效果都是依赖于jQuery库写的插件,所以在使用bootstrap.min.js之前一定要先加载jquery.min.js才会生效果. 使用方法如下: 1. 使用一个名为"dropdown"的容器包裹了整个下拉菜单元素:<div class="dropdown"><

  • Bootstrap源码解读表单(2)

    源码解读Bootstrap表单 基础表单 对于基础表单,Bootstrap并未对其做太多的定制性效果设计,仅仅对表单内的fieldset.legend.label标签进行了定制.主要将这些元素的margin.padding和border等进行了细化设置. 这些元素如果使用了类名"form-control",将会实现一些设计上的定制效果. 1. 宽度变成了100% 2. 设置了一个浅灰色(#ccc)的边框 3. 具有4px的圆角 4. 设置阴影效果,并且元素得到焦点之时,阴影和边框效果会

  • jQuery源码解读之removeAttr()方法分析

    本文较为详细的分析了jQuery源码解读之removeAttr()方法.分享给大家供大家参考.具体分析如下: 扩展jQuery原型对象的方法: 复制代码 代码如下: jQuery.fn.extend({ //name,传入要DOM元素要移除的属性名.     removeAttr: function( name ) { //使用jQuery.fn对象,即jQuery原型对象的each方法遍历当前选择器选择的jQuery对象数组,并返回该jQuery对象以便链式调用.         return

  • 关于PyTorch源码解读之torchvision.models

    PyTorch框架中有一个非常重要且好用的包:torchvision,该包主要由3个子包组成,分别是:torchvision.datasets.torchvision.models.torchvision.transforms. 这3个子包的具体介绍可以参考官网: http://pytorch.org/docs/master/torchvision/index.html. 具体代码可以参考github: https://github.com/pytorch/vision/tree/master/

  • 详解webpack-dev-middleware 源码解读

    前言 Webpack 的使用目前已经是前端开发工程师必备技能之一.若是想在本地环境启动一个开发服务,大家只需在 Webpack 的配置中,增加 devServer的配置来启动.devServer 配置的本质是 webpack-dev-server 这个包提供的功能,而 webpack-dev-middleware 则是这个包的底层依赖. 截至本文发表前,webpack-dev-middleware 的最新版本为 webpack-dev-middleware@3.7.2,本文的源码来自于此版本.本

随机推荐