为了账号安全,请及时绑定邮箱和手机立即绑定

Flutter ScopedModel源码浅析

标签:
CSS3

上一篇文章简单了解了一下 ScopedModel 的用法,

这一篇文章我们来深入 ScopedModel 源码来看一下它的原理。

其实ScopedModel 只有一个文件,我们直接打开 scoped_model.dart 文件,从上往下看。

Model

首先在最上面的是 Model ,看一下源码:

abstract class Model extends Listenable{
  
final
 
Set
<
VoidCallback
>
 _listeners 
=
 
Set
<
VoidCallback
>();
  
int
 _version 
=
 
0
;
  
int
 _microtaskVersion 
=
 
0
;

  
/// [listener] will be invoked when the model changes.
  
@override
  
void
 addListener
(
VoidCallback
 listener
)
 
{
    _listeners
.
add
(
listener
);
  
}

  
/// [listener] will no longer be invoked when the model changes.
  
@override
  
void
 removeListener
(
VoidCallback
 listener
)
 
{
    _listeners
.
remove
(
listener
);
  
}

  
/// Returns the number of listeners listening to this model.
  
int
 
get
 listenerCount 
=>
 _listeners
.
length
;

  
/// Should be called only by [Model] when the model has changed.
  
@protected
  
void
 notifyListeners
()
 
{
    
// We schedule a microtask to debounce multiple changes that can occur
    
// all at once.
    
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,至于为什么,看到后面就了解了。

前两个方法都好理解,增加/删除一个监听。

最主要的就是后面的 notifyListeners()方法,

其中主要的逻辑就是把_version++,并消除并发 change 时所可能引发的问题。

解决问题的逻辑就是首先判断 _microtaskVersion 和 version 是否相等。

如果相等,则把_microtaskVersion ++,然后启动一个微任务来把version++,并处理 listener()。

如果不相等就不做任务,这样就消除了可能因为并发所引发的问题。

ModelFinder

该类是用来查找 Model 的,但是已经弃用了,改用 ScopedModel.of。

代码如下:

/// Finds a [Model]. Deprecated: Use [ScopedModel.of] instead.
@deprecated
class
 
ModelFinder
<
T 
extends
 
Model
>
 
{
  
/// Returns the [Model] of type [T] of the closest ancestor [ScopedModel].
  
///
  
/// [Widget]s who call [of] with a [rebuildOnChange] of true will be rebuilt
  
/// whenever there's a change to the returned model.
  T of
(
BuildContext
 context
,
 
{
bool
 rebuildOnChange
:
 
false
})
 
{
    
return
 
ScopedModel
.
of
<
T
>(
context
,
 rebuildOnChange
:
 rebuildOnChange
);
  
}
}


ScopedModel

该类为我们所查看的重点,毕竟库的名字就是它。

二话不说,先来看一下类上面的注释:

/// Provides a [Model] to all descendants of this Widget.
///
/// Descendant Widgets can access the model by using the
/// [ScopedModelDescendant] Widget, which rebuilds each time the model changes,
/// or directly via the [ScopedModel.of] static method.
///
/// To provide a Model to all screens, place the [ScopedModel] Widget above the
/// [WidgetsApp] or [MaterialApp] in the Widget tree.

/// 向此小部件的所有后代提供[Model]。
///
/// 子代小部件可以使用[ScopedModelDescendant]小部件访问Model,该小部件在每次模型更改时重建,或者直接通过[ScopedModel.of]静态方法进行访问。
///
/// 要向所有页面提供Model,请将[ScopedModel]小部件放在小部件树中的[WidgetsApp]或[MaterialApp]上方。
注释上面写的很明显了,如果你要向所有的页面都提供Model,那么就放在 widget 树的最上方,也就是我们的 MaterialApp 的上面。

再看一下代码:

class
 
ScopedModel
<
T 
extends
 
Model
>
 
extends
 
StatelessWidget
 
{
  
/// The [Model] to provide to [child] and its descendants.
  
final
 T model
;

  
/// The [Widget] the [model] will be available to.
  
final
 
Widget
 child
;

  
ScopedModel
({
@required
 
this
.
model
,
 
@required
 
this
.
child
})
      
:
 
assert
(
model 
!=
 
null
),
        
assert
(
child 
!=
 
null
);

  
@override
  
Widget
 build
(
BuildContext
 context
)
 
{
    
return
 
AnimatedBuilder
(
      animation
:
 model
,
      builder
:
 
(
context
,
 _
)
 
=>
 
_InheritedModel
<
T
>(
model
:
 model
,
 child
:
 child
),
    
);
  
}

  
/// Finds a [Model] provided by a [ScopedModel] Widget.
  
///
  
/// Generally, you'll use a [ScopedModelDescendant] to access a model in the
  
/// Widget tree and rebuild when the model changes. However, if you would to
  
/// access the model directly, you can use this function instead!
  
static
 T of
<
T 
extends
 
Model
>(
    
BuildContext
 context
,
 
{
    
bool
 rebuildOnChange 
=
 
false
,
  
})
 
{
    
final
 
Type
 type 
=
 _type
<
_InheritedModel
<
T
>>();

    
Widget
 widget 
=
 rebuildOnChange
        
?
 context
.
inheritFromWidgetOfExactType
(
type
)
        
:
 context
.
ancestorWidgetOfExactType
(
type
);

    
if
 
(
widget 
==
 
null
)
 
{
      
throw
 
new
 
ScopedModelError
();
    
}
 
else
 
{
      
return
 
(
widget 
as
 
_InheritedModel
<
T
>).
model
;
    
}
  
}

  
static
 
Type
 _type
<
T
>()
 
=>
 T
;
}

首先,ScopedModel 需要一个泛型来确认 Model 的类型,并且这是一个无状态的 W idget。

然后 ScopedModel 需要一个child Widget,这个 child Widget 就可以说是我们的 MaterialApp了。

接着是 build()方法,这就是 ScopedModel 最精髓的地方。

build() 方法

build()方法使用了 AnimatedBuilder来构建 widget,因为 AnimatedBuilder会自动监听 animation 数据的更改。

AnimatedBuilder的参数 animation 需要的是一个 Listenable 对象,而我们的 Model 正好继承的就是Listenable。

所以我们在自定义的 Model 中,需要更新的地方手动调用 notifyListeners()。

notifyListeners()前面也说了,就是把 _version++。

既然_version++了,那就达到了我们想要的目的,更新使用了该数据的UI。

of() 方法

of() 方法我们很熟悉,是用来获取 Model 的。

用起来很简单,但是里面的内容却不少,看一下源码:

static
 T of
<
T 
extends
 
Model
>(
  
BuildContext
 context
,
 
{
    
bool
 rebuildOnChange 
=
 
false
,
  
})
 
{
  
final
 
Type
 type 
=
 _type
<
_InheritedModel
<
T
>>();

  
Widget
 widget 
=
 rebuildOnChange
    
?
 context
.
inheritFromWidgetOfExactType
(
type
)
    
:
 context
.
ancestorWidgetOfExactType
(
type
);

  
if
 
(
widget 
==
 
null
)
 
{
    
throw
 
new
 
ScopedModelError
();
  
}
 
else
 
{
    
return
 
(
widget 
as
 
_InheritedModel
<
T
>).
model
;
  
}
}

static
 
Type
 _type
<
T
>()
 
=>
 T
;
of() 方法需要传入一个 Model 的泛型,

首先获取了一下它的 Type,随后根据 rebuildOnChange 的值用 inheritFromWidgetOfExactType/ ancestorWidgetOfExactType来查找widget。

这里解释一下这两个方法:

inheritFromWidgetOfExactType 是用来获取给定类型的最近的 widget,并且在值更新的时候自动重新构建。

ancestorWidgetOfExactType 是用来获取给定类型最近的 祖先 Widget,并且在值更新的时候不重新构建。

所以这样就控制住了没有必要的UI更新。



_InheritedModel

在上面的build 和 of 方法中,都出现了该 Model,

他其实就是一个 InheritedWidget,重写了 updateShouldNotify方法来控制重绘。

代码如下:

class
 
_InheritedModel
<
T 
extends
 
Model
>
 
extends
 
InheritedWidget
 
{
  
final
 T model
;
  
final
 
int
 version
;

  
_InheritedModel
({
Key
 key
,
 
Widget
 child
,
 T model
})
      
:
 
this
.
model 
=
 model
,
        
this
.
version 
=
 model
.
_version
,
        
super
(
key
:
 key
,
 child
:
 child
);

  
@override
  
bool
 updateShouldNotify
(
_InheritedModel
<
T
>
 oldWidget
)
 
=>
      
(
oldWidget
.
version 
!=
 version
);
}

重绘的逻辑就是判断 version 是否相同。

ScopedModelDescendant

该类就是一个包装了 ScopedModel.of方法的无状态 widget。

代码如下:

class
 
ScopedModelDescendant
<
T 
extends
 
Model
>
 
extends
 
StatelessWidget
 
{
  
/// Called whenever the [Model] changes.
  
final
 
ScopedModelDescendantBuilder
<
T
>
 builder
;

  
/// An optional constant child that does not depend on the model.  This will
  
/// be passed as the child of [builder].
  
final
 
Widget
 child
;

  
/// An optional constant that determines whether the
  
final
 
bool
 rebuildOnChange
;

  
/// Constructor.
  
ScopedModelDescendant
({
    
@required
 
this
.
builder
,
    
this
.
child
,
    
this
.
rebuildOnChange 
=
 
true
,
  
});

  
@override
  
Widget
 build
(
BuildContext
 context
)
 
{
    
return
 builder
(
      context
,
      child
,
      
ScopedModel
.
of
<
T
>(
context
,
 rebuildOnChange
:
 rebuildOnChange
),
    
);
  
}
}

具体就不细说了,因为几乎没有逻辑在里面。

总结

可以看到ScopedModel 的设计真的是非常巧妙,

利用 AnimatedBuilder 和 InheritWidget 来做到全局的状态管理。

关于微任务(Microtask) 和 EventQueue的理解,我会继续出一篇文章来说一下。

那多个Model如何使用?利用 mixin 就可以了。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
移动开发工程师
手记
粉丝
30
获赞与收藏
45

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消