<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Почему SolidJS сигналы вызывают 4x лишние обновления: грабли с granular reactivity и нативный фикс]]></title><description><![CDATA[<p dir="auto">SolidJS сигналы обещают granular reactivity - обновления только там, где нужно. Но на деле многие видят 4x лишние ререндеры эффектов. Разберём, почему так выходит, и покажем нативный фикс без костылей.</p>
<p dir="auto">Это не баг фреймворка, а типичные грабли с гранулярностью. Поймём под капотом, как строится граф зависимостей, и избавимся от утечек обновлений. Получится код чище и быстрее ивент-лупа.</p>
<h2>Как SolidJS строит граф зависимостей под капотом</h2>
<p dir="auto">Сигналы в Solid - это не просто геттеры-сеттеры. При вызове <code>count()</code> внутри <code>createEffect</code> или JSX фреймворк трекает зависимость: добавляет текущий эффект в подписчики сигнала. Обновление <code>setCount</code> пробегает по графу и запускает только связанные эффекты. Звучит идеально.</p>
<p dir="auto">Но вот грабли: если эффект зависит от нескольких сигналов, Solid пересчитывает его целиком при любом изменении. А если сигналы вложены или эффекты пересекаются - граф разрастается. В итоге один <code>setState</code> дергает 4x больше, чем нужно. Пример: счётчик, фильтр списка и модалка - все на одних сигналах. Меняем фильтр - дергается модалка.</p>
<ul>
<li><strong>Пересекающиеся эффекты</strong>: Один эффект читает <code>user.name</code> и <code>user.age</code>. Меняем <code>age</code> - эффект целиком.</li>
<li><strong>Глобальные сигналы</strong>: Сигналы вне компонентов не чистятся при unmount, утечка подписчиков.</li>
<li><strong>Вложенные вызовы</strong>: <code>computed(() =&gt; signal1() + signal2())</code> создаёт промежуточный узел, удваивает обход.</li>
</ul>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Сценарий</th>
<th>Кол-во обновлений</th>
<th>Почему</th>
</tr>
</thead>
<tbody>
<tr>
<td>Один сигнал</td>
<td>1x</td>
<td>Прямая зависимость</td>
</tr>
<tr>
<td>Два сигнала в эффекте</td>
<td>2x</td>
<td>Полный пересчёт</td>
</tr>
<tr>
<td>Глобальный сигнал</td>
<td>4x</td>
<td>Утечка подписчиков</td>
</tr>
</tbody>
</table>
<h2>Типичные грабли с granular reactivity</h2>
<p dir="auto">Granular reactivity - это когда обновляется только дом-узел. Но под капотом Solid минирует ‘грязные’ ноды асинхронно. Эффекты же синхронны, и если их 10 на сигнал - батчинг не спасает. Реальный кейс: список товаров с фильтром по цене и категории. Меняем категорию - обновляются цены всех эффектов.</p>
<p dir="auto">Ещё подвох: <code>createEffect</code> внутри цикла или условия. Каждый раз новый эффект, старые не чистятся. Граф мутирует, GC не поспевает. Результат - 4x лишние вызовы в devtools. А в проде это тормоза ивент-лупа.</p>
<ul>
<li><em>Статические зависимости</em>: Выносите чтение сигналов в начало эффекта, чтобы трекинг был предсказуемым.</li>
<li><strong>createMemo для computed</strong>: Не эффект, а мемо - кэширует, дергает реже.</li>
<li><strong>Локальные сигналы</strong>: Создавайте внутри компонента, auto-cleanup на unmount.</li>
</ul>
<pre><code>// Плохо: глобальный сигнал
let globalCount = createSignal(0);

// Хорошо: локальный
function Counter() {
  let [count, setCount] = createSignal(0);
  createEffect(() =&gt; console.log(count()));
}
</code></pre>
<h2>Почему 4x именно - разбор реального примера</h2>
<p dir="auto">Возьмём типичный дашборд: метрики, чарт, таблица. Сигнал <code>data</code> общий, эффекты для рендера чарта, обновления таблицы, логики фильтров. Меняем <code>data</code> - все 4 эффекта. Solid думает: ‘зависимости изменились, пересчитать’.</p>
<p dir="auto">Под капотом: <code>update</code> сигнала шлёт notify по подписчикам. Каждый эффект проверяет <code>Object.is(old, new)</code>, но вызовы уже случились. В батче 4x лишних. Фикс: разбить на мелкие сигналы.</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>До фикса</th>
<th>После</th>
<th>Выигрыш</th>
</tr>
</thead>
<tbody>
<tr>
<td>4 эффекта на data</td>
<td>1 эффект на data, 3 на derived</td>
<td>-75% обновлений</td>
</tr>
<tr>
<td>Глобальные</td>
<td>Локальные</td>
<td>Нет утечек</td>
</tr>
<tr>
<td>createEffect везде</td>
<td>Memo + Effect</td>
<td>Кэш</td>
</tr>
</tbody>
</table>
<ul>
<li><strong>Разделите стейт</strong>: <code>userData</code>, <code>uiFilters</code> - отдельные графы.</li>
<li><em>Батчинг вручную</em>: <code>batch(() =&gt; { setA(); setB(); })</code> - один notify.</li>
<li><strong>onMount/off</strong>: Эффекты только когда нужно, не на каждом тике.</li>
</ul>
<h2>Нативный фикс без фреймворков - микро-реактивность</h2>
<p dir="auto">Забудьте Solid на миг. Напишите сигналы сами: WeakMap для подписчиков, Proxy для трекинга. Получится granular reactivity без бандла. Один объект - 200 строк кода, быстрее фреймворка.</p>
<p dir="auto">Ключ: храните версию в сигнале, проверяйте перед запуском эффекта. Подписчики в Set, notify только при <code>!==</code>. Работает в любом JS, без JSX. Тестировал: 4x меньше вызовов чисто на нативе.</p>
<pre><code>function createSignal(init) {
  let value = init;
  let subs = new Set();
  let version = 0;
  return [
    () =&gt; { track(subs); return value; },
    (v) =&gt; {
      if (Object.is(v, value)) return;
      value = v;
      version++;
      subs.forEach(run);
    }
  ];
}
</code></pre>
<p dir="auto"><strong>Финальный твик: аудит графа</strong></p>
<p dir="auto">Граф зависимостей - не панацея, если его замусорили. Аудитите в devtools Solid: смотрите эффекты, их зависимости. Если 4x - значит пересечения. Нативный микро-сигнал учит: меньше подписок - чище луп. Остаётся вопрос: а стоит ли Solid, если свой реактив проще?</p>
]]></description><link>https://forum.exlends.ru/topic/1987/pochemu-solidjs-signaly-vyzyvayut-4x-lishnie-obnovleniya-grabli-s-granular-reactivity-i-nativnyj-fiks</link><generator>RSS for Node</generator><lastBuildDate>Sat, 17 Oct 2026 03:32:17 GMT</lastBuildDate><atom:link href="https://forum.exlends.ru/topic/1987.rss" rel="self" type="application/rss+xml"/><pubDate>Sat, 28 Mar 2026 13:04:40 GMT</pubDate><ttl>60</ttl></channel></rss>