Сигнали з відкритого космосу

доповідь, в якій ви дізнаєтесь про концепцію signals, їх переваги й недоліки, а також на власні очі побачите справжню реакт-абізяну

Чому ми говоримо про сигнали?

  • Проблема: Швидкодія страждає через надмірні оновлення
  • Напрямок: Фреймворки прагнуть fine-grained оновлень
  • Рішення?: Signals — нова парадигма реактивності

Проблеми компонентної моделі

  • Будь-яка зміна стану ререндерить весь компонент (а часто і піддерево)
  • Контроль залежностей — на нашій совісті
  • Неточкові оновлення призводять до перевитрати ресурсів

В пошуках священного Ґраалю

  • Excel (1985): перші реактивні “клітинки” з формулами
  • FRP (1990-ті): модель даних як потоків зі вбудованим трекінгом
  • Knockout.js & Meteor Tracker (2010-ті): перші веб-реалізації реактивності

TL;DR

const a = signal(0);
const b = computed(() => a() + 1);
  • Сигнал — реактивний об’єкт із вбудованим трекінгом залежностей
  • Зміна сигналу оновлює лише пов’язані обчислення та UI
  • Реактивність без ручних підписок та складних налаштувань

Як це працює

  • Фіксація залежності: читання сигналу в computed/effect реєструє зв’язок
  • Інвалідація: при зміні сигналу всі залежні обчислення позначаються «dirty»
  • Ліниве оновлення: перерахунок виконується лише під час наступного читання

Що таке fine-grained оновлення?

  • Змінюється тільки той DOM-вузол, що залежить від сигналу
  • Рендер-функція компонента не виконується повторно
  • Обчислення та оновлення DOM зведено до мінімуму

Де гроші приклади, Лебовскі Бабіч?

SolidJS

function Counter() {
  const [count, setCount] = createSignal(0);

  return (
    <button onClick={() => setCount(count() + 1)}>
      Клікнуто {count()} разів
    </button>
  );
}

AngularJS

@Component({
  selector: 'app-counter',
  template: `
    <button (click)="count.set(count() + 1)">
      Count: {{ count() }}
    </button>
  `
})
export class CounterComponent {
  count = signal(0);
}

Svelte 5

<script>
  import { state, derived, effect } from 'svelte/runes';
  const count = state(0);
  const doubled = derived(() => count.value * 2);
  effect(() => console.log(`Count: ${count.value}`));
</script>

<button on:click={() => count.value++}>
  Count: {count.value}
</button>

<p>Doubled: {doubled.value}</p>

Що їх усі обʼєднує?

  • Відсутність Virtual DOM: UI оновлюється точково, лише там, де змінився стан.
  • Залежності керуються явно (Svelte) чи неявно (Solid, Angular), але завжди через реактивні звʼязки.
  • Єдина реактивна модель для локального і глобального стану — жодного поділу.

Не бажаєте ще трошки реактивности?

  • MobX: перший «сигнальний» стейт-менеджер із observable/computed/reaction
  • Qwik: поєднання сигналів із resume-ability для миттєвого відновлення UI
  • Preact Signals: fine-grained оновлення поверх Virtual DOM
  • Та інші

А шо по React?

  • Preact Signals не скасовують глобальний rerender React
  • Зайві useState/useEffect менше, але компонент все одно ререндериться
  • “Fine-grained” оновлення обмежені React-lifecycle

Реальна fine-grained реактивність можлива лише в runtime-моделях без Virtual DOM, як у Solid чи Angular.

Приклад з практики: fine-grained костиль на Recoil

const Atomic = ({atom}) => <>{useAtom(atom)}</>;

const Component = () => <>
  <h2>User name</h2>
  <p><Atomic atom={user.name} /></p>
  <h2>User title</h2>
  <p><Atomic atom={user.title} /></p>
</>;

  • Використання “атомних” компонентів замість вбудованої реактивності
  • Підхід працює лише для простого тексту, не для атрибутів чи пропсів
  • Не є системним, а радше частковим випадком fine-grained

Signals в реальних проєктах

  • Інтерактивні віджети, графіки та великі таблиці без “фризів”
  • Мінімальне навантаження на main thread: оновлюється лише змінена частина UI
  • Легке відстеження й розуміння стану навіть у складних інтерфейсах

Так, у сигналів є проблеми

  • Відлагодження вимагає спеціалізованих DevTools для візуалізації графа залежностей
  • Дуже просто зациклити залежності або наробить дурні в ефектах
  • SSR не потребує саме сигналів, тому може виникнути оверхед

Інтеграція в існуючий код

  • Signals і старі підходи можуть співіснувати у тому ж компоненті
  • Поступова міграція: впроваджуйте сигнали в критичних місцях
  • toSignal()/toObservable() — місточки між Observable та signal API
  • Якщо ви на React — сумно зітхайте, або городіть костилі

Signals в TC39

  • Stage 1 у TC39 — офіційний старт стандартизації сигналів
  • Підтримка лідерів: Angular, Preact, Solid, Ember, RxJS
  • У майбутньому Signal може з’явитися як вбудований примітив JavaScript

Що це означає для нас?

  • Signals як новий стандарт: реактивні примітиви поступово стануть фундаментом UI — так само, як Promises для асинхронності.
  • Єдина модель для всіх фреймворків: одна концепція працює в Solid, Angular, Svelte і навіть у React через Preact Signals.
  • Інвестиція в майбутнє: освоєння сигналів сьогодні спрощує перехід на нові технології та підвищує продуктивність команди завтра.

Висновки

Сигнали вводять атомарну реактивність, замість компонентних ререндерів

Fine-grained оновлення мінімізує навантаження на CPU та DOM

Сигнали вже в Solid, Angular, Svelte, Preact і на шляху в стандарт JS

Мекнув,
питайте

YouTube
Telegram