Бу! Испугался? Не бойся. Angular 19 уже не за горами и представляет новый мощный примитив, называемый linkedSignal, который поможет вам управлять сложным состоянием в ваших приложениях. Это альтернатива использованию effect для простого обновления сигнала на основе изменения другого сигнала. В сообществе не все были согласны (впрочем, как и всегда), некоторые продвигали идею использовать computed вместо effectдля сброса сигналов на основе изменения другого сигнала.

linkedSignal — это функция, призванная упростить этот процесс, она позволяет связывать сигналы, чтобы их значения автоматически синхронизировались на основе зависимости. Это полезно для создания реактивных данных, когда одно значение должно автоматически обновляться при изменении другого.

Рассмотрим пример

Допустим у нас есть сигнал, который представляет количество рублей на счёте пользователя:

const amountRubles = signal(700);

Теперь создадим сигнал, который автоматически будет пересчитывать наше значение в юани:

const amountYuan = linkedSignal(() => amountRubles() / 13.63);

amountRubles и amountYuan возвращают WritableSignal, это расширенный интерфейс сигнала, который позволяет устанавливать/обновлять значения и делать их только для чтения.

Этот же код, но написанный через effect:

const amountYuan = signal(amountRubles() / 13.63);
effect(() => {
  amountYuan.set(amountRubles() / 13.63);
});

Результат при изменении основного сигнала такой же, связанный автоматически изменится:

console.log(amountRubles()); // 700
console.log(amountYuan()); // 51.35

amountRubles.set(1000);

console.log(amountRubles()); // 1000
console.log(amountYuan()); // 73.36

Существуют ситуации, когда нужна возможность задать более гибкие настройки. linkedSignal позволяет передать объект, в котором будет прописана логика обновления:

selectedCity = signal('Москва');
weatherSignal = linkedSignal({
    source: () => this.selectedCity(), // Базовый сигнал (источник)
    computation: (city, previous) => {
      if (previous?.source === city && previous?.value) {
        // Если город не изменился, используем предыдущее значение
        return previous.value;
      }
      // Получение прогноза из API при изменении города
      return this.fetchWeather(city);
    },
    equal: (newVal, oldVal) => newVal.temperature === oldVal.temperature
    // equal предотвращает лишние обновления, если температура осталась прежней
  });

Этот подход позволяет создать linkedSignal, который автоматически обновляется, только если изменяется температура для выбранного города, что минимизирует ненужные перерисовки.

Преимущества

  • Автоматическая синхронизация данных
    При изменении одного состояния, все связанные с ним данные обновляются автоматически. Это позволяет избежать «ручных» пересчётов и синхронизации, что снижает вероятность ошибок

  • Реактивность без дополнительных зависимостей
    linkedSignal делает код более прямолинейным, так как позволяет работать с реактивными данными без традиционного RxJS

  • Упрощение логики и повышение читаемости
    Использование linkedSignal снижает количество кода для обработки реактивности. Когда зависимости декларативно указаны в функции linkedSignal, код становится чище и легче в поддержке, так как видно, какие именно значения влияют на связанный сигнал

  • Повышение производительности
    linkedSignal оптимизирует вычисления, вызывая пересчёт только в тех местах, где действительно изменилось состояние. Это снижает нагрузку на приложение по сравнению с неэффективными пересчётами

Комментарии (2)


  1. inmativ
    31.10.2024 07:26

    В rxjs есть два способа управления потоком - императивный через подписки и субьекты, и декларативный через pipe и разные операторы. Когда эти два подхода смешиваются, то получается нечитаемая лапша, подписки в подписках, подписки, в которых делают next субъекты и прочее непотребство.

    Особенно много боли возникает, когда из сервиса торчит публичный behaviorSubject. Проблема behSubject в том, что у него есть несколько способов записи значения (initial и next), а так же несколько способов чтения. И при развитии кода он становится мини god-объектом, на котором все завязывается. Половина проекта в него пишет, а другая половина читает. Дебажить и рефакторить это невозможно.

    Когда только появились сигналы и было запрещено в effect и compute делать next, то я очень обрадовался. Это бывало неудобно, но принуждало писать хороший незапутанный код.

    А linkedSignal - ещё хуже, чем behSubject. Потому что он кроме initial и set имеет ещё один источник изменений. С linkedSignal - мы вообще рехнёмся, потому что там никаких концов уже не сыщешь. В этом графе всё будет переплетаться.

    Спасёт ситуацию только если они какой-нибудь крутой дебагер для этого дела напишут.

    Я бы забанил linkedSignal линтером на первое время.

    Ангуляр пытается внутри сигналов решить многие проблемы. Чтобы наружу торчало простое api. Если им удастся реализовать всю эту машинерию так, чтобы это было безопасно использовать, без мучительно отладки, то сигналы - это, конечно, очень удобно. И linkedSignal тоже. Но я пока не понимаю, как его дебажить.

    Он очень удобен. Но это удобство такого же рода, как и глобальные переменные, например. Это очень удобно, пока тебе не надо понимать, что вообще тут происходит.


    1. wdhappyk
      31.10.2024 07:26

      Судя по всему данный механизм введён просто из-за проблем реакции на изменения в effect, который вызывается не синхронно и пропускает все установленные значения в рамках одного синхронного куска кода и соответственно не обновляет зависимый сигнал (в примерах выше мы видим, что теперь такой проблемы нет). В общем расписать связку значений становится довольно мучительно. Мы в своём проекте решили, что signal будем использовать исключительно для тех данных, которые выводятся в шаблон. Но и это на самом деле не совсем решает проблему, т.к. приведение потоков к сигналами тоже работает не всегда как ожидается, т.е. например значение в сигнал может не попасть 1 раз 100 или вообще поток может преобразоваться в сигнал так, что значения не идут совсем. В таких случаях приходится переходить на новые решения, т.к. обернуть старое в toSignal = сломать.