Обновить

Комментарии 10

Сигналы (signals) как примитив управления состоянием стали популярны во фронтенд-фреймворках благодаря простоте и эффективности

Если бы сигналы были просты, то не было бы столько попыток объяснить их концепцию. Или, возможно, это какая-то компания по популяризации сигналов в Angular, потому что за последний месяц это уже вторая статья. Но что бы это ни было, мне больше интересно, как вы определили, что сигналы эффективны?

Это не может быть правдой. Смотрите:

const a = 1;
const b = 2;

то же самое, но с «абстрактной» реализацией сигналов:

class Signal {
  value;
  
  constructor(initialValue) {
    this.value = initialValue;
  }
  
  get() {}
  set(value) {}
  
  subscribers = []
}

const a = new Signal(1);
const b = new Signal(2);

В самом простом случае, на одно значение приходится один вызов функции + создание двух объектов.

Предложенная вами реализация еще менее эффективна:

export const customSignal = (initValue) => {
 // замыкание
 let value = initValue;

 // аллоцируем память под Мапку 
 const watchers = new Map();

 // аллоцируем память под объект Function 
 function get(): T {
   if (watcher) { // тут ошибка, watcher-а не существует
     
     // Тут не очень ненадежно 
     const key = Math.random().toString(16);
     
     watchers.set(key, watcher);
     
     // еще аллоцируем память под масив
     watcher.deps.push([key, watchers]);
   }
   return value;
 }

 // аллоцируем память под объект Function  
 function set(newValue: T) {
   if (value !== newValue) {
     value = newValue;
     
     // на каждое изменение создается новый итератор
     for (let watcherItem of watchers.values()) {
       try {
         watcherItem();
       } catch (e) {
         console.log(e);
       }
     }
   }
 }

 // аллоцируем память под масив 
 return [get, set]; 
};

Тут нет никакой эффективности.

Сама концепция сигналов не нова и до появления в Angular уже активно использовалась в других frontend-фреймворках

Скорее сигнал, это извращенная форма acceessor-ов. Если использовать уже встроенный в язык «Сигнал», получится чуть более эффективно:

// Мономорфный класс, великолепно оптимизируется JIT-ом
class Signal {
  #value;

  #publishers = new Map;
  #subscribers = new Map;

  // переиспользуется для всех сигналов в приложении
  get value() {
    // реактивная машинерия
    // ...
    return this.#value;
  }

  // переиспользуется для всех сигналов в приложении
  set value(newValue) {
    // реактивная машинерия
    // ...
    this.#value = newValue;
  }
}

Кажется вы просто предрались к словам что сигналы просты и эффективны.

Но они просты в использовании - с этим, я думаю, что тяжело поспорить.

В решении задачи обнаружения изменения и отимизации обработки компонентов - сигналы эффективны и с точки зрения Angular, позволяют идти в сторону ухода от zonejs (и на бенчмарках можно увидеть разницу с zonejs и без)

Вы рассматриваете реализацию сигнала, приведенную в статье, с точки зрения эффективности использования памяти, но как сказано в начале статьи - реализация больше наивная - для демонстрации механики работы, а не промышленный код, который автор предлагает использовать:)

И если вас не затруднит, то расскажите про встроенные в язык сигналы, мне известно только предложение, которое еще не стандарт насколько я помню. А код приведенный вами это просто класс, который такая же функция, потому что классы это синтаксический сахар свежих стандартов ES.

Кажется вы просто предрались к словам что сигналы просты и эффективны.

Кажется Хабр не развлекательный портал, а полу-научное сообщество, и заявленное в статье нужно подтверждать пруфами.

Но они просты в использовании - с этим, я думаю, что тяжело поспорить.

Не могу согласится. Лично мне вызывать у какого-то числа метод get или set как-то неудобно, и непривычно. Но это дело вкуса.

но как сказано в начале статьи - реализация больше наивная - для демонстрации механики работы

В статье она наивна, потому что в оригинале все еще хуже. А упомянутый в статье Solid.js одумался, и отказывается от сигналов в пользу Proxy.

на бенчмарках можно увидеть разницу

Это не очень полезный бенчмарк для оценки эффективности реактивных примитивов, но ок, давайте сравним:

https://krausest.github.io/js-framework-benchmark/2025/table_chrome_141.0.7390.55.html

Как видим, Solid.js с Proxy – быстрее. Vue reactivity с Proxy – быстрее. Может показаться странным что я включил в выборку Preact + hooks, но, вот в чем дело: на третьем месте – Observable с Proxy, и он эффективнее ангуляровских сигналов! Но это не все. Для связи Observable с Preact-ом используется HOC observer, который внутри себя использует Preact-овские хуки (useState + useEffect + useLayoutEffect + useRef), и при всем этом – он быстрее ангуляровских сигналов.

И если вас не затруднит, то расскажите про встроенные в язык сигналы

Я взял слово сигналы в кавычки:

...уже встроенный в язык «Сигнал» ...

