假设我有这个ReactiveUI视图模型结构,其中Model
是任意的模型类型。
class ViewModel : ReactiveObject
{
private readonly ReactiveList<Model> _models;
public IReactiveList<Model> Models { get { return _models; } }
public IReactiveCommand LoadModels { get; private set; }
public bool LoadingModels { get; private set; } // Notifies;
}
这些模型来自基于任务的异步API,由以下接口建模:
interface ITaskApi
{
Task<IEnumerable<Model>> GetModelsAsync();
}
在了解了Octokit.net的反应式库是如何编写的之后,我编写了以下类来将API适应于反应式世界:
class ObservableApi
{
private readonly ITaskApi _taskApi;
public IObservable<Model> GetModels() {
return _taskApi.GetModelsAsync().ToObservable().SelectMany(c => c);
}
}
现在,我已经在ViewModel()
构造函数中编写了以下方法来实现LoadModels
命令内部的模型加载:
// In both cases, we want the command to be disabled when loading:
LoadModels = new ReactiveCommand(this.WhenAny(x => x.LoadingModels, x => !x.Value));
// First method, with the Observable API;
LoadModels.Subscribe(_ =>
{
LoadingModels = true;
_models.Clear();
observableClient.GetModels().ObserveOnDispatcher()
.Subscribe(
m => _models.Add(m),
onCompleted: () => { LoadingModels = false; });
});
// Second method, with the task API;
LoadModels.Subscribe(async _ =>
{
LoadingModels = true;
try {
var loadedModels = await taskClient.GetModelsAsync();
_models.Clear();
_models.AddRange(loadedModels);
} catch (Exception ex) {
RxApp.DefaultExceptionHandler.OnNext(ex);
} finally {
LoadingModels = false;
}
});
虽然我认为这两种方式都能完成任务,但我有以下疑虑:
- 在第一个示例中,我应该处理内部订阅吗?还是在内部可观察对象完成或出错时处理
- 在第二个示例中,我知道
GetModelsAsync
方法中引发的异常将被接受
从异步枚举(IObservable<T>
或Task<IEnumerable<T>>
(或者IObservable<IEnumerable<T>>
更好?))填充ReactiveList<T>
的"最佳"、最惯用的方法是什么?
在了解了Octokit.net的反应库是如何编写的之后,我编写了以下类以使API适应反应世界:
虽然您有时想要这样做(即展平集合),但通常将其保留为IEnumerable<T>
更方便,除非您计划对列表中的每个项调用异步方法。既然我们只想把所有东西都放在一个列表中,我们就不想这样做。将其保留为Task<T>
在第一个示例中,我应该处理内部订阅吗?还是在内部可观察对象完成或出错时处理?
每当您在另一个Subscribe中有一个Subscript时,您可能需要SelectMany
运算符。然而,有一种更好的方法来做你想做的事情,你应该看看这篇文档文章来了解更多信息。
所以,以下是我如何编写您的代码:
// In both cases, we want the command to be disabled when loading:
LoadModels = new ReactiveCommand();
LoadModels.RegisterAsyncTask(_ => taskClient.GetModelsAsync())
.Subscribe(items =>
{
// This Using makes it so the UI only looks at the collection
// once we're totally done updating it, since we're basically
// changing it completely.
using (_models.SuppressChangeNotifications())
{
_models.Clear();
_models.AddRange(items);
}
});
LoadModels.ThrownExceptions
.Subscribe(ex => Console.WriteLine("GetModelsAsync blew up: " + ex.ToString());
// NB: _loadingModels is an ObservableAsPropertyHelper<bool>
LoadModels.IsExecuting
.ToProperty(this, x => x.LoadingModels, out _loadingModels);