Сегодня во фронтенде многое кажется очевидным: интерфейс обновляется при изменении данных, код удобно разбивается на части, архитектура уже встроена в инструменты. Но раньше всё это приходилось писать вручную, и каждое решение рождалось из конкретной боли разработчиков.
Если разобраться, откуда взялись подходы, с фреймворками проще работать:
Понятно, что делает инструмент, а что нужно настраивать вручную.
Меньше ситуаций, когда поведение кажется неправильным, хотя всё работает по логике самого фреймворка.
Легче читать чужой код, особенно в проектах с разными архитектурными слоями, которые развивались годами. Без понимания контекста легко начать рефакторить под себя и сломать то, что уже работает.
Новые инструменты осваиваются быстрее — они решают те же задачи, но другим способом.
Мы подготовили практический разбор, который поможет увидеть структуру за интерфейсом: почему фреймворки устроены именно так и что из старых решений до сих пор влияет на код.
А в конце — несколько кейсов, где знание контекста сразу становится полезным.
Для навигации:
Кратко: что такое фронтенд-фреймворк
С чего началась архитектура во фронтенде
Как AngularJS автоматизировал работу с интерфейсом
Что изменилось с появлением React
Какие подходы развивают Vue, Svelte и Solid
Как знание истории помогает в работе: разбираем кейсы

Владимир Языков
Фронтенд-разработчик, преподаватель, автор телеграм-канала Useful Web. Консультировал при подготовке этого материала
Кратко: что такое фронтенд-фреймворк
Фронтенд-фреймворк — это инструмент, который помогает структурировать интерфейс: упростить работу с данными и DOM, разбить код на части и сделать его удобнее для поддержки.
Фреймворки стали логичным этапом, когда интерфейсы усложнились: с динамическими данными, интерактивными элементами и логикой, которую уже не получалось держать в голове. До их появления всю логику писали на ванильном JavaScript или с помощью jQuery. Общей структуры не было, и архитектуру каждый выстраивал по-своему — из-за этого проекты часто разрастались в спагетти-код.
Например, при изменении данных нужно было вручную найти нужный элемент через document.querySelector
, поменять его содержимое через innerText
, пересоздать вложенные элементы и заново назначить обработчики событий. Любая мелкая правка тянула за собой изменения в разных частях кода.
Современные фреймворки решили эту проблему: достаточно изменить данные — и инструмент сам обновит нужные части DOM, сохранив порядок и структуру.

Часто фреймворки путают с библиотеками, и не всегда понятно, в чём разница. Формально библиотека — это набор функций, которые вызывает разработчик. А фреймворк сам управляет тем, когда и как запускать код.
Но на практике всё не так строго. React обычно называют библиотекой, потому что он отвечает только за отображение. Но его часто используют как фреймворк: добавляют роутер, управление состоянием и строят архитектуру вокруг компонентов. Vue, наоборот, считается фреймворком, но его легко подключить точечно — просто для реактивного интерфейса.
Поэтому чёткой границы нет — всё зависит от того, как используется инструмент.
Базу освежили, теперь перейдём к разбору.
С чего началась архитектура во фронтенде
Когда интерфейсы стали вести себя как приложения, стало понятно, что одного jQuery уже недостаточно. Нужно было как-то разделять, где хранятся данные, как они связаны с отображением, как переключается состояние без перезагрузки.
В 2010 году появился Backbone.js — один из первых инструментов, предложивших структуру. Он не задавал архитектуру целиком, но дал разработчику опору: роли и принципы, вокруг которых стало проще выстраивать интерфейс.
Как устроен фреймворк
Backbone.js опирался на принципы MVC: разделение данных, представления и логики. Но эту модель адаптировали под задачи фронтенда, и вместо трёх частей появилось четыре — Model
, Collection
, View
и Router
.
Model
описывала данные — например, одного пользователя, задачу или комментарий. В ней задавались поля и логика работы с сервером. Методы fetch()
и save()
позволяли отправлять и получать данные без ручного написания запросов. Если серверное API работало нестандартно, поведение можно было настроить через метод sync()
.
Например, чтобы получить данные пользователя с сервера, достаточно было создать модель и вызвать метод fetch()
:
// Создаём модель пользователя
var User = Backbone.Model.extend({
urlRoot: '/users'
});
// Создаём экземпляр и загружаем данные
var user = new User({ id: 1 });
user.fetch({
success: function(model) {
console.log(model.get('name'));
}
});
Collection
объединяла несколько моделей одного типа, например список задач. Она включала методы для фильтрации, сортировки и поиска, а также могла загружать данные с сервера. Её часто использовали вместе с View, чтобы отрисовать списки.
View
отвечала за отображение и поведение интерфейса: клики, ввод, наведение. Она могла отслеживать изменения в модели: разработчик вручную настраивал подписку на событие и описывал, что должно произойти, — например, вызов render()
, чтобы обновить DOM. Автоматического обновления интерфейса не было — всё приходилось прописывать самостоятельно.
Во View
не было встроенного способа формирования HTML — шаблоны подключались отдельно. Обычно использовали Handlebars или шаблонизатор из Underscore, чтобы описать, как должны выглядеть элементы интерфейса.
Компоненты взаимодействовали между собой через события: модель могла сообщить об изменении, а View
— отреагировать и выполнить нужное действие.
Router
отслеживал изменения в URL и управлял состоянием интерфейса. Это позволяло создавать одностраничные приложения: при переходе по ссылке страница не перезагружалась, обновлялся только нужный участок.

