如何从 "traditional" 过渡到响应式 MVVM
How to make the transition from "traditional" to reactive MVVM
我想过渡到反应式视图模型/模型。
到目前为止我已经使用了 3 个场景:
"ValueA": 一次只能从一个视图模型访问模型值,并且只能通过视图模型更改值
=> 在模型中简单 属性,在视图模型中用 PropertyChanged
转发 属性
"ValueB":从多个视图模型访问模型值and/or 从其他来源更改
=> 属性 在模型中有事件,转发 属性 并将更改的事件转换为视图模型中的 PropertyChanged
"ValueC": 仅在视图模型中使用的值
=> 模型中没有 属性,属性 由自己的字段支持,视图模型中有 PropertyChanged
这是我的"current"方法:
class Model
{
public string ValueA {get;set;}
private string valueB;
public event ValueBChangedEvent ValueBChanged;
public string ValueB
{
get
{
return valueB;
}
set
{
valueB = value;
ValueBChanged();
}
}
}
class ViewModel : INotifyPropertyChanged
{
private Model model;
public string ValueA
{
get
{
return model.ValueA;
}
set
{
model.ValueA = value;
OnPropertyChanged();
}
}
ViewModel()
{
model.ValueBChanged += model_ValueBChanged;
}
private void model_ValueBChanged()
{
OnPropertyChanged("ValueB");
}
public string ValueB
{
get
{
return model.ValueB;
}
set
{
model.ValueB = value;
// no change notification since done via model
}
}
private string valueC;
public string ValueC
{
get
{
return valueC;
}
set
{
valueC = value;
OnPropertyChanged();
}
}
}
这就是我打算使用响应式扩展对它们进行建模的方式:
class ReactiveModel
{
public string ValueA {get;set;}
private ISubject<string> valueB = new Subject<string>();
public ISubject<string> ValueB
{
get
{
return valueB;
}
}
}
class ReactiveViewModel : INotifyPropertyChanged
{
private ReactiveModel model;
public string ValueA
{
get
{
return ???;
}
set
{
???
}
}
private ReactiveProperty<string> valueB = model.valueB.ToReactiveProperty();
public Reactive<string> ValueB
{
get
{
return valueB;
}
// no setter since access via ValueB.Value which is read-write
}
private ISubject<string> _valueC = new Subject<string>();
private ReactiveProperty<string> valueC = _valueC.ToReactiveProperty();
public ReactiveProperty<string> ValueC
{
get
{
return valueC;
}
// no setter since access via ValueC.Value which is read-write
}
}
总结:
- "ValueA": 我对这个案子一无所知
- "ValueB":乍一看有效,但既不会将更改从视图模型传播到模型,也不会以其他方式传播。
- "ValueC":按预期工作
如果我有 ValueA 和 ValueB 的解决方案,我会很高兴。
这是一个有点争议的话题,但我个人并不认为 属性 更改通知是特定于视图模型和视图的,因此我使用 B 但我也将 INPC 添加到模型中在我的数据层。这可以在 post-processing 构建步骤中使用 Fody 或通过使用 Castle Dynamic Proxy 之类的代理将模型包装在代理中来完成。我个人使用后者,尽管它需要与您的 ORM 集成,以免影响性能,即您不希望您的数据库代码加载模型对象,然后认为该对象已更改,因为您尝试使用代理更新它包装器(当您将 IList<> 转换为 ObservableCollection 时尤其如此)。
您目前的做法似乎没有多大意义。您正在实施 事件 以在 Model
发生变化时发出信号,以便 View Model
可以采取行动。然而只有视图模型应该改变模型,因此事件是完全没有必要的。
View Model
负责对 Model
进行更改,因此它应该 知道 何时执行更改,因为它是源所说的变化。
纯 MVVM 方法是这样的:
public class MyModel
{
public string MyValue { get; set; }
...
}
public class MyViewModel
{
private MyModel _Model;
public string MyModelValue
{
get { return _Model.MyValue; }
set
{
_Model.MyValue = value;
//Notify property changed.
}
}
...
}
Model
没有责任通知 View
变化,ViewModel
有责任通知这些变化。 Model
应该 而不是 暴露给 View
,而是 View
的 Model
的属性]requires 应该公开。
这样想。
- 用户在
View
上的 TextBox
中更改了 MyModelValue
属性。
View
将更改通知 ViewModel
。
ViewModel
更改 Model
。
INotifyPropertyChanged
的唯一目的是当上述过程反转时,ViewModel
需要告诉一个属性变了View
:
- 调用
ViewModel
中的方法更新 MyModelValue
。
ViewModel
将更改通知 View
。
View
更新了TextBox
。
不总是遵循视图需要的 仅 Model
属性的模式,相反,您可以看到 entire Model
被暴露给 View
,但正如我之前多次说过的,MVVM 是一种 模式 ,不是法律。在 Model
中实施 INotifyPropertyChanged
是完全可以接受的。
ValueB:视图模型负责更新模型。 ReactiveProperty
仅使用模型属性中的 IObservable 接口并从 ValueB
中读取值(不写入任何内容)。
ReactiveProperty
通过 Value
属性 视图更改。
ReactiveProperty
实现了 IObservable
,您应该订阅更改以获得新值。
ValueA:我们可以在视图模型端创建一个ReactiveProperty
订阅以将更改的值传播到模型。
这是解决方案的代码:
class ReactiveModel
{
public string ValueA {get;set;}
private readonly Subject<string> valueB = new Subject<string>();
public IObservable<string> ValueB
{
get
{
return valueB;
}
}
public void UpdateB(string newValue)
{
valueB.OnNext(newValue);
}
}
class ReactiveViewModel
{
private readonly ReactiveModel model;
private readonly ReactiveProperty<string> valueA;
private readonly ReactiveProperty<string> valueB;
public ReactiveViewModel(ReactiveModel model)
{
this.model = model;
valueA = new ReactiveProperty<string>(model.ValueA);
valueA.Subscribe(x => model.ValueA = x);
valueB = model.ValueB.ToReactiveProperty();
valueB.Subscribe(model.UpdateB);
}
public IObservable<string> ValueA
{
get
{
return valueA;
}
}
public ReactiveProperty<string> ValueB
{
get
{
return valueB;
}
}
}
XAML 在这两种情况下都是:
<TextBox Text="{Binding ValueA.Value, UpdateSourceTrigger=PropertyChanged}"/>
我想过渡到反应式视图模型/模型。
到目前为止我已经使用了 3 个场景:
"ValueA": 一次只能从一个视图模型访问模型值,并且只能通过视图模型更改值
=> 在模型中简单 属性,在视图模型中用
PropertyChanged
转发 属性"ValueB":从多个视图模型访问模型值and/or 从其他来源更改
=> 属性 在模型中有事件,转发 属性 并将更改的事件转换为视图模型中的
PropertyChanged
"ValueC": 仅在视图模型中使用的值
=> 模型中没有 属性,属性 由自己的字段支持,视图模型中有
PropertyChanged
这是我的"current"方法:
class Model
{
public string ValueA {get;set;}
private string valueB;
public event ValueBChangedEvent ValueBChanged;
public string ValueB
{
get
{
return valueB;
}
set
{
valueB = value;
ValueBChanged();
}
}
}
class ViewModel : INotifyPropertyChanged
{
private Model model;
public string ValueA
{
get
{
return model.ValueA;
}
set
{
model.ValueA = value;
OnPropertyChanged();
}
}
ViewModel()
{
model.ValueBChanged += model_ValueBChanged;
}
private void model_ValueBChanged()
{
OnPropertyChanged("ValueB");
}
public string ValueB
{
get
{
return model.ValueB;
}
set
{
model.ValueB = value;
// no change notification since done via model
}
}
private string valueC;
public string ValueC
{
get
{
return valueC;
}
set
{
valueC = value;
OnPropertyChanged();
}
}
}
这就是我打算使用响应式扩展对它们进行建模的方式:
class ReactiveModel
{
public string ValueA {get;set;}
private ISubject<string> valueB = new Subject<string>();
public ISubject<string> ValueB
{
get
{
return valueB;
}
}
}
class ReactiveViewModel : INotifyPropertyChanged
{
private ReactiveModel model;
public string ValueA
{
get
{
return ???;
}
set
{
???
}
}
private ReactiveProperty<string> valueB = model.valueB.ToReactiveProperty();
public Reactive<string> ValueB
{
get
{
return valueB;
}
// no setter since access via ValueB.Value which is read-write
}
private ISubject<string> _valueC = new Subject<string>();
private ReactiveProperty<string> valueC = _valueC.ToReactiveProperty();
public ReactiveProperty<string> ValueC
{
get
{
return valueC;
}
// no setter since access via ValueC.Value which is read-write
}
}
总结:
- "ValueA": 我对这个案子一无所知
- "ValueB":乍一看有效,但既不会将更改从视图模型传播到模型,也不会以其他方式传播。
- "ValueC":按预期工作
如果我有 ValueA 和 ValueB 的解决方案,我会很高兴。
这是一个有点争议的话题,但我个人并不认为 属性 更改通知是特定于视图模型和视图的,因此我使用 B 但我也将 INPC 添加到模型中在我的数据层。这可以在 post-processing 构建步骤中使用 Fody 或通过使用 Castle Dynamic Proxy 之类的代理将模型包装在代理中来完成。我个人使用后者,尽管它需要与您的 ORM 集成,以免影响性能,即您不希望您的数据库代码加载模型对象,然后认为该对象已更改,因为您尝试使用代理更新它包装器(当您将 IList<> 转换为 ObservableCollection 时尤其如此)。
您目前的做法似乎没有多大意义。您正在实施 事件 以在 Model
发生变化时发出信号,以便 View Model
可以采取行动。然而只有视图模型应该改变模型,因此事件是完全没有必要的。
View Model
负责对 Model
进行更改,因此它应该 知道 何时执行更改,因为它是源所说的变化。
纯 MVVM 方法是这样的:
public class MyModel
{
public string MyValue { get; set; }
...
}
public class MyViewModel
{
private MyModel _Model;
public string MyModelValue
{
get { return _Model.MyValue; }
set
{
_Model.MyValue = value;
//Notify property changed.
}
}
...
}
Model
没有责任通知 View
变化,ViewModel
有责任通知这些变化。 Model
应该 而不是 暴露给 View
,而是 View
的 Model
的属性]requires 应该公开。
这样想。
- 用户在
View
上的TextBox
中更改了MyModelValue
属性。 View
将更改通知ViewModel
。ViewModel
更改Model
。
INotifyPropertyChanged
的唯一目的是当上述过程反转时,ViewModel
需要告诉一个属性变了View
:
- 调用
ViewModel
中的方法更新MyModelValue
。 ViewModel
将更改通知View
。View
更新了TextBox
。
不总是遵循视图需要的 仅 Model
属性的模式,相反,您可以看到 entire Model
被暴露给 View
,但正如我之前多次说过的,MVVM 是一种 模式 ,不是法律。在 Model
中实施 INotifyPropertyChanged
是完全可以接受的。
ValueB:视图模型负责更新模型。 ReactiveProperty
仅使用模型属性中的 IObservable 接口并从 ValueB
中读取值(不写入任何内容)。
ReactiveProperty
通过 Value
属性 视图更改。
ReactiveProperty
实现了 IObservable
,您应该订阅更改以获得新值。
ValueA:我们可以在视图模型端创建一个ReactiveProperty
订阅以将更改的值传播到模型。
这是解决方案的代码:
class ReactiveModel
{
public string ValueA {get;set;}
private readonly Subject<string> valueB = new Subject<string>();
public IObservable<string> ValueB
{
get
{
return valueB;
}
}
public void UpdateB(string newValue)
{
valueB.OnNext(newValue);
}
}
class ReactiveViewModel
{
private readonly ReactiveModel model;
private readonly ReactiveProperty<string> valueA;
private readonly ReactiveProperty<string> valueB;
public ReactiveViewModel(ReactiveModel model)
{
this.model = model;
valueA = new ReactiveProperty<string>(model.ValueA);
valueA.Subscribe(x => model.ValueA = x);
valueB = model.ValueB.ToReactiveProperty();
valueB.Subscribe(model.UpdateB);
}
public IObservable<string> ValueA
{
get
{
return valueA;
}
}
public ReactiveProperty<string> ValueB
{
get
{
return valueB;
}
}
}
XAML 在这两种情况下都是:
<TextBox Text="{Binding ValueA.Value, UpdateSourceTrigger=PropertyChanged}"/>