Так как же, черт возьми, React борется с проблемами, связанными с подходом на основе ср... — 9 июня 2026 г. в 07:12:35.163
Так как же, черт возьми, React борется с проблемами, связанными с подходом на основе сравнения VDOM-деревьев? Напомним, в чем тут проблема: 1. Каждое обновление состояния компонента приводит к вызову его рендер-функции. Даже если в шаблоне компонента вообще не используется значение, которое поменялось. При этом если внутри рендер-функции большие циклы или другие тяжелые операции, то всё это будет выполняться каждый раз. Ну и само собой, каждый раз функция будет создавать новое VDOM-дерево, что тоже не бесплатно и создает нагрузку на GC. 2. Сравнение деревьев. В наивной реализации это синхронная рекурсивная операция. Если деревья большие, то легко создать long task, которая сама по себе или в совокупности с другими приведет к ощущению, что приложение тормозит. И на самом деле данные проблемы до конца пока так и не решены. Но давайте разберемся с улучшениями, которые были сделаны командой React. Мемоизация вручную Первым решением было добавление API для мемоизации компонентов и фрагментов рендер-функции, а также добавление метода shouldComponentUpdate, который позволял разработчику руками управлять и подсказывать React, чтобы он не делал лишнюю работу. Проблема такого подхода в том, что это усложнение и зашумление кода. React Fiber (React 16) Начиная с React 16 было предложено новое решение — новая реализация VDOM, которая получила название React Fiber. В рамках поста я не смогу детально рассказать нюансы реализации, но основную идею выделить не сложно. Рендер-функция компонента возвращает не VDOM, а JSX-дерево (объекты, которые раньше и называли VDOM), на основе которого React строит Fiber-дерево. В отличие от JSX-узлов, Fiber-узлы хранят ссылки не только на потомков, но и на родителя, а также на «соседние» (сиблинги) элементы. И, что важнее, они переиспользуются между фазами обновления. «Ну и что это дало?» — спросите вы. А дало вот что: теперь мы можем обходить такие деревья по итератору (просто двигаемся по ссылкам), а значит, без проблем в любой момент прерываться и давать основному потоку поработать. А потом продолжить с места, на котором остановились. Но если до этого прилетел новый запрос на рендер (пользователь нажал на ссылку или ещё что-то), то операцию можно вообще прервать. Таким образом проблема фризов лечится за счёт внедрения простого правила: сравнение не должно блокировать поток более чем на ~20 мс. За это отвечает специальный планировщик. Кто был на моём открытом уроке по кооперативной многозадачности, должен сразу узнать этот подход. Мы разбиваем большую задачу на кусочки и гарантируем, что главный поток не будет блокироваться надолго. Пусть из-за этого общее время операции сравнения увеличится, но в задачах UI куда важнее отзывчивость. Данный подход полностью убрал необходимость в shouldComponentUpdate (для борьбы с фризами), но, к сожалению, ничего не сделал для решения проблемы вызова рендер-функции каждый раз. Эта проблема так и решалась за счёт ручного внедрения мемоизации самим разработчиком. Компилятор (React 19) И вот в React 19 появляется подход к решению и этой проблемы — компилятор. Если кратко, то теперь в процессе сборки JSX-файлов компилятор анализирует код компонента и вставляет мемоизацию самостоятельно, тем самым убирая потребность делать это явно. Такой подход давно используется многими другими библиотеками, и вот его наконец внедрили в React. Но опять же, это оптимизация, а не решение проблемы. Рендер-функции всё равно вызываются на каждом апдейте, просто цена такого вызова стала меньше. К сожалению, компилятор работает только для функциональных компонентов, и это очередной гвоздь в крышку гроба классовых компонентов, которые хоть ещё и поддерживаются, но по факту уже являются легаси в React. Хорошо это или плохо — сегодня разбирать не будем.