Что делает фреймворк, а что — разработчик
Backbone задавал структуру, но не управлял тем, как части приложения работают вместе. Он обозначал роли, а дальше всё связывал разработчик.
Если в модели что-то менялось, интерфейс не обновлялся сам. Нужно было подписаться на событие, вызвать render()
и обновить нужный фрагмент. Шаблоны подключались отдельно, и, чтобы они начали работать с данными, всё приходилось настраивать вручную.
Зато такой подход легко встраивался в существующий проект. Не нужно было менять архитектуру — достаточно просто подключить Backbone под конкретную задачу. Например, так в Trello с его помощью управляли карточками и досками, а в SoundCloud подгружали треки и обновляли интерфейс без перезагрузки страницы.
Это давало гибкость, но чем больше становилось приложение, тем сложнее было удерживать все связи. Где-то забыли подписку, где-то не обновили шаблон — и на экране оставались старые данные. Не из-за ошибки в коде, а потому что фреймворк сам за это не отвечал.
Вывод: почему фреймворк работает именно так
Backbone появился в момент, когда у фронтенда ещё не было общей структуры — только скрипты, события и логика, собранная вручную. Он ввёл базовые роли, которые помогли структурировать код и задали схему для построения интерфейса.
При этом фреймворк не управлял поведением: шаблоны, обновления, связи между слоями — всё оставалось на стороне разработчика. Это не считалось ограничением — тогда было важно встроиться в существующий код и ничего не сломать.
Сегодня Backbone почти не используется в новых проектах, но его можно встретить в старом коде.
Как AngularJS автоматизировал работу с интерфейсом
AngularJS выпустили в 2010 году — в то же время, когда начали появляться первые инструменты для организации фронтенд-кода. Но если другие решения предлагали только структуру, Angular включал сразу всё: шаблоны, роутинг, формы, модули, внедрение зависимостей. Это был первый фреймворк, который закрывал базовые задачи фронтенда из коробки.
В отличие от Backbone.js, подход был такой: не просто разложить код по ролям, а встроить поведение интерфейса прямо в систему.
Как устроен фреймворк
В основе AngularJS была архитектура, близкая к MVVM — подходу, где данные (Model), шаблон интерфейса (View) и логика, которая связывает одно с другим (ViewModel), вынесены в отдельные части. Разработчик описывал, с какими данными работает компонент, а фреймворк сам решал, что и когда нужно обновить в DOM.
Чтобы это работало, Angular использовал механизм digest cycle. После каждого изменения он проходился по шаблону — по HTML с привязками — и проверял, что изменилось. Если находил отличия, обновлял только нужные участки DOM.
Благодаря этому в Angular появилась одна из главных фишек — двусторонняя привязка. Если пользователь вводил значение в поле, оно сразу попадало в переменную. А если переменная менялась в коде, значение обновлялось в поле.
Например, чтобы связать поле ввода и текст на странице, в AngularJS достаточно было указать привязку через ng-model
:
<input ng-model="name">
<p>{{ name }}</p>
Архитектурно это работало через постоянный цикл проверок и обновлений между моделью и представлением:

Кроме двусторонней привязки данных, фреймворк включал шаблонизатор, маршрутизацию, систему модулей и встроенное внедрение зависимостей. Всё это входило в стандартную сборку и задавало структуру проекта. За счёт этого команде было проще ориентироваться в коде.
Что делает фреймворк, а что — разработчик
AngularJS снимал с разработчика много рутины: сам отслеживал изменения и обновлял интерфейс. Но это работало только внутри самого фреймворка.
Если данные приходили из стороннего кода — например, по таймеру или из внешней библиотеки, — Angular мог их просто не заметить. Чтобы такие изменения отразились в интерфейсе, приходилось вручную запускать цикл обновлений — через $apply()
.
Когда значение менялось по таймеру, код выглядел так:
setTimeout(function() {
$scope.$apply(function() {
$scope.value = 'Новое значение';
});
}, 1000);
В небольших проектах это почти не мешало. Но в более сложных интерфейсах поведение становилось менее предсказуемым: где-то данные изменились, а шаблон не отреагировал, где-то сработало не то выражение. Приходилось разбираться не в логике приложения, а в том, как именно Angular обрабатывает изменения.
Кроме того, с ростом количества выражений в шаблоне возрастала и нагрузка: Angular проверял каждое из них при каждом обновлении. В крупных интерфейсах это уже влияло на производительность.
Фреймворк автоматизировал бо́льшую часть процессов, но не брал на себя управление целиком. Часть задач по-прежнему оставалась на стороне разработчика.
Вывод: почему фреймворк работает именно так
AngularJS не стал развивать подходы, где разработчик сам управляет состоянием и обновлениями. Вместо этого он предложил другой путь: встроить всю логику интерфейса в систему, от шаблонов до обновления DOM.
Это был противоположный подход по сравнению с инструментами вроде Backbone. AngularJS не оставлял свободы в архитектуре — он предлагал готовую схему, где многое было решено за разработчика. Это упрощало вход и ускоряло ход проекта, но поведение интерфейса сильнее зависело от внутренних механизмов.
В тот момент это выглядело логично: интерфейсы становились сложнее, и ручная настройка обновлений плохо справлялась с ростом проектов. AngularJS решил проблему автоматизацией, хоть это и сделало систему менее прозрачной.
С конца 2021 года AngularJS официально считается устаревшим. Вместо него Google развивает новый фреймворк Angular — с компонентной архитектурой, поддержкой TypeScript и другим устройством всего стека. При этом проекты на AngularJS всё ещё встречаются, особенно в старых системах.

Что изменилось с появлением React
React появился в 2013 году — сначала для ленты новостей в соцсетях, где интерфейс постоянно менялся и важно было понимать, когда и почему он обновляется.
В других инструментах приходилось или прописывать обновления вручную, или разбираться, как сработает встроенный механизм. React предложил другой подход: не управлять изменениями напрямую, а описывать, как должен выглядеть интерфейс при текущих данных.
Как мы писали выше, формально React — это библиотека, но на практике его часто используют как фреймворк. Поэтому в статье мы говорим о нём наравне с остальными инструментами.
Как устроен фреймворк
Раньше во фронтенде структура и поведение разделялись: HTML отвечал за разметку, JavaScript — за логику. React объединил данные, поведение и описание интерфейса внутри компонентов. Вместо отдельных слоёв разработчик описывает, как должен выглядеть фрагмент прямо в коде на JavaScript, — через JSX.

Вот как выглядела бы структура интерфейса и логика взаимодействия в обычном HTML + JavaScript:
<!-- HTML -->
<div id="greeting"></div>
<!-- JavaScript -->
<script>
document.getElementById('greeting').innerText = 'Hello, world!';
</script>
А в React это объединяется в одном компоненте через JSX:
function Greeting() {
return <div>Hello, world!</div>;
}
Компонент в React — это обычная функция на JavaScript, которая получает данные и описывает, как должен выглядеть фрагмент интерфейса. В отличие от AngularJS, здесь нет отдельных шаблонов и автоматической привязки данных: всё описывается напрямую в коде.
Когда данные меняются, React сравнивает текущее дерево компонентов с предыдущим и обновляет только изменившиеся части DOM. Такой подход снижает количество лишних операций и делает обновление интерфейса точечным.
Данные передаются сверху вниз — от родителя к дочерним компонентам. Это называется однонаправленным потоком: он помогает понять, откуда приходят данные и как они проходят через интерфейс.
Что делает фреймворк, а что — разработчик
React отвечает только за отображение: он показывает интерфейс на основе состояния, но не управляет архитектурой, маршрутизацией и тем, как хранятся данные. Эти решения остаются на стороне разработчика.
Если данные в коде поменялись, интерфейс сам не обновится. Нужно явно изменить состояние — через setState
, useState
или другой механизм. React не отслеживает переменные и не синхронизирует их с разметкой автоматически.
Такой подход оставляет больше свободы: можно самому выбрать архитектуру, подключить нужные инструменты и управлять тем, как связаны части интерфейса. Но вместе с этим больше точек, где важно быть внимательным, — особенно в больших проектах, где данные проходят через несколько уровней компонентов. Поэтому вокруг React обычно собирают собственный стек: для маршрутов, состояния, синхронизации и связи между компонентами.

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

