热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

FlutterScoped_Model浅析

在前端开发中,我们经常能听到redux等状态管理的词汇。但是对于我这种搞移动端出身的人,对这些词汇就不是很熟悉。Flutter作为借鉴了很多React思

在前端开发中,我们经常能听到 redux 等状态管理的词汇。

但是对于我这种搞移动端出身的人,对这些词汇就不是很熟悉。

Flutter 作为借鉴了很多 React 思想的语言,自然也会有相对应的状态管理。

那什么是状态管理?为什么需要状态管理?

什么是状态管理?

个人认为 状态管理解决的是组件之间的通讯以及状态集中管理和分发的问题

举个例子:

比如我多个页面同时使用了 User 对象,当我其中一个地方改了以后,想要其他的地方也都要更改,那这个时候就需要状态管理来集中管理数据。

为什么需要状态管理?

前面已经说过一点,另一点:

我们已经使用过 StatefulWidget,也知道它维护了一个 State,也就是当前 Widget的状态。

当我们需要改变 Widget 的状态的时候,就需要 setState(),这样就会重新走一遍 build 方法来重绘。

当页面简单的时候还好说,如果页面复杂了,我们每次点击、或者滑动都要来进行整个页面的 build 吗?

很明显,这样不符合常理。

相信很多人已经听过 provide redux... 等等状态管理的方案,

那么 Scoped_Model 是什么?

Scoped_Model

先看一下Scoped_Model GitHub 文档上的内容:

A set of utilities that allow you to easily pass a data Model from a parent Widget down to it's descendants. In addition, it also rebuilds all of the children that use the model when the model is updated. This library was originally extracted from the Fuchsia codebase.

一组实用程序,允许您轻松地将数据模型从父窗口小部件传递给它的后代。此外,它还重建了模型更新时使用模型的所有子代。这个库最初是从 Fuchsia 基代码中提取的。

和其他的状态管理一样,它也是使用的 InheritedWidget, 利用 InheritedWidget 来管理使用了该数据的Widget。

这样就可以在数据改变的时候更新该 Widget 了。

简单使用 Scoped_Model

来看一下官方给出的Demo:

