scoped-model原始碼解析

奮鬥的Leo發表於2019-05-06

參考

scoped_model 是 Google 推薦使用的應用狀態管理庫(Simple app state management),當然,除了它,還有其他選擇,比如 Redux,Rx,hooks 等等。

臨時狀態和應用狀態

我們說 scoped_model 用於應用狀態管理,是因為除了**應用狀態(app state)**以外,還有臨時狀態(ephemeral state)。

臨時狀態

也可以稱為 UI 狀態或者本地狀態,是一種可以包含在單個 widget 的狀態。

比如以下定義:

  • PageView 當前的頁面
  • 動畫當前的進度
  • BottomNavigationBar 當前選中項

使用臨時狀態,我們不需要使用狀態管理,只需要一個 StatefulWidget

應用狀態

需要在應用中多個元件之間共享,而且在不同的使用者會話之間保持,這就是應用狀態,有時候也稱為共享狀態。

比如以下定義:

  • 使用者偏好設定
  • 登入資訊
  • 通知欄功能
  • 購物車功能
  • 已讀未讀功能

臨時狀態和應用狀態之間沒有非常明確的界限,比如你可能需要持久化儲存 BottomNavigationBar 的選中項,那它就不是臨時狀態了,而是應用狀態。如果你願意,你甚至可以不用任何狀態管理庫,只使用 StatesetState() 來管理應用所有的狀態。

scoped_model

scoped_model 由三個部分組成,ScopedModelScopedModelDescendantModel

Model

Model 是定義一個資料模型的基類,它繼承了 Listenable,提供了 notifyListeners() 方法來通知元件需要更新。

@protected                                                                  
void notifyListeners() {                                                    
  // We schedule a microtask to debounce multiple changes that can occur    
  // all at once.                     
  if (_microtaskVersion == _version) {
    // 去抖
    _microtaskVersion++; 
    // 安排一個 Microtask 任務
    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());   
    });                                                                     
  }                                                                         
}                                                                           
複製程式碼

關於 Microtask:這涉及到 dart 的單執行緒模型,這裡我們只需要知道,它的優先順序比 event 要高,會先執行。

關於更多資訊,可以檢視 event-loop

ScopedModel

在定義一個 Model 後,我們需要再定義一個 ScopedModel 作為 root widget,它有兩個引數,一個是 model 即我們上面定義的 Model 例項,另外一個是 child,則是我們定義的 widget。ScopedModel 是一個 StetelessWidget,我們看下它的 build() 方法:

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

AnimatedBuilder 是一個用於構建動畫的 widget,它的 animation 引數是一個 Listenable 類:

abstract class Listenable {                                                          
  /// Abstract const constructor. This constructor enables subclasses to provide     
  /// const constructors so that they can be used in const expressions.              
  const Listenable();                                                                
                                                                                     
  /// Return a [Listenable] that triggers when any of the given [Listenable]s        
  /// themselves trigger.                                                            
  ///                                                                                
  /// The list must not be changed after this method has been called. Doing so       
  /// will lead to memory leaks or exceptions.                                       
  ///                                                                                
  /// The list may contain nulls; they are ignored.                                  
  factory Listenable.merge(List<Listenable> listenables) = _MergingListenable;       
                                                                                     
  /// Register a closure to be called when the object notifies its listeners.        
  void addListener(VoidCallback listener);                                           
                                                                                     
  /// Remove a previously registered closure from the list of closures that the      
  /// object notifies.                                                               
  void removeListener(VoidCallback listener);                                        
}                                                                                    
複製程式碼

AnimatedBuilder 會呼叫 addListener() 方法新增一個監聽者,然後呼叫 setState() 方法進行 rebuild。從上面可知,Model 繼承 Listenable 類。這也是為什麼在修改值後需要呼叫 notifyListeners() 的原因。

再看下 builder 引數,它實際上返回了一個 _InheritedModel 例項:

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);                                
}                                                                    
複製程式碼

InheritedWidgetscoped_model 的核心。

InheritedWidget

InheritedWidget 可以在元件樹中有效的傳遞和共享資料。將 InheritedWidget 作為 root widget,child widget 可以通過 inheritFromWidgetOfExactType() 方法返回距離它最近的 InheritedWidget 例項,同時也將它註冊到 InheritedWidget 中,當 InheritedWidget 的資料發生變化時,child widget 也會隨之 rebuild。

InheritedWidget rebuild 時,會呼叫 updateShouldNotify() 方法來決定是否重建 child widget。

繼續看 ScopedModel,它使用 version 來判斷是否需要通知 child widget 更新:

@override                                                          
bool updateShouldNotify(_InheritedModel<T> oldWidget) =>           
      (oldWidget.version != version); 
複製程式碼

當我們呼叫 ModelnotifyListeners() 方法時,version 就會自增。

ScopedModelDescendant

ScopedModelDescendant 是一個工具類,用於獲取指定型別的 Model,當 Model 更新時,會重新執行 build() 方法:

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


static T of<T extends Model>(                       
  BuildContext context, {                           
  bool rebuildOnChange = false,                     
}) {                                                
  final Type type = _type<_InheritedModel<T>>();    
  
  // 最終也是使用 inheritFromWidgetOfExactType 或 ancestorWidgetOfExactType
  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.of() 方法時,有個 rebuildOnChange 引數,表示當 Model 更新時,是否需要 rebuild。當設定為 false 時,會使用 ancestorWidgetOfExactType() 方法去獲取最近的 InheritedWidget,和 inheritFromWidgetOfExactType() 方法的區別是,inheritFromWidgetOfExactType 在獲取的同時會註冊到 InheritedWidget 上。

總結

使用 scoped_model,我們首先定義一個 Model,這裡面封裝了對資料的操作,需要注意,資料改變後需要呼叫 notifyListeners() 方法。接著再將 ScopedModel 作為 root widget,傳遞一個 Model 例項,最後我們可以使用 ScopedModelDescendant 來響應資料的修改,也可以手動呼叫 ScopedModel.of() 方法來獲取 Model 例項,呼叫這個方法,如果引數 rebuildOnChange 傳遞為 true,則同時會將當前 widget 註冊到 InheritedWidget 上,當資料改變時,當前 widget 會重新構建。

原文地址

相關文章