對於客戶端應用程序而言,單頁應用程序(Single Page Application)是最常見的表現形式。有經驗的開發人員往往會把一個View分解多個SubView。那么,如何在多個SubView之間 『共享數據』 是一個很棘手的事情。又因為ViewModel才是真正為View提供數據來源,所以本質上『共享數據』指的是多個ViewModel之間共享同一塊數據控件。
JavaScript中的原型鏈
談到『共享』兩字,腦海里跳出第一個印象就是『繼承』。對吧,因為你是父母的孩子,所以理所當然你可以和父母共享家中的一切。所以『共享』的前提,就是構建一個『繼承鏈』,也就是JavaScript中的『原型鏈』。
那么JavaScript是怎樣實現原型鏈呢?有經驗的JavaScript程序員想必早就記的滾瓜爛熟了——通過內置屬性 __proto__ 來實現。
所以ViewModel之間『共享數據』的核心就是如何去實現一個繼承鏈,如下所示:
為ViewModel構建繼承關系
有了上述的分析之后,只要仿照JavaScript的 __proto__ 的實現,我們對所有ViewModel的基類ViewModelBase添加一個ParentViewModel 屬性,它代表當前ViewModel的父親對象。
public class ViewModelBase
{
public ViewModelBase ParentViewModel { get; set; }
//...
}
接着我參考了WPF中是怎樣獲取父ViewModel當中的數據:
Binding="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.ParentViewModelProperty}
可以看到通過 FindAncestor 方法,去指定 AncestorType 類型的上層對象中獲取數據。
所以,我為ViewModelBase 增加一個擴展方法,可以通過繼承鏈實現從指定的祖先對象獲取數據。
public static IEnumerable<T> Ancestors<T>(this ViewModelBase origin) where T : ViewModelBase
{
if (origin==null)
{
yield break;
}
var parentViewModel = origin.ParentViewModel;
while (parentViewModel!=null)
{
var castedViewModel = parentViewModel as T;
if (castedViewModel != null)
{
yield return castedViewModel;
}
parentViewModel = parentViewModel.ParentViewModel;
}
}
對應在ViewModel中,可以通過 Ancestors擴展方法獲取上層對象的數據
var ancestors = this.Ancestors<FaceBoxViewModel>();
最后,以圖示的形式會更加直觀,下圖所示,SubViewModel依靠繼承鏈可以輕松訪問到ParentViewModel的共享數據:
小結
本篇文章介紹了怎樣在ViewModel之間共享數據,實際上解決方案是非常簡單的,人為的構造了一個繼承鏈並隨着繼承鏈往上找,總是能找到希望獲取到的數據。類似與JavaScript中的原型鏈,維護了一種至上而下的父子關系。
源代碼托管在Github上,點擊此了解