import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
void main() { runApp(MyApp( model: CounterModel(), ));
}
class MyApp extends StatelessWidget { final CounterModel model; const MyApp({Key key, @required this.model}) : super(key: key); @override Widget build(BuildContext context) { // At the top level of our app, we'll, create a ScopedModel Widget. This // will provide the CounterModel to all children in the app that request it // using a ScopedModelDescendant. return ScopedModel( model: model, child: MaterialApp( title: 'Scoped Model Demo', home: CounterHome('Scoped Model Demo'), ), ); }
}
// Start by creating a class that has a counter and a method to increment it.
//
// Note: It must extend from Model.
class CounterModel extends Model { int _counter = 0; int get counter => _counter; void increment() { // First, increment the counter _counter++; // Then notify all the listeners. notifyListeners(); }
}
class CounterHome extends StatelessWidget { final String title; CounterHome(this.title); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('You have pushed the button this many times:'), // Create a ScopedModelDescendant. This widget will get the // CounterModel from the nearest parent ScopedModel. // It will hand that CounterModel to our builder method, and // rebuild any time the CounterModel changes (i.e. after we // `notifyListeners` in the Model). ScopedModelDescendant( builder: (context, child, model) { return Text( model.counter.toString(), style: Theme.of(context).textTheme.display1, ); }, ), ], ), ), // Use the ScopedModelDescendant again in order to use the increment // method from the CounterModel floatingActionButton: ScopedModelDescendant( builder: (context, child, model) { return FloatingActionButton( onPressed: model.increment, tooltip: 'Increment', child: Icon(Icons.add), ); }, ), ); }
}

代码有点长,但是没关系,大部分都是注释,不管那么多,

我们直接copy代码到项目中,运行看一下效果:

640?wx_fmt=gif

效果非常简单,和我们刚开始学Flutter一样的例子。

下面就解释一下代码,

可以看到,首先是把 ScopedModel 放在了APP 最顶部来初始化:

class MyApp extends StatelessWidget { final CounterModel model; const MyApp({Key key, @required this.model}) : super(key: key); @override Widget build(BuildContext context) { // At the top level of our app, we'll, create a ScopedModel Widget. This // will provide the CounterModel to all children in the app that request it // using a ScopedModelDescendant. return ScopedModel( model: model, child: MaterialApp( title: 'Scoped Model Demo', home: CounterHome('Scoped Model Demo'), ), ); }
}


随后定义了一个 CounterModel:

// Start by creating a class that has a counter and a method to increment it.
//
// Note: It must extend from Model.
class CounterModel extends Model { int _counter = 0; int get counter => _counter; void increment() { // First, increment the counter _counter++; // Then notify all the listeners. notifyListeners(); }
}

注释上面写的很清楚,必须继承自 Model


为什么?我们看Model源码:

abstract class Model extends Listenable { final Set _listeners = Set(); int _version = 0; int _microtaskVersion = 0; /// [listener] 将在Model更改时调用。 @override void addListener(VoidCallback listener) { _listeners.add(listener); } /// [listener] 移除时调用。 @override void removeListener(VoidCallback listener) { _listeners.remove(listener); } /// Returns the number of listeners listening to this model. int get listenerCount => _listeners.length; /// 仅当Model已更改时由[model]调用。 @protected void notifyListeners() { // 我们安排一个微任务来消除可能同时发生的多个更改。 if (_microtaskVersion == _version) { _microtaskVersion++; scheduleMicrotask(() { _version++; _microtaskVersion = _version; // Convert the Set to a List before executing each listener. This // prevents errors that can arise if a listener removes itself during // invocation! _listeners.toList().forEach((VoidCallback listener) => listener()); }); } }
}

可以看到,Model 继承了 Listenable,所以我们在自己定义 Model 的时候才可以调用 notifyListeners()方法。

最后在需要该 Model的地方使用 ScopedModelDescendant 来获取。

ScopedModelDescendant( builder: (context, child, model) { return Text( model.counter.toString(), style: Theme.of(context).textTheme.display1, ); },
),

有人可能觉得这种方式不是很优雅,代码太多。

官方也提供了另一种方法&#xff1a; ScopedModel.of<CounterModel>(context)

状态的集中管理以及 Widget更新

官方示例只是提供一个简单的例子&#xff0c;并不能展现出它的威力&#xff0c;

所以我们自己写一个示例。

该示例在多个页面同时使用同一个数据&#xff0c;然后在其中一个页面更新数据。

这样就达到了我们所谓状态的集中管理。

效果如下&#xff1a;

640?wx_fmt&#61;gif

主要代码如下&#xff1a;

// 点击事件
&#64;override
Widget build(BuildContext context) { return FloatingActionButton( onPressed: ScopedModel.of(context).increment, tooltip: &#39;Increment&#39;, child: const Icon(Icons.add), );
}
// 接收事件
class CounterLabel extends StatelessWidget { const CounterLabel({Key key}) : super(key: key); &#64;override Widget build(BuildContext context) { print("third counter label build"); final counter &#61; ScopedModel.of(context, rebuildOnChange: true); return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( &#39;You have pushed the button this many times:&#39;, ), Text( &#39;${counter.count}&#39;, style: Theme.of(context).textTheme.display1, ), ], ); }
}

可以看到我们的 Widget 都是无状态的&#xff0c;也就是说我们确实达到了数据更新就更新UI的要求。

那么我们再打印log 看一下&#xff0c;是否只是更新了 使用该 Model 的 Widget。

还是整个Page 都 build 了。

我们在 Page 的 build方法中打印&#xff1a;

class MyHomePage extends StatelessWidget { const MyHomePage({Key key}) : super(key: key); &#64;override Widget build(BuildContext context) { print("home page build"); }
}
........ // 第二第三页同理
print("second home page build");
print("third counter label build");

然后在 CounterLabel 中 打印

class CounterLabel extends StatelessWidget { const CounterLabel({Key key}) : super(key: key); &#64;override Widget build(BuildContext context) { print("home counter label build"); }
}
........ // 第二第三页同理
print("second counter label build");
print("third counter label build");

运行效果如下&#xff1a;

640?wx_fmt&#61;gif

可以看到&#xff0c;确实只更新了使用该 Model 的 Widget。

总结

在Flutter 中状态管理有很多&#xff0c;redux、fish_redux 等等等等。

而Scoped_Model 是我用过最简单&#xff0c;最舒服的一种。

因为我是搞移动开发的&#xff0c;所以我会选择 Scoped_Model。

下一篇简单讲讲 Scoped_Model 的原理。

完整代码已经传至GitHub&#xff1a;https://github.com/wanglu1209/WFlutterDemo

640?wx_fmt&#61;jpeg




推荐阅读
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • CentOS7.8下编译muduo库找不到Boost库报错的解决方法
    本文介绍了在CentOS7.8下编译muduo库时出现找不到Boost库报错的问题,并提供了解决方法。文章详细介绍了从Github上下载muduo和muduo-tutorial源代码的步骤,并指导如何编译muduo库。最后,作者提供了陈硕老师的Github链接和muduo库的简介。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • 本文讨论了将HashRouter改为Router后,页面全部变为空白页且没有报错的问题。作者提到了在实际部署中需要在服务端进行配置以避免刷新404的问题,并分享了route/index.js中hash模式的配置。文章还提到了在vueJs项目中遇到过类似的问题。 ... [详细]
  • 第一步:PyQt4Designer设计程序界面该部分设计类同VisvalStudio内的设计,改下各部件的objectName!设计 ... [详细]
  • [翻译]PyCairo指南裁剪和masking
    裁剪和masking在PyCairo指南的这个部分,我么将讨论裁剪和masking操作。裁剪裁剪就是将图形的绘制限定在一定的区域内。这样做有一些效率的因素࿰ ... [详细]
  • 使用Flutternewintegration_test进行示例集成测试?回答首先在dev下的p ... [详细]
  • fileuploadJS@sectionscripts{<scriptsrc~Contentjsfileuploadvendorjquery.ui.widget.js ... [详细]
  • React提供三种方式创建Refs:字符串Refs(将被废弃)回调函数RefsReact.createRef(从React16.3开始)第一种方式不推荐使用,原因在此,并且可能会在之后的版本移除。classMyComponentextendsReact.Component{constructor(props){sup ... [详细]
  • java unhandled,Eclipse编辑java文件报Unhandled event loop exception错误的解
    本人Eclipse版本是”eclipse-jee-kepler-SR2-win32-x86_64“昨天因为换电脑,所以重装了一下软件,装好eclipse ... [详细]
author-avatar
zf19920222
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有