Разумеется Сигнал отсутствует как явный класс в джаваскрипте, но как я написал выше, Signal – это просто извращенная форма accessor-а. Дескриптор accessor-а выглядит так:

const descriptor: PropertyDescriptor = {
  get() {},
  set(value) {},
  // ...
}

В приведенном мной примере:

class Signal {
  #value;

  get value() {
    // ...
    return this.#value;
  }

  set value(newValue) {
    // ...
    this.#value = newValue;
  }
}

все созданные сигналы будут использовать один и ту же функцию get и set из дескриптора свойства value прототипа Signal, что гораздо эффективнее создания двух новых объектов Function для каждого сигнала.

потому что классы это синтаксический сахар свежих стандартов ES

Это не совсем так.

class IAmNotJustAFunction {
  #privateDataProperty = 1;

  static #staticPrivateDataProperty = 1;
  
  get #privateAccessor() { 
    return 1 
  }

  static #staticPrivateAccessor() {
    return 2
  }

  #privateMethod() {}

  static #staticPrivateMethod() {}

  static {
    try {
      
    } catch {
      
    }
  }
}

Это не очень полезный бенчмарк для оценки эффективности реактивных примитивов

Если вы прочтете немного внимательнее мой комментарий, то заметите что я приводил данный бенч не с целью доказать, что сигналы эффективнее всего, а то что конкретно Angular с сигналами позволяет избавится от подхода в определении изменнений через zonejs, что в свою очередь делает фреймворк эффективнее, по сравнению с этим же фреймворком, но без сигналов

заявленное в статье нужно подтверждать пруфами.

На мой взгляд в полу-научном сообществе принято сначала все-таки спросить автора с какой точки зрения он считает, что сигналы эффективны, а только потом доказывать, что это не так, на мой взгляд пазл сложился, у вас другая точка зрения под другим углом и в другой грани эффективности, что не противоречит утверждениям в статье.

Это не совсем так.

c точки зрения того что в классе все его экземпляры будут использовать функцию прототипа, да согласен удобнее, но насчет того что классы сильно экономичнее по памяти не берусь оценивать.

В статье она наивна, потому что в оригинале все еще хуже. А упомянутый в статье Solid.js одумался, и отказывается от сигналов в пользу Proxy.

А зачем врать? Почему у вас ссылка введет на пример реализации стора, а не сигнала?

Я если честно не очень понимаю тезис что А быстрее Б в сравнение разных фрейворках. Если сравнивать в рамках Solid, то получается опять сигналы быстрее.

А упомянутый в статье Solid.js одумался, и отказывается от сигналов в пользу Proxy.

Он отказался в рамках реализации стора или что значит это утверждение? Сигналы в солид все также используются

В самом простом случае, на одно значение приходится один вызов функции + создание двух объектов.

Конечно в самом простом случае, серьёзные реализации давно оптимизируют сигналы без подписчиков: значения, не используемые реактивно, не создают дополнительных структур и не инициируют уведомления.

Скорее сигнал, это извращенная форма acceessor-ов. Если использовать уже встроенный в язык «Сигнал», получится чуть более эффективно:

Аксессор (get/set) — это механизм локального переопределения поведения свойства объекта. Он не знает ничего о графе зависимостей, подписчиках, консистентности состояния и не имеет встроенной модели реактивного вычисления. Это низкоуровневая конструкция, предназначенная для инкапсуляции и контроля доступа к данным.

По сути, Ваш тезис подойдет для простого объявления переменной, но не более того. Вот если начнёте внедрять реактивность, тут аксессоры сразу сдуваются. Они не умеют строить DAG вычислений, не знают о зависимостях, не обеспечивают топологическую упорядоченность обновлений и не могут предотвратить циклы реакций.

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

Попробуйте сделать это через чистые аксессоры — получите или каскадное дублирование вызовов, или гонки обновлений, или просто непредсказуемое поведение. Любая попытка решить эти проблемы вручную превращается в реализацию того самого реактивного ядра, против которого вы, собственно, выступаете.

А ведь мы даже не затронули тему очистки ресурсов и асинхронности...

превращается в реализацию того самого реактивного ядра, против которого вы, собственно, выступаете

Я выступаю не против реактивного ядра, а против впихивания этого самого ядра в каждый отдельный атом.

Вот если начнёте внедрять реактивность, тут аксессоры сразу сдуваются.

Уже завершил. Не сдулись. Более того, справляются лучшем чем сигналы из Solid, Preact, Svelte и пр.

Они не умеют строить DAG вычислений, не знают о зависимостях, не обеспечивают топологическую упорядоченность обновлений и не могут предотвратить циклы реакций.

На мой взгляд, атом и не должен все это уметь.

Уже завершил. Не сдулись. Более того, справляются лучшем чем сигналы из Solid, Preact, Svelte и пр.

Благодарю, я посмотрю позже! :)

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Информация

Сайт
psblabdigital.ru
Дата регистрации
Дата основания
Численность
свыше 10 000 человек
Местоположение
Россия
Представитель
Наталья Низкоус