Лучший способ игнорировать ваши собственные изменения с помощью Aurelia Property Observer

Я надеюсь, что кто-то может предложить некоторые рекомендации.

Речь идет об использовании наблюдателей Aurelia в лучшем виде.

Пример представляет собой выдуманный пример, иллюстрирующий воспринимаемую проблему.

Допустим, у меня есть три свойства числа: часы, минуты, секунды, также известные как H, M, S, и я хочу построить объект, который контролирует H, M, S, и каждый раз, когда любое из этих трех изменений, он создает свойство T, которое является строкой это выглядит как продолжительность, например. "чч: мм: сс" с использованием свойств H, M, S с соответствующим заполнением нулями и т. д.

И наоборот, свойство T связано с пользовательским интерфейсом, и поэтому, если пользователь изменяет T (например, через поле ввода), мне нужно разложить части T на их компоненты H, M, S и отодвинуть отдельные значения обратно. в свойствах H, M, S.

Никто не должен забывать, что это поведение удивительно похоже на поведение конвертера значений Aurelia с точки зрения поведения "toView" и "fromView", за исключением того, что мы имеем дело с 3 свойствами с одной стороны и 1 свойством с другой стороны,

Что касается реализации, я могу использовать BindingEngine или ObserverLocator для создания наблюдателей на свойствах H, M, S и T и подписки на эти уведомления об изменениях. Легко peasy для этого бита.

Мой вопрос в конечном итоге это:

Когда H изменится, это вызовет пересчет T, поэтому я напишу новое значение в T.

Изменение T вызовет уведомление об изменении для T, чей обработчик затем разложит значение T и запишет новые значения обратно в H, M, S.

Обратная запись в H, M, S запускает другое уведомление об изменении для создания нового T и так далее.

Кажется самоочевидным, что, по крайней мере, ЖЕЛАТЕЛЬНО, что когда мой объект-конвертер записывает свойства H, M, S, T, он должен каким-то образом подготовиться к игнорированию уведомления об изменениях, которое, как он ожидает, будет быть предстоящим, как следствие.

Однако [легкость] сказать это и сделать это две разные вещи.

Мой вопрос - действительно ли это вообще необходимо? Если это желательно, как мне сделать это как можно проще. Препятствия для этого заключаются в следующем:

Чтобы создать уведомление об изменении, должно быть реальное изменение значения, поэтому вам нужно заранее знать, ожидаете ли вы его получить. Гораздо более сложная проблема заключается в том, что уведомления об изменениях отправляются через "синтетическую" очередь микро-задач Aurelia, поэтому вы очень не знаете, когда получите этот вызов микро-задачи.

Итак, есть ли хорошее решение этой проблемы? Кто-нибудь на самом деле беспокоится об этом, или они просто полагаются на пункт 1, дающий самоограничивающий результат? То есть может быть несколько циклов уведомлений об изменениях, но процесс в конечном итоге согласится?

1 ответ

Решение

Самым простым способом реализации N-way привязки (в данном случае 4-way) является использование обработчиков изменений в сочетании с флагами "ignore" для предотвращения бесконечной рекурсии.

Приведенная ниже концепция очень похожа на то, как некоторые из этих проблем решаются внутри системы Aurelia - она ​​надежна, эффективна и примерно настолько же "естественна", насколько это возможно.

@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);
    }
}

Если вам нужно универсальное решение, которое вы можете повторно использовать в нескольких ViewModel, где у вас есть разные виды N-way связываний, вы можете сделать это с BindingBehavior (и, возможно, даже с комбинацией 2 ValueConverters).

Но логика, которая должна быть реализована в них, была бы концептуально похожа на мой пример выше.

На самом деле, если в рамках уже было готовое решение этой проблемы (скажем, например, настройка @bindable() декоратор), то внутренняя логика этого решения также будет аналогичной. Вы всегда будете нуждаться в этих флагах так же как задержать их сброс через микро задачу.

Единственный способ избежать использования микро-задачи - это изменить некоторые внутренние компоненты инфраструктуры таким образом, чтобы можно было передать контекст, который может сообщить диспетчеру уведомлений об изменениях: "это изменение произошло из обработчика изменений, поэтому не инициируйте это изменение. снова обработчик

Другие вопросы по тегам