使用 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"