Flutter против React Native: решения, которые проявляются только в продакшене
Подробное сравнение Flutter и React Native, в котором не рассматриваются соревнования по показателям производительности, а внимание сосредоточено на рендеринге, языке программирования, состоянии приложения, списках и экосистеме по мере его развития.
Источники: Flutter vs React Native: The Real Differences Start After “Hello World”
Flutter и React Native обещают одно и то же: написать один кодовый базис, выпустить приложение для Android и iOS, избавившись от необходимости поддержки двух отдельных нативных приложений. На бумаге их предложения идентичны, и для первого экрана приложения это действительно так. Создание кнопки, отображение списка, настройка навигации или показ ответа от API — всё это просто в любой из этих фреймворков. Однако различия, которые действительно влияют на проект, проявляются не здесь. Они появляются тогда, когда приложение развивается и начинает включать в себя пользовательский интерфейс, анимации, управление состоянием, интеграцию с нативными компонентами и требует многолетней поддержки. Это сравнение показывает, в чём начинаются различия после демонстрации, чтобы вы могли сделать выбор исходя из поведения каждого фреймворка в масштабе, а не только из скорости отображения сообщения «Hello World».
Первое впечатление намеренно схоже
Оба фреймворка позволяют быстро создать работающее приложение. Вы можете разрабатывать интерфейсы, добавлять навигацию, вызывать API, отображать списки и обрабатывать ввод пользователя, не разделяя код на две нативные базы. Именно такой синергетический эффект является причиной, по которой команды в первую очередь обращаются к мультиплатформенным инструментам. Простой интерфейс будет выглядеть практически одинаково независимо от выбранного фреймворка. Однако как только приложение становится сложным, две философии развиваются в разные направления, поэтому честное сравнение происходит именно в сложных частях кода, а не в стартовых шаблонах.
Flutter работает с виджетами
Самая заметная особенность Flutter заключается в том, что практически всё, с чем вы работаете, представляет собой виджет. Экраны, кнопки, отступы вокруг них и структура их расположения — всё это виджеты, как и многие другие элементы, которые не отображают ничего видимого на экране. Например, колонка состоит из метки, небольшого отступа и кнопки; каждый из этих элементов является отдельным виджетом, вложенным в иерархию.
Column(
children: [
Text('Welcome'),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {},
child: Text('Continue'),
),
],
)
Как только выбирается такая модель, создание интерфейса становится чрезвычайно последовательным: маленькие виджеты объединяются в более крупные, а те в конечном итоге превращаются в целые экраны. React Native следует другому подходу. Он описывает интерфейс с помощью компонентов React, написанных на JavaScript или TypeScript, поэтому тактика работы по сути сводится к созданию приложения на React, что кажется сразу естественным для тех, кто уже работает с React. Ни одна из этих моделей не является абсолютно лучшей. Они просто заставляют подходить к проектированию интерфейса с разных точек зрения, причем «правильный» вариант во многом зависит от того, какие подходы уже приняты в вашей команде.
Отрисовка и степень контроля над интерфейсом
Подход Flutter к отрисовке делает его привлекательным для создания сильно персонализированных интерфейсов. Когда дизайн требует чего-то, что не похоже на стандартные экраны платформы, Flutter предоставляет обширный каталог виджетов вместе с примитивами рисования, с помощью которых можно создавать практически любой визуальный результат. Персонализированные карточки, анимации, переходы, индикаторы загрузки, графики, интерактивные элементы, необычные макеты и высокохудожественно оформленные экраны — всё это реализуется в рамках одной и той же системы виджетов.
React Native также может создавать высоко настраиваемые интерфейсы, но делает это иным способом, поскольку традиционно использует собственные нативные компоненты платформы через свою архитектуру выполнения. Стоит быть точным здесь: современный React Native значительно изменился благодаря своей новой архитектуре, поэтому старое описание в виде «JavaScript общается с нативным кодом через мост» больше не отражает реальный способ работы текущей версии, а сравнения, основанные на нем, устарели. Суть не в том, что одна из фреймворков не способна создать определенный интерфейс — обе могут это сделать. Разница заключается в пути, который каждый из них выбирает для достижения цели, и в том, как этот путь проявляется на практике.
Dart против JavaScript и TypeScript
Одно из основных отличий проявляется ещё до того, как вы начнёте разрабатывать пользовательский интерфейс: это язык программирования. Flutter построен на Dart; React Native работает с JavaScript или TypeScript в более серьёзных проектах. Dart — это язык с строгой типизацией, разработанный специально для использования с Flutter, и после освоения он кажется очевидным выбором для кода приложений. Несколько примеров объявлений с типами выглядят следующим образом.
final String name = 'Ankit';
final int age = 25;
Такая статическая типизация облегчает понимание более крупных кодовых баз, поскольку компилятор выявляет целые категории ошибок ещё до их выполнения. Команды React Native часто используют TypeScript, чтобы добиться аналогичных преимуществ в среде JavaScript.
const name: string = 'Ankit';
const age: number = 25;
TypeScript обеспечивает строгую типизацию, оставаясь частью экосистемы JavaScript, что является значительным преимуществом для организаций, уже инвестировавших в JavaScript. Компания с готовой командой по разработке сайтов на React может чаще без проблем внедрять React Native, поскольку разработчики могут использовать уже имеющиеся у них концепции, инструменты и привычки. Для команды, полностью ориентированной на Dart, Flutter предлагает более сфокусированную и самодостаточную среду. Здесь ставка делается на широту существующей экосистемы в ущерб целостности специализированной среды.
Управление состоянием перестаёт быть простой задачей
Именно в состоянии происходит самое быстрое исчезновение первоначальной простоты. У небольшого приложения есть всего несколько элементов состояния; в производственном приложении их накапливается множество — статус аутентификации, данные профиля пользователя, содержимое корзины, состояния загрузки API, пагинация, фильтры поиска, уведомления, предпочтения темы, локальное хранилище, состояние формы и обновления в реальном времени. Когда сосуществует столько факторов, одного только фреймворка уже недостаточно. Потребуется архитектура и тщательно продуманная стратегия управления состоянием, разработанная на её основе.
Оба экосистемы предлагают различные решения. В Flutter существуют Provider, Riverpod, BLoC, Cubit, GetX и другие инструменты; в React Native — Redux, Zustand, MobX, Context API и многие другие. Количество пакетов здесь мало что говорит. Решающим фактором для обеспечения поддерживаемости является способность команды последовательно структурировать и развивать состояние приложения со временем. Фреймворк может обеспечивать отличную производительность интерфейса, но при этом создавать приложение с плохой поддерживаемостью, если архитектура состояния недостаточно прочна. Та же дисциплина, которая сохраняет целостность архитектуры крупного React-приложения, применима и здесь, независимо от того, какой фреймворк отвечает за отображение элементов интерфейса.
Производительность — это не числовой показатель
Производительность — это самая часто обсуждаемая тема в этих сравнениях. Статьи регулярно объявляют одну фреймворк-систему быстрее другой на основе синтетических тестов, но реальные приложения не являются такими тестами. Продакшн-приложение обычно включает в себя сетевые запросы, большие списки, изображения, анимации, карты, локальное хранилище, фоновую работу, уведомления, веб-виджеты, инструменты аналитики, нативные SDK и сторонние пакеты, причем каждый из этих элементов может повлиять на производительность.
Плохо написанное приложение на Flutter может работать медленно, так же как и плохо написанное приложение на React Native. Ненужные пересборки, чрезмерная обработка изображений, слишком крупные изображения, неэффективные списки, ресурсоемкие вычисления, небрежное использование памяти и плохо спроектированные сетевые процессы приводят к замедлению работы, независимо от названия фреймворка. Более важный вопрос заключается в том, насколько хорошо архитектура приложения справляется с его конкретной нагрузкой, а не в том, какой фреймворк победит в абстрактном сравнении. Ответ на вопрос «какой быстрее» практически бессмыслен без информации о том, что именно вы создаете.
Списки делают ситуацию более понятной
Рассмотрим приложение для электронной коммерции с тысячами товаров. Загружать все товары в память и отображать их одновременно — ошибка в любом фреймворке. Решением Flutter является механизм построения элементов по мере их прокрутки в поле зрения.
ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
return ProductCard(
product: products[index],
);
},
)
Основной принцип заключается в том, чтобы создавать только то, что действительно необходимо, а не генерировать все элементы заранее. React Native реализует аналогичную идею с помощью таких механизмов, как FlatList. Концепция остаётся одинаковой; отличия кроются в конкретном API, модели отрисовки, методах оптимизации и условиях разработки. По мере увеличения сложности списка факторы, обеспечивающие его плавную работу — такие как пагинация, кэширование изображений, размер элементов, обновление состояния и избежание ненужных перерисовок — становятся гораздо важнее, чем выбранный фреймворк.
Экосистема является решающим фактором
React Native обладает явным преимуществом в одном конкретном аспекте: он интегрирован в экосистему JavaScript. JavaScript и TypeScript уже используются повсеместно — в фронтенде, бэкенде, инструментах и на вебе, а React относится к самым популярным библиотекам интерфейсов в мире. Поэтому организации, имеющие сильные команды по React, могут легко перейти на React Native, используя уже имеющиеся навыки и библиотеки.
Преимущество Flutter заключается в чем-то противоположном. Он предлагает относительно сфокусированную экосистему, основанную на Flutter и Dart, что может показаться особенно целостным решением для команд, предпочитающих создавать мобильные приложения без использования обширных инструментов более широкого мира JavaScript. Ни один из вариантов не является абсолютно лучшим; оптимальный выбор зависит от состава вашей команды.
Такой вариант, какой же выбрать?
Не существует универсального победителя, и правильное решение зависит от особенностей проекта и его команды. Если у команды уже есть значительный опыт работы с React и TypeScript, React Native легко интегрируется в её существующую технологическую стек. Если команда хочет специализированную кроссплатформенную рамку для интерфейса, построенную на Dart и основанную на модели вайджетов, Flutter становится естественным выбором. Приложение, которое опирается на накопленный опыт команды в использовании React и её инфраструктуру на JavaScript, является убедительным аргументом в пользу React Native; приложение, требующее высокой степени индивидуализации интерфейса и разработки специалистами, уже владеющими Flutter, — убедительным аргументом в пользу последнего. Необходимо избегать ошибки выбора рамки только потому, что в статье она названа «лучшей», а не потому, что она подходит для конкретного продукта.
Заключение
Flutter и React Native решают одну и ту же общую проблему, но с разных подходов. Flutter предоставляет модель, основанную на виджетах и языке Dart, а также тесно интегрированный набор инструментов для интерфейса, отлично подходящий для кастомного дизайна. React Native внедряет экосистему React и JavaScript в мобильные приложения, предоставляя доступ к возможностям нативной платформы благодаря архитектуре, которая значительно развилась за пределы своей первоначальной репутации как временного решения. Ни один из вариантов по умолчанию не делает все приложения лучше. Важный вопрос заключается не в том, «какая фреймворк победит», а в том, «какой фреймворк подходит для этого продукта, этой команды и данного типа приложения». Практически любой инструмент может отрисовать первый экран; именно во всем, что следует за ним, эти два фреймворка действительно различаются.