Vue пошёл в сторону стандартизации. Здесь есть фиксированная структура компонентов, автоматическая реактивность и прямое связывание данных с шаблоном через переменные. Это не снимает с разработчика всех архитектурных решений, но упрощает типовые задачи, которые в других инструментах приходится настраивать вручную.
Как устроен фреймворк
В Vue обновление состояния происходит автоматически, в отличие от React, где его нужно запускать вручную через setState
или useState
. За этим стоит механизм реактивности: фреймворк сам отслеживает зависимости между переменными и шаблоном и обновляет только нужные части DOM.
Работает это через Proxy. Во время рендера Vue запоминает, какие данные использовались, и связывает их с конкретными участками интерфейса. Потом, если что-то в этих данных меняется, обновляются только связанные фрагменты, а всё остальное остаётся как есть.

А вот как это выглядит в реальном компоненте:
<template>
<div>
<p>{{ message }}</p>
<button @click="updateMessage">Изменить сообщение</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Привет, мир!'
};
},
methods: {
updateMessage() {
this.message = 'Сообщение обновлено!';
}
}
}
</script>
В этом примере при вызове метода updateMessage
Vue автоматически обновит в DOM только тот фрагмент, где используется значение message
.
При этом реактивность работает по чётким правилам. Если переменная не использовалась напрямую в шаблоне, Vue не будет отслеживать её изменения. Это часть архитектуры: отслеживаются только реальные зависимости.
Компоненты в Vue пишутся в одном .vue
-файле: шаблон, логика и стили находятся рядом. Такая структура повторяется от проекта к проекту и снижает количество решений, которые нужно принимать вручную. Это упрощает работу с кодом, особенно в командах.
Но у этого подхода есть и обратная сторона. Если привычнее более гибкая организация — например, разделение логики по функциям, как в React, — то фиксированная структура компонентов может показаться жёсткой.
Что делает фреймворк, а что — разработчик
Реактивность в Vue закрывает большинство типовых сценариев, но не все. Как уже писали выше, фреймворк отслеживает только те данные, которые участвовали в рендере. Если изменить вложенное поле или добавить новое свойство, интерфейс может не обновиться просто потому, что эта связь не была создана.
Такое поведение помогает избежать лишних обновлений, но требует аккуратности. Неочевидные баги чаще всего возникают именно из-за того, что данные не попали в реактивную цепочку.
С архитектурой компонентов похожая история. Пока код простой, структура .vue
-файла работает удобно. Но по мере роста проекта логика может начать мешаться в одном месте. Чтобы этого избежать, в Vue 3 добавили Composition API — он позволяет выносить поведение в отдельные функции. Но чтобы пользоваться им гибко, важно понимать, как устроена реактивность.
Вывод: почему фреймворк работает именно так
Vue не пересобрал архитектуру с нуля, а доработал то, что чаще всего приходилось делать вручную. Он сосредоточился на типовых задачах: как связать данные с шаблоном, как упростить структуру компонентов, как сократить количество лишних обновлений.
Из этого выросла его модель: автоматическая реактивность, фиксированная структура компонентов, логика рядом с разметкой. Это не упрощение ради простоты, а способ встроить в сам фреймворк те решения, которые раньше каждый раз приходилось придумывать заново.
Svelte и Solid
Компоненты помогают структурировать интерфейс, но не всегда избавляют от лишних обновлений. В React, например, изменение состояния может затронуть весь компонент и его вложенные части, если не включены оптимизации.
Поэтому Svelte и Solid предложили другой подход. Они переосмыслили не структуру компонентов, а сам механизм обновлений: что именно должно меняться при изменении данных и где это вычислять.

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

