使用 Aurelia 属性 Observer 忽略您自己的更改的最佳方法
Best way to ignore your own changes using an Aurelia Property Observer
我希望有人能提供一些指导。
这是关于以最佳方式利用 Aurelia Observers。
该示例是为了说明感知到的问题而编造的示例。
假设我有三个数字属性:小时、分钟、秒又名 H、M、S,我希望构建一个对象来监视 H、M、S,每次这三个变化中的任何一个发生变化时,它都会产生一个属性 T 是一个看起来像持续时间的字符串,例如。 "hh:mm:ss" 使用具有适当零填充等的 H、M、S 属性
相反,属性 T 在用户界面中绑定,因此如果用户修改 T(例如,通过输入字段),我需要将 T 的各个部分分解为它们的 H、M , S 组件并将各个值推回 H、M、S 属性。
任何人都不应忘记,此行为在 "toView" 和 "fromView" 行为方面与 Aurelia 值转换器行为非常相似,除了我们处理 1 上的 3 个属性一侧,另一侧 1 属性。
在实施方面,我可以使用 BindingEngine 或 ObserverLocator 在 H、M、S 和 T 属性上创建观察者,并订阅这些更改通知。这一点很容易。
我的问题最终是这样的:
当H发生变化时,将触发T的重新计算,因此我将向T写入一个新值。
更改 T 将触发 T 的更改通知,然后其处理程序将分解 T 的值并将新值写回 H、M、S。
写回 H、M、S 会触发另一个更改通知以生成新的 T,依此类推。
这似乎是不言而喻的 - 至少 - 当我的 "converter" 对象写入 H、M、S、T 属性时,它应该准备好自己以某种方式忽略因此,它期望的更改通知即将到来。
但是,说起来和做起来[容易]是两回事。
我的问题是——真的有必要吗?如果需要,我该如何以最简单的方式去做。这样做的障碍如下:
必须对值进行真正的更改才能生成更改通知,因此您需要提前知道自己是否 "expecting" 才能收到通知。
更困难的问题是更改通知是通过 Aurelia "synthetic" 微任务队列发出的,因此您几乎不知道何时会收到该微任务调用。
那么,这个问题有什么好的解决办法吗?有没有人真的担心这个,或者他们只是依靠第 1 点产生自我限制的结果?也就是说,可能会有几个更改通知周期,但该过程最终会默认?
实现 N 向绑定(在本例中为 4 向)的最简单方法是使用更改处理程序结合 "ignore" 标志以防止无限递归。
下面的概念与 Aurelia 框架内部解决其中一些问题的方式非常相似 - 它稳健、高效,并且达到了 "natural" 的水平。
@autoinject()
export class MyViewModel {
@bindable({ changeHandler: "hmsChanged" })
public h: string;
@bindable({ changeHandler: "hmsChanged" })
public m: string;
@bindable({ changeHandler: "hmsChanged" })
public s: string;
@bindable()
public time: string;
private ignoreHMSChanged: boolean;
private ignoreTimeChanged: boolean;
constructor(private tq: TaskQueue) { }
public hmsChanged(): void {
if (this.ignoreHMSChanged) return;
this.ignoreTimeChanged = true;
this.time = `${this.h}:${this.m}:${this.s}`;
this.tq.queueMicroTask(() => this.ignoreTimeChanged = false);
}
public timeChanged(): void {
if (this.ignoreTimeChanged) return;
this.ignoreHMSChanged = true;
const hmsParts = this.time.split(":");
this.h = hmsParts[0];
this.m = hmsParts[1];
this.s = hmsParts[2];
this.tq.queueMicroTask(() => this.ignoreHMSChanged = false);
}
}
如果您需要一个通用的解决方案,您可以在具有不同类型的 N 向绑定的多个 ViewModel 中重复使用,您可以使用 BindingBehavior(甚至可能是 2 个 ValueConverter 的组合)来实现。
但是需要在其中实现的逻辑在概念上与我上面的示例类似。
事实上,如果框架中有针对此问题的预制解决方案(例如,@bindable()
装饰器的设置),那么该解决方案的内部逻辑也会类似。您将始终需要这些标志,并通过微任务延迟它们的重置。
避免使用微任务的唯一方法是更改框架的某些内部结构,以便可以传递上下文,告诉调度程序更改通知"this change came from a change handler, so don't trigger that change handler again"
我希望有人能提供一些指导。
这是关于以最佳方式利用 Aurelia Observers。
该示例是为了说明感知到的问题而编造的示例。
假设我有三个数字属性:小时、分钟、秒又名 H、M、S,我希望构建一个对象来监视 H、M、S,每次这三个变化中的任何一个发生变化时,它都会产生一个属性 T 是一个看起来像持续时间的字符串,例如。 "hh:mm:ss" 使用具有适当零填充等的 H、M、S 属性
相反,属性 T 在用户界面中绑定,因此如果用户修改 T(例如,通过输入字段),我需要将 T 的各个部分分解为它们的 H、M , S 组件并将各个值推回 H、M、S 属性。
任何人都不应忘记,此行为在 "toView" 和 "fromView" 行为方面与 Aurelia 值转换器行为非常相似,除了我们处理 1 上的 3 个属性一侧,另一侧 1 属性。
在实施方面,我可以使用 BindingEngine 或 ObserverLocator 在 H、M、S 和 T 属性上创建观察者,并订阅这些更改通知。这一点很容易。
我的问题最终是这样的:
当H发生变化时,将触发T的重新计算,因此我将向T写入一个新值。
更改 T 将触发 T 的更改通知,然后其处理程序将分解 T 的值并将新值写回 H、M、S。
写回 H、M、S 会触发另一个更改通知以生成新的 T,依此类推。
这似乎是不言而喻的 - 至少 - 当我的 "converter" 对象写入 H、M、S、T 属性时,它应该准备好自己以某种方式忽略因此,它期望的更改通知即将到来。
但是,说起来和做起来[容易]是两回事。
我的问题是——真的有必要吗?如果需要,我该如何以最简单的方式去做。这样做的障碍如下:
必须对值进行真正的更改才能生成更改通知,因此您需要提前知道自己是否 "expecting" 才能收到通知。 更困难的问题是更改通知是通过 Aurelia "synthetic" 微任务队列发出的,因此您几乎不知道何时会收到该微任务调用。
那么,这个问题有什么好的解决办法吗?有没有人真的担心这个,或者他们只是依靠第 1 点产生自我限制的结果?也就是说,可能会有几个更改通知周期,但该过程最终会默认?
实现 N 向绑定(在本例中为 4 向)的最简单方法是使用更改处理程序结合 "ignore" 标志以防止无限递归。
下面的概念与 Aurelia 框架内部解决其中一些问题的方式非常相似 - 它稳健、高效,并且达到了 "natural" 的水平。
@autoinject()
export class MyViewModel {
@bindable({ changeHandler: "hmsChanged" })
public h: string;
@bindable({ changeHandler: "hmsChanged" })
public m: string;
@bindable({ changeHandler: "hmsChanged" })
public s: string;
@bindable()
public time: string;
private ignoreHMSChanged: boolean;
private ignoreTimeChanged: boolean;
constructor(private tq: TaskQueue) { }
public hmsChanged(): void {
if (this.ignoreHMSChanged) return;
this.ignoreTimeChanged = true;
this.time = `${this.h}:${this.m}:${this.s}`;
this.tq.queueMicroTask(() => this.ignoreTimeChanged = false);
}
public timeChanged(): void {
if (this.ignoreTimeChanged) return;
this.ignoreHMSChanged = true;
const hmsParts = this.time.split(":");
this.h = hmsParts[0];
this.m = hmsParts[1];
this.s = hmsParts[2];
this.tq.queueMicroTask(() => this.ignoreHMSChanged = false);
}
}
如果您需要一个通用的解决方案,您可以在具有不同类型的 N 向绑定的多个 ViewModel 中重复使用,您可以使用 BindingBehavior(甚至可能是 2 个 ValueConverter 的组合)来实现。
但是需要在其中实现的逻辑在概念上与我上面的示例类似。
事实上,如果框架中有针对此问题的预制解决方案(例如,@bindable()
装饰器的设置),那么该解决方案的内部逻辑也会类似。您将始终需要这些标志,并通过微任务延迟它们的重置。
避免使用微任务的唯一方法是更改框架的某些内部结构,以便可以传递上下文,告诉调度程序更改通知"this change came from a change handler, so don't trigger that change handler again"