前言
UniRx中ReactiveCommand和AsyncReactiveCommand是一种基于IObservable的可控命令机制,用于控制是否允许进程运行
很难用文字说明,下面我们直接看代码吧
ReactiveCommand
void Start()
{
//创建IObservable<bool>
ReactiveProperty<bool> gate = new BoolReactiveProperty(false);
// 基于gate创建一个ReactiveCommand
var command = new ReactiveCommand((IObservable<bool>)gate);
// command.Execute()代表命令执行,在Subscribe回调中接受
command.Subscribe(_ =>
{
Debug.Log("执行");
});
// 设置值为false,
gate.Value = false;
Debug.Log("gate = false ");
//执行Execute时判定gate状态
//如果为true,则在Observable上执行OnNext
//如果是false的话什么都不做
command.Execute();
// 设置值为true
gate.Value = true;
Debug.Log("gate = true");
command.Execute();
}
输出
gate = false
gate = true
执行
ReactiveCommand本身是一个可订阅的Sunscribe实体,并通过Execute()执行发布OnNext
ReactiveCommand以IObservable为参数,利用Observable的值来控制命令的执行权限
原理见下图:
当value为false时
当value为true时
ToReactiveCommand
IObservable直接通过ToReactiveCommand()创建ReactiveCommand,而无需 new ReactiveCommand。
如下所示,其他用法都一致。
ReactiveProperty<bool> gate = new BoolReactiveProperty(false);
var command = gate.ToReactiveCommand();
与UGUI结合
上面的示例虽然容易理解,但是没有任何实用价值,ReactiveCommand的真正价值在于和UGUI的联动。
看示例代码
public class ReactiveCommandSample : MonoBehaviour
{
[SerializeField] private Toggle _toggle;
[SerializeField] private Button _button;
[SerializeField] private Text _resultText;
void Start()
{
// 使用toggle状态创建ReactiveCommand
var command = _toggle.OnValueChangedAsObservable().ToReactiveCommand();
// 绑定到Button,通过toogle的值来控制button的interactive
command.BindTo(_button);
command.Subscribe(_ =>
{
_resultText.text += "Click!";
});
// BindToOnClick来定义
// command.BindToOnClick(_button, _ => _resultText.text += "Click!");
}
}
按钮的交互状态和点击事件都取决于toogle的开关状态
AsyncReactiveCommand
AsyncReactiveCommand是ReactiveCommand的异步版本。他们的行为相似,主要区别在于Subscribe函数结构
IDisposable Subscribe(Func<T, IObservable<Unit>> asyncAction);
AsyncReactiveCommand创建时,如果没有指定任何参数,它会在Execute()执行时自行将执行状态切换为false。
并且在订阅时等待IObservable完成,并在一切完成时返回 true 。
我们通过示例来看下:
以下代码对输入的 URL 执行 HTTP 通信并显示结果。
AsyncReactiveCommand使通信过程和Button的状态连接起来。
public class AsyncReactiveCommandSample : MonoBehaviour
{
[SerializeField] private InputField _urlText;
[SerializeField] private Button _button;
[SerializeField] private Text _resultText;
void Start()
{
//创建空参AsyncReactiveCommand
var command = new AsyncReactiveCommand();
//和button回调绑定
command.BindToOnClick(_button, _ =>
{
//www是一个异步过程,异步请求的过程中,command的value都是false,直到请求返回,value为true
return ObservableWWW.GetWWW(_urlText.text)
.ForEachAsync(www => _resultText.text = www.text);
});
}
}
在 HTTP 通信期间 Button 是灰色和禁用的。
通过使用AsyncReactiveCommand,我们可以轻松编写执行异步处理,直到完成才执行下一个命令。换句话说,我们能够创建一个准确的异步响应机制
如果一个运行多个AsyncReactiveCommand,即并行运行异步处理,则直到所有异步处理完成后,才会返回true状态。
AsyncReactiveCommand简写方式
使用简写方法,你会发现自己新建AsyncReactiveCommand是多余的。
UniRx提供了一个AsyncReactiveCommand简写方式来直接生成和使用UI 元素BindToOnClick。
代码如下:
//普通方式
var command = new AsyncReactiveCommand();
command.BindToOnClick(_button, _ =>
{
return ObservableWWW.GetWWW(_urlText.text)
.ForEachAsync(www => _resultText.text = www.text);
});
//简写方式
_button.BindToOnClick(_ =>
{
return ObservableWWW.GetWWW(_urlText.text)
.ForEachAsync(www => _resultText.text = www.text);
});
补充:ForEachAsync
ForEachAsync代表当有值流动时,它会转换为同时只处理一次 OnCompleted() 和 OnNext() 的 UnitObservable。或者可以理解为Do()+AsUnitObservable();
AsyncReactiveCommand控制用户界面
AsyncReactiveCommand也有接受IReactiveProperty参数的构造方法。
使用它,我们可以使用一个通用的 IReactiveProperty 控制多个 AsyncReactiveCommand。
代码如下:
public class AsyncReactiveCommandSample2 : MonoBehaviour
{
[SerializeField] private Button _wait1SecondButton;
[SerializeField] private Button _wait2SecondsButton;
[SerializeField] private Button _httpButton;
[SerializeField] private Text _resultText;
/// <summary>
/// 所有按钮都通用的ReactiveProperty
/// </summary>
private ReactiveProperty<bool> _sharedGate = new ReactiveProperty<bool>(true);
void Start()
{
// 等待1秒的按钮
_wait1SecondButton.BindToOnClick(_sharedGate, _ =>
{
_resultText.text = "等待1秒";
return Observable.Timer(TimeSpan.FromSeconds(1))
.ForEachAsync(__ => _resultText.text = "1秒钟过去了");
});
// 等待2秒的按钮
_wait2SecondsButton.BindToOnClick(_sharedGate, _ =>
{
_resultText.text = "等待2秒";
return Observable.Timer(TimeSpan.FromSeconds(2))
.ForEachAsync(__ => _resultText.text = "2秒钟过去了");
});
// 通信按钮
_httpButton.BindToOnClick(_sharedGate, _ =>
{
_resultText.text = "发起通信";
return ObservableWWW.GetWWW("https://unity3d.com")
.ForEachAsync(www => _resultText.text = www.text);
});
}
}
这样,通过将公共值传递给AsyncReactiveCommand ,现在IReactiveProperty可以对 Button 进行分组并共同管理它们的状态了。
总结
ReactiveCommand,AsyncReactiveCommand 是一个感觉用处不大,但其实通用性高,用起来非常方便的机制。尤其是当它绑定到UI上时,才真正显示出它的价值。