Это снижает объём обновлений и делает интерфейс легче на уровне исполнения. Но шаблон в Svelte — не просто HTML с вкраплениями JavaScript. Во время компиляции фреймворк анализирует, какие данные использовались, и выстраивает зависимости. Чтобы всё работало, важно соблюдать его правила:
реактивность работает только с локальными переменными;
выражения оформляются по правилам, например
$: count = a + b;
если переменная не увидена при сборке, она не будет отслеживаться.
Например, реактивное выражение в Svelte оформляется так:
<script>
let a = 2;
let b = 3;
// Реактивное выражение
$: sum = a + b;
</script>
<p>Сумма: {sum}</p>
Это требует понимания модели: можно написать корректный JS-код, который ничего не обновит, потому что он не прошёл через систему зависимостей.
Solid: точечные обновления в рантайме
Формально Solid считается библиотекой, но используется как фреймворк — со своей архитектурой, реактивной моделью и точечными обновлениями DOM.
Он решает ту же проблему, что и Svelte, но делает это на этапе выполнения, а не сборки. Главная идея: компонент не пересоздаётся при каждом обновлении. Вместо этого Solid отслеживает, какие именно выражения зависят от данных, и обновляет только те участки DOM, которым это нужно.
Это достигается за счёт сигналов — createSignal
. Они работают как реактивные ячейки: когда значение меняется, пересчитываются только связанные с ним части интерфейса. В отличие от React или Svelte, здесь не используется виртуальное дерево — обновления идут напрямую по связям между значением и шаблоном.
import { createSignal } from 'solid-js';
function CounterComponent() {
const [count, setCount] = createSignal(0);
return (
<button onClick={() => setCount(count() + 1)}>
{count()}
</button>
);
}
Здесь createSignal
создаёт реактивное значение. Когда пользователь нажимает кнопку, setCount
обновляет состояние, и Solid автоматически меняет только нужный участок DOM — без пересоздания компонента.
Такой подход даёт максимальную точность и предсказуемость. Если что-то меняется, видно, где и почему это произошло. Но вместе с этим появляются жёсткие требования к синтаксису:
нельзя использовать сигнал как обычную переменную;
важно не разрывать цепочку вычислений — иначе обновления не сработают.
Реактивность здесь требует дисциплины: всё должно быть оформлено строго по правилам.
Вывод: почему фреймворки работают именно так
Svelte и Solid появились как ответ на архитектурные издержки: в сложных интерфейсах перегрузку создаёт не только объём данных, но и сама модель обновлений.
Svelte решил это на этапе сборки: убрал всё лишнее из рантайма и оставил конкретные инструкции для DOM. Solid пошёл другим путём: оставил реактивность в браузере, но сделал её максимально точечной за счёт связей между значением и DOM.
Отсюда и их нюансы. В Svelte важна структура шаблона: если фреймворк не увидел переменную во время сборки, он за ней не будет следить. В Solid критична дисциплина: сигнал нужно использовать по правилам, иначе он выпадет из реактивной цепочки.
А что с другими фреймворками
В статье мы разобрали те инструменты, которые сильно повлияли на архитектуру фронтенда: каждый из них менял не только API, но и сам подход к обновлению интерфейса и организации кода. Но на этом список, конечно, не заканчивается.
Экосистема JavaScript устроена так, что новые фреймворки появляются регулярно. Одни развивают знакомые идеи, другие закрывают узкие задачи, третьи предлагают свои сборки из уже привычных инструментов.

