Главная / Статьи / Svelte 5 Runes и SolidJS Signals: обновление интерфейса без повторной отрисовки

Svelte 5 Runes и SolidJS Signals: обновление интерфейса без повторной отрисовки

Посмотрите, как Svelte 5 компилирует руны и SolidJS связывает сигналы для прямой обновления DOM, в чем это отличается от React и Angular, и когда стоит перейти на него.

1774 слов

Каждый разработчик на React рано или поздно сталкивается с необходимостью объяснить, почему компонент отрисовывается четыре раза, хотя ничего видимого не менялось, и почему для решения проблемы требуются memo, массив зависимостей и стабильная функция-обратный вызов. Svelte и SolidJS исходят из другой предпосылки: если фреймворк точно знает, какая часть состояния влияет на конкретный элемент DOM, он может обновить только этот элемент и вообще избежать повторной обработки компонентов. В этой статье объясняется, как каждый из них достигает этого, как выглядит код на практике, в чем их модель отличается от React и Angular, и как решить, стоит ли использовать тот или иной фреймворк в вашем следующем проекте.

Виртуальный DOM — это средство, а не цель

Основной идеей React при его запуске в 2013 году был виртуальный DOM. Интерфейс описывается как функция состояния. Когда состояние меняется, React снова вызывает ваш компонент, создает новое дерево в памяти, сравнивает его с предыдущим и применяет изменения только к реальному DOM.

Такая архитектура делает интерфейсы гораздо предсказуемее, чем ручная обработка DOM, и остается эффективной моделью. Однако у нее есть и недостатки. Функция компонента выполняется заново независимо от того, нужны ли изменения в ее выводе; выделяется новое дерево, и происходит сравнение для определения фактических изменений, причем в худшем случае все это делается для обновления всего лишь одного элемента <span>. Если вы хотите узнать подробности о том, что сравнивает механизм согласования и почему, статья о том, как работает сравнение виртуального DOM рассматривает эти аспекты.

Большая часть API React с тех пор — memo, useMemo, useCallback, а также недавно появившийся React Compiler — предназначена для избежания работы, которую в противном случае выполняла бы модель отрисовки и сравнения. Компилятор автоматизирует процесс мемоизации, позволяя разработчикам написать меньше кода вручную, но основная модель остается прежней: компоненты перезапускаются, а оптимизация заключается в том, чтобы убедить их этого не делать. Статья о том, что оптимизирует React Compiler и что оставляет на вашей ответственности рассматривает эти ограничения.

Svelte и Solid ставят более простой вопрос: что, если зависимость между каждым элементом состояния и каждым узлом DOM была бы точно известна, так что при изменении состояния обрабатывался бы только соответствующий узел?

Svelte: компилятор, который пишет код обновления за вас

Svelte в первую очередь является компилятором. Вы создаете компоненты в файлах с расширением .svelte, и во время сборки Svelte преобразует их в обычный JavaScript, который напрямую управляет DOM. В нем нет виртуального DOM и механизма сравнения на этапе выполнения; в браузер отправляется только небольшой набор кода для работы.

Начиная с Svelte 5, реактивность реализуется с помощью рун — специальных примитивов, распознаваемых компилятором. В приведённом ниже примере компонента объявляется состояние и значение, получаемое из него, после чего оба элемента отображаются в виде кнопки:

<script>
  let count = $state(0);
  let doubled = $derived(count * 2);
</script>
<button onclick={() => count++}>
  {count} doubled is {doubled}
</button>

Это весь компонент. Здесь нет функции-установщика и массива зависимостей. Вы увеличиваете count, как будто это обычная переменная, и поскольку компилятор уже проанализировал, какие части маркипада используют count и doubled, он генерирует код, который обновляет именно эти текстовые узлы. $derived пересчитывается только тогда, когда меняется то, что он считывает. Обратите внимание, что обработчики событий в Svelte 5 представляют собой обычные атрибуты, такие как onclick, что заменяет старую синтаксис-директиву on:click.

Почему команды его любят

  • Меньше кода для того же результата. Без хуков, функций-установщиков или обёрточных компонентов компоненты Svelte обычно оказываются значительно короче по сравнению с их аналогами в React. Меньше кода означает меньше возможностей для ошибок и более быстрый процесс проверки.
  • Небольшие пакеты. Поскольку большая часть работы выполняется во время компиляции, затраты фреймворка в браузере невелики. Для сайтов с контентом, лендинг-страниц и любых проектов, где важна скорость первой загрузки, это является реальным коммерческим преимуществом.
  • Знакомые элементы веб-разработки. Маркировка, стили и скрипты находятся в одном файле и напоминают по формату HTML, CSS и JavaScript. Нет специфических для JSX конвенций, таких как className, что делает файлы понятными для дизайнеров и рецензентов, которые не пишут на React.
  • Для полноценных приложений SvelteKit добавляет маршрутизацию, серверную отрисовку и API-эндпоинты, выполняя ту же роль, что Next.js для React, при этом отличаясь более простой настройкой.

    Существует один важный момент, о котором стоит знать заранее: руны являются особенностями компилятора, поэтому они работают только в файлах с расширением .svelte и в модулях, названных с суффиксами .svelte.js или .svelte.ts. Перенос реактивной логики в обычный файл с расширением .js не будет работать без такого названия.

    SolidJS: JSX, выполняющийся один раз

    На первый взгляд Solid легко спутать с React. Он использует JSX, составляет небольшие функции, а счётчик выглядит практически идентично:

    function Counter() {
      const [count, setCount] = createSignal(0);
      return (
        <button onClick={() => setCount(count() + 1)}>
          Count: {count()}
        </button>
      );
    }
    

    То, что удивляет разработчиков React, — это то, что Counter выполняется ровно один раз. Solid основан на тонкой реактивности с использованием сигналов. Функция createSignal возвращает геттер и сеттер, причем геттер count() представляет собой вызов функции, а не простое значение. Когда JSX читает count() внутри выражения, Solid фиксирует, что этот конкретный текстовый узел зависит от данного сигнала. Последующий вызов setCount обновляет именно этот текстовый узел, ничего более. Функция компонента служила лишь шагом настройки, соединяющим сигналы с узлами DOM; она больше никогда не выполняется, поэтому нет ничего, что требовало бы перерисовки.

    Именно благодаря этой модели Solid демонстрирует результаты на уровне или близкие к лучшим среди тестов общепринятых фреймворков, часто сопоставимые с результатами написанного вручную стандартного JavaScript. Рассматривайте любые рейтинги тестов как моментальную картину ситуации и оценивайте свою собственную нагрузку, однако архитектурное преимущество реально: затраты на обновления пропорциональны объему изменений, а не размеру дерева компонентов.

    Почему команды его любят

    • Отсутствие концепции повторной отрисовки. Вопрос о том, почему что-то отрисовывается заново, в React не возникает. У функций useMemo, useCallback и React.memo нет аналогов, поскольку пропускать что-либо не нужно.
  • Прогнозируемые эффекты. Эффект срабатывает тогда, когда меняется сигнал, который он считывает, а не тогда, когда компонент случайно перерисовывается и массив зависимостей это позволяет. Проблема устаревших замыканий, хорошо известная ловушка React, в значительной степени исчезает, поскольку значения всегда считываются свежими через геттеры.
  • Плавный переход от React. JSX и композиция компонентов передаются практически без изменений, поэтому команде, работающей с React, в основном нужно забыть неприятные аспекты, а не переучиваться всему заново.
  • Привычки, которые необходимо отказаться использовать

    Модель однократного использования имеет последствия, которые мешают новичкам. Разбор пропсов в верхней части компонента происходит один раз, что нарушает реактивность, поэтому к пропсам обычно обращаются через props.name. Ранние возвраты и тернарные операторы в теле функции оцениваются только один раз, именно поэтому Solid предоставляет компоненты управления потоком выполнения, такие как Show и For. Как только эти правила становятся понятными, они остаются последовательными, но именно они являются основной причиной ошибок у разработчиков, переходящих из React.

    Как это сравнивается с React и Angular

    Более глубокая разница заключается в философии, а не в синтаксисе.

    React переработал свою основную модель с функциями «перезапуск и сравнение изменений» и на протяжении многих лет разрабатывал инструменты для снижения затрат, связанных с этой моделью. Он по-прежнему остается отличным выбором: его экосистема непревзойденна, найм специалистов происходит легко, а компилятор React действительно сокращает необходимость вручную выполняемой мемоизации. Однако есть недостаток — работа ведется в рамках модели, разработанной с учетом ограничений того времени.

    Angular — это полнофункциональный вариант для крупных предприятий, оснащенный встроенной системой внедрения зависимостей, библиотекой RxJS и строгими стандартами практически для всего. Он хорошо справляется с очень большими кодовыми базами, но требует значительных усилий для освоения и соблюдения определенных правил. Его самые значимые недавние изменения, такие как механизмы signals и беззонная проверка изменений, приближают его к более тонкой реактивности, характерной для фреймворка Solid.

    Это сближение заметно во всей индустрии. Angular принял концепцию сигналов, React ввел компилятор на этапе сборки, более новые фреймворки, такие как Qwik, основаны на тонкой реактивности, а Vue, чьи реактивные объекты всегда были близки к сигналам, исследует собственные стратегии компиляции. Было бы преувеличением утверждать, что Svelte и Solid изобрели каждую из этих идей, но они рано и ясно показали, что компиляция и сигналы могут обеспечить функциональность целого фреймворка.

    Почему разработчики продолжают двигаться в этом направлении

    Три не таких уж привлекательных причины объясняют большую часть интереса:

    • Меньше информации о фреймворке, которую нужно запоминать. Внимание сосредотачивается на продукте, а не на семантике обновлений фреймворка. Правильное хранение в кэше функций-ответов не считается значимой работой.
  • Высокая производительность по умолчанию. В React или Angular быстрая работа приложения — это результат тщательной разработки. В Svelte или Solid замедление работы приложения обычно требует значительных усилий.
  • Уважение к вашему времени. Меньше API, меньше шаблонного кода и меньше подводных камней. Опросы разработчиков неоднократно показывают высокий уровень удовлетворенности и интереса к этим фреймворкам, при этом их общее использование продолжает расти на относительно небольшой базе.
  • Стоит ли перейти?

    Для существующего продукта, скорее всего, не сразу. Когда уже существует крупное приложение на React или Angular, и команда хорошо разбирается в этой технологической стеке, переписывание кода — один из самых надежных способов замедлить разработку проекта. Важен также размер экосистемы: у React есть библиотеки для практически любых задач, и хотя экосистемы Svelte и Solid развиваются активно, они меньше по размеру, поэтому заранее убедитесь, что необходимые компоненты, такие как библиотеки компонентов, инструменты для форм и интеграции аутентификации, существуют и находятся в активном обслуживании.

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

    • Выберите Svelte, если вам нужна наименее сложная навигация при обучении и решение, которое в основном работает без проблем.
  • Выберите Solid, если ваша команда работает с JSX и стремится к максимальной производительности во время выполнения с использованием подхода, аналогичного React.
  • Оставайтесь с React или Angular, когда ширина экосистемы, возможности найма специалистов и существующий уровень экспертизы имеют большее значение, чем простая эффективность.
  • Основные выводы

    • Модель отрисовки и сравнения компонентов в React предсказуема, но объем работы пропорционален структуре дерева компонентов; мемоизация и React Compiler сокращают этот объем работы без изменения самой модели.
    • Svelte 5 перемещает механизм реактивности в компилятор с помощью таких элементов, как $state и $derived, что позволяет осуществлять прямые обновления DOM и сокращать размер библиотеки во время выполнения.
    • Solid выполняет каждый компонент один раз и связывает сигналы непосредственно с узлами DOM, что исключает повторную отрисовку, но требует новых подходов к использованию параметров и управлению потоком выполнения.
    • Более широкая экосистема движется к одинаковым идеям: анализ на этапе компиляции, использование сигналов вместо повторной отрисовки и прямое обновление вместо сравнения изменений.
    • Используйте эти фреймворки там, где их преимущества важны и экосистема соответствует вашим потребностям; не переписывайте работающую базу кода только ради следования трендам.