Сами по себе они не ломают модель, но показывают, как далеко разрослись фронтенд-подходы и как быстро в них появляются локальные вариации тех же архитектурных тем.
Кратко опишем несколько примеров:
Angular (новый) — это не продолжение AngularJS, а отдельный инструмент. Он использует компонентную архитектуру, строгое разделение слоёв и TypeScript по умолчанию. В отличие от React и Vue, здесь больше встроенных решений: маршруты, DI, формы, сборка. Это удобно для крупных проектов, но архитектура строго задана фреймворком. Концептуально он не сдвинул границы, а закрепил модель с максимальной структурой и контролем.
Preact — упрощённый аналог React. Он использует ту же компонентную модель и API, но весит меньше и работает быстрее. Это выбор для тех случаев, где важны скорость загрузки и минимальный JS, но архитектурно он ничего не меняет: это всё тот же React, только полегче.
Qwik экспериментирует с тем, как загружается и выполняется код. Вместо того чтобы грузить всё сразу, он откладывает выполнение JavaScript до тех пор, пока тот не понадобится. Сначала загружается обычный HTML, а скрипты подключаются только при взаимодействии с элементами. Это ускоряет загрузку и уменьшает объём кода, который выполняется сразу. Архитектура компонентов здесь сохраняется, но поведение смещается ближе к серверной логике.
Marko делает ставку на серверный рендеринг и обновление DOM без виртуального дерева. Он действует на этапе сборки: превращает шаблоны в точечные операции. Это похоже на работу Svelte, но с ориентацией на большие платформы, где важна производительность при масштабировании.
Astro предлагает смешанный подход: на первом экране он рендерит интерфейс на сервере, а дальше подключает фреймворки только там, где нужна интерактивность. Это уменьшает количество клиентского JS и делает структуру гибкой. Но внутри компонентов всё работает так же, как в React или Vue, — архитектурно это не новый подход, а способ гибко применять старый.
Как знание истории помогает в работе: разбираем кейсы
Разберём несколько типичных ситуаций, когда данные изменились, но интерфейс никак не отреагировал.
Пример 1. Форма в React не обновляется, хотя значение вроде бы меняется
Кажется, что всё сделано правильно: value
связан со стейтом, onChange
вызывает setValue
. Но при попытке ввести что-то в поле ничего не происходит. Текст сразу сбрасывается обратно.
const [value, setValue] = useState("initial");
return (
<input value={value} onChange={() => setValue("initial")} />
);
Почему так: React обновляет компонент, только если значение реально изменилось. В этом примере мы каждый раз записываем "initial"
— ту же самую строку, что уже и так есть в value. Для React это сигнал: обновления не нужно, всё и так актуально. Он не будет перерисовывать DOM без изменения состояния.
Что ещё важнее, управляемые компоненты работают в жёсткой связке: value
говорит, что должно быть в поле, а onChange
— как это менять. Если в onChange
не учесть введённое значение, поведение будет ломаться.
Архитектурный контекст: так устроен сам React. Он не следит за элементами на странице. Интерфейс — это функция от состояния. Если состояние не поменялось, ничего не перерисуется. Это решение было сознательным: разработчик полностью контролирует, что и как должно обновляться.
Правильный вариант:
const [value, setValue] = useState("initial");
const handleChange = (e) => {
setValue(e.target.value);
};
return (
<input value={value} onChange={handleChange} />
);
Теперь поле управляется правильно: value
— это состояние, а onChange
обновляет это состояние. И React просто ререндерит интерфейс на основе новых данных.
Пример 2. Обновляем объект в Vue, а интерфейс не реагирует
После присваивания нового значения user.name
интерфейс остаётся прежним — Alice
не появляется. Хотя данные внутри объекта изменились, шаблон не отреагировал.
<script setup>
const user = reactive({});
</script>
<template>
<p>{{ user.name }}</p>
</template>
<!-- Позже -->
user.name = "Alice";
Почему так: Vue отслеживает зависимости между данными и шаблоном на этапе рендера. Если в момент первого рендера поле name
отсутствовало, оно не попало в список наблюдаемых. Vue не знает, что оно влияет на DOM, и не инициирует обновление. Это следствие архитектурного решения: фреймворк работает с теми значениями, к которым уже есть реактивные связи.
Архитектурный контекст: Vue строит реактивность через Proxy — он отслеживает, какие свойства читаются во время рендера, и запоминает эти связи. При этом такой подход требует определённой предсказуемости в структуре данных.
Что с этим делать
Указать нужные свойства заранее:
const user = reactive({ name: "" });
Или использовать ref
, если структура объекта формируется динамически:
const name = ref("");
Пример 3. Пропсы в Solid обновляются, а компонент — нет
Значение value
передаётся в Child
, но console.log("render")
срабатывает только один раз. Кажется, что компонент не обновляется, хотя на экране число меняется.
function Child(props) {
console.log("render");
return <div>{props.value}</div>;
}
function App() {
const [count, setCount] = createSignal(0);
return (
<>
<Child value={count()} />
<button onClick={() => setCount(count() + 1)}>+</button>
</>
);
}
Почему так: в Solid компоненты не являются реактивными единицами. Функция вызывается один раз — при создании. Все обновления происходят не за счёт повторного рендера, как в React, а за счёт реактивных выражений внутри JSX.
Ключевой момент
props
— это входные данные, которые родитель передаёт дочернему компоненту. Это стандартный способ передать значения в компонент:
<Child value={count()} />
В Solid передача props
сама по себе не запускает обновления. Чтобы интерфейс реагировал на изменения, значение нужно использовать внутри реактивного контекста, например в JSX или createEffect
.
Архитектурный контекст: Solid не пересоздаёт компоненты при каждом обновлении, а сразу подписывается на нужные данные. Это даёт точечные обновления без виртуального DOM.
Что с этим делать
Если вы используете значение в JSX, всё будет работать корректно. Но если вам нужно явно отследить изменение, используйте createEffect
:
function Child(props) {
createEffect(() => {
console.log("value changed:", props.value);
});
return <div>{props.value}</div>;
}
Пример 4. Обновляем массив в Svelte, а интерфейс не меняется
После нажатия на кнопку в массив добавляется число 4
, но интерфейс остаётся прежним. Четвёртый элемент не появляется на экране.
<script>
let items = [1, 2, 3];
function addItem() {
items.push(4);
}
</script>
{#each items as item}
<p>{item}</p>
{/each}
<button on:click={addItem}>Добавить</button>
Почему так: в Svelte реактивность работает только при присваивании. Фреймворк отслеживает переменные, и, чтобы вызвать обновление, нужно сигнализировать о том, что данные изменились. items.push(4)
сам по себе не вызывает пересчёт, потому что переменная items
не получила новое значение.
Это связано с тем, как работает компиляция: при items = [...]
фреймворк встраивает подписку на эту переменную. Но при изменении массива внутри (push, splice) он ничего не узнаёт.
Как это связано с архитектурой: Svelte принципиально отличается от Vue, React и Solid — он не использует виртуальный DOM и не держит фреймворк в рантайме. Svelte сразу превращает реактивный код в конкретные действия с DOM — без виртуального слоя. Это ускоряет интерфейс, но работает только тогда, когда изменение данных видно фреймворку.
Как это исправить:
function addItem() {
items = [...items, 4]; // Пересоздаём массив — Svelte это заметит
}
Или:
items.push(4);
items = items; // Сигнал об изменении
Закрепляем вывод
Сегодня во фронтенде можно начать с готовых инструментов: открыть React, собрать приложение и не вникать в детали. И это нормально — экосистема к этому располагает. Но чем глубже погружаешься в проекты, тем больше возникает вопросов: почему state обновляется не сразу, откуда берутся лишние ререндеры, зачем всё переписали на хуки.
Ответы на такие вопросы обычно не описаны в документации — они становятся понятны, если знать, как развивались подходы. Многие архитектурные решения во фронтенде — это не идеальные конструкции, а компромиссы. За каждым удобным API стоит чей-то старый костыль, обёрнутый в понятный интерфейс. Когда понимаешь, откуда он взялся, с ним становится проще работать и не тянет переделывать то, что однажды уже решили.
Вот несколько типичных ситуаций, которые решаются проще после нашего краткого разбора:
В коде на React встречаются классы и методы жизненного цикла. Вместо того чтобы всё переписывать на хуки, можно доработать поведение в старой модели: понятно, где что происходит и как работает setState.
В Vue новое поле добавляется в объект, но не появляется в шаблоне. Не тратим время на отладку: знаем, что переменная должна участвовать в первом рендере, иначе реактивность её не увидит. Значит, нужно добавить её заранее.
В Svelte после push в массив список не обновляется. Не переписываем код — просто делаем items = items, потому что механизм реактивности завязан на присваивание, и это ожидаемое поведение.
В проекте перемешаны jQuery, Vue и частично React. Понимаем, как это сложилось: старый слой на jQuery, Vue добавили для локальных задач, позже началась миграция на компоненты. Зная архитектурные подходы, видим, где можно вмешаться, а что проще оставить как есть.
Берём в работу новый фреймворк и сразу видим знакомые принципы: реактивность, компоненты, однонаправленный поток данных. Остаётся только разобраться в реализации, потому что основные идеи уже понятны.

Войти во фронтенд и прокачаться до уровня первых коммерческих проектов помогут программы «Фронтенд-разработчик» и «Веб-разработчик с нуля: профессия с выбором специализации». Обучение построено на практике: сначала — основы HTML, CSS и JavaScript, потом — работа с интерфейсами, данными и командными задачами.
А начать можно с простого: бесплатные курсы «Frontend-разработка: основы HTML, CSS и JavaScript» и «Как начать работать на фрилансе» помогут освоить базовые инструменты и понять, где искать первые заказы.