Start Debugging

go_router против auto_route против Navigator 2.0 во Flutter

go_router и auto_route оба работают поверх Navigator 2.0, поэтому реальный выбор -- это декларативная маршрутизация по URL против типизированных маршрутов на основе кодогенерации против ручной реализации Router API. Матрица решения с конфигурацией для каждого варианта и случаи, когда чистый Navigator всё ещё выигрывает.

Краткий ответ: для большинства приложений Flutter в 2026 году используйте go_router. Он поддерживается командой Flutter, это тот вариант, на который указывает официальная документация, и он превращает URL в экраны с наименьшими церемониями. Обращайтесь к auto_route, когда вам нужны проверяемые на этапе компиляции, строго типизированные вызовы навигации и вы готовы запускать генератор кода, чтобы их получить. Опускайтесь до чистого Navigator 2.0 (самостоятельно реализуя RouterDelegate и RouteInformationParser) только тогда, когда у вас действительно необычная модель навигации, которую не выражает ни один из пакетов, потому что вы подписываетесь на большое количество шаблонного кода. А если ваше приложение небольшое и не имеет требований к глубоким ссылкам или веб-URL, обычный императивный Navigator.push всё ещё остаётся вполне хорошим ответом.

Главное, что почти все понимают неправильно в этом сравнении, — это то, что три варианта воспринимаются как параллельные. Это не так. “Navigator 2.0” — это Router API фреймворка Flutter, и оба пакета, go_router и auto_route, построены поверх него. Так что выбор не в том, “пакет A против пакета B против фреймворка”; он в том, “на каком уровне я хочу писать свою маршрутизацию”. В этом посте используются go_router 17.3.0, auto_route 11.1.0, Flutter 3.44 stable и Dart 3.x.

Слоёный пирог: что на самом деле такое “Navigator 2.0”

Во Flutter бок о бок существуют два API навигации.

Navigator 1.0 — это императивный API, который вы уже знаете: Navigator.of(context).push(MaterialPageRoute(...)) и Navigator.pop(context). Вы добавляете и убираете экраны, как в стеке. Он прост и он работает, и для небольшого приложения это всё, что вам нужно.

Navigator 2.0, более точно называемый Router API, декларативен. Вместо императивного добавления вы передаёте фреймворку список объектов Page, которые описывают весь стек, и когда состояние вашего приложения меняется, вы передаёте ему новый список. Два элемента заставляют это работать: RouteInformationParser, который превращает входящий URL (RouteInformation) в ваш собственный тип данных, специфичный для приложения, и RouterDelegate, который читает эти данные плюс состояние вашего приложения и строит Navigator с правильными pages. Официальная документация по навигации и маршрутизации описывает именно это разделение.

Загвоздка в том, что написание корректного RouterDelegate и RouteInformationParser вручную — это большой объём работы. Вы отвечаете за разбор путей, поддержание списка страниц, обработку системной кнопки “назад”, синхронизацию URL браузера и восстановление глубоких ссылок. Документация Flutter прямо говорит об этом:

Если вы предпочитаете не использовать пакет маршрутизации и хотите полного контроля над навигацией и маршрутизацией в вашем приложении, переопределите RouteInformationParser и RouterDelegate.

Это “если вы предпочитаете полный контроль” — показательно. Для всех остальных пакет маршрутизации оборачивает этот механизм. go_router и auto_route — оба являются такой обёрткой. Они потребляют Navigator 2.0; они не конкурируют с ним. Поэтому, когда кто-то спрашивает “go_router или Navigator 2.0?”, честный ответ таков: go_router и есть Navigator 2.0 с уже написанным за вас шаблонным кодом.

На что оптимизирован go_router: URL как источник истины

go_router публикуется проверенным издателем flutter.dev и поддерживается командой Flutter. Его модель ориентирована на URL: вы объявляете таблицу маршрутов из шаблонов путей, и навигация — это вопрос перехода к пути. Глубокие ссылки, веб-URL и внутренняя навигация в приложении — все разрешаются через одну и ту же таблицу, поэтому глубокая ссылка и нажатие кнопки приводят на один и тот же экран.

Минимальная настройка:

// Flutter 3.44, go_router 17.3.0
final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
      routes: [
        GoRoute(
          path: 'orders/:id',
          builder: (context, state) {
            final id = state.pathParameters['id']!;
            return OrderScreen(orderId: id);
          },
        ),
      ],
    ),
  ],
);

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) =>
      MaterialApp.router(routerConfig: router);
}

Навигация — это строка:

// go_router 17.3.0 - navigate by URL
context.go('/orders/42');   // replace the stack, land on order 42
context.push('/orders/42'); // push on top of the current stack

Сила здесь — это одновременно и острый край: цели навигации представляют собой строки. context.go('/orders/42') не проверяется компилятором. Опечатайтесь в пути или переименуйте маршрут, и вы узнаете об этом во время выполнения. У go_router есть ответ, типизированные маршруты через go_router_builder, который генерирует типизированные классы GoRouteData, чтобы вы могли вызывать OrderRoute(id: 42).go(context) вместо этого. Но это опциональная кодогенерация, прикрученная к пакету, чей идиоматический подход по умолчанию — строки, и на практике многие кодовые базы go_router так её и не включают.

go_router также владеет двумя возможностями, ради которых его на самом деле и берут. ShellRoute и StatefulShellRoute.indexedStack дают вам постоянную оболочку (нижнюю панель навигации, которая переживает переключения вкладок), а колбэк redirect верхнего уровня обрабатывает контроль доступа для всего дерева. Если вам нужно полное руководство по оболочкам и настройке глубоких ссылок, смотрите настройку вложенных маршрутов и глубоких ссылок с go_router.

Одно замечание о статусе, важное для решения на несколько лет вперёд: команда Flutter указывает go_router как функционально завершённый. Их заявление о дорожной карте гласит:

Этот пакет считается функционально завершённым. Основное внимание команды Flutter будет уделяться исправлению ошибок и обеспечению стабильности.

Читайте это как стабильность, а не заброшенность. Это означает, что API, который вы изучаете сегодня, вряд ли будет меняться под вами, и исправления ошибок по-прежнему выходят. Это также означает, что крупные новые возможности с большей вероятностью придут в виде пакетов сообщества, чем как основные функции go_router.

На что оптимизирован auto_route: типизированные маршруты из кодогенерации

auto_route 11.1.0 — это пакет сообщества от Milad Akarie. Он атакует именно ту слабость, что есть в go_router по умолчанию: строково-типизированную навигацию. Вы аннотируете каждую страницу с помощью @RoutePage(), запускаете build_runner, и он генерирует строго типизированный маршрут для каждого экрана. Вызовы навигации затем проверяются компилятором, а их аргументы типизированы.

// Flutter 3.44, auto_route 11.1.0
@RoutePage()
class OrderScreen extends StatelessWidget {
  const OrderScreen({super.key, required this.orderId});
  final int orderId; // a real int, not a string yanked from a path

  @override
  Widget build(BuildContext context) => const Placeholder();
}

@AutoRouterConfig()
class AppRouter extends RootStackRouter {
  @override
  List<AutoRoute> get routes => [
        AutoRoute(page: HomeRoute.page, initial: true),
        AutoRoute(page: OrderRoute.page, path: '/orders/:id'),
      ];
}

После dart run build_runner build навигация типизирована из конца в конец:

// auto_route 11.1.0 - typed navigation, checked by the compiler
context.router.push(OrderRoute(orderId: 42)); // orderId is an int

Переименуйте orderId, измените его тип или удалите маршрут, и код, который переходит к нему, перестанет компилироваться. Это и есть вся идея, и для большого приложения с десятками экранов и нетривиальными аргументами это реальная, ежедневная разница в производительности. Вам никогда не приходится сериализовать объект в строку запроса и вручную разбирать её обратно.

auto_route также поставляет первоклассные эквиваленты возможностей go_router. Вложенная навигация использует виджет AutoRouter в качестве выхода для дочерних маршрутов; навигация с вкладками использует AutoTabsRouter, который сохраняет состояние вкладок за пределами экрана так же, как это делает StatefulShellRoute. Контроль доступа использует стражи: вы наследуетесь от AutoRouteGuard, реализуете onNavigation и прикрепляете страж к каждому маршруту или глобально.

// auto_route 11.1.0 - a route guard
class AuthGuard extends AutoRouteGuard {
  @override
  void onNavigation(NavigationResolver resolver, StackRouter router) {
    if (isSignedIn) {
      resolver.next(true);
    } else {
      router.push(const LoginRoute());
    }
  }
}

Цена — это генератор кода. Вы добавляете auto_route_generator и build_runner как dev-зависимости, и каждый раз, когда вы добавляете или изменяете @RoutePage, вы регенерируете. В активной разработке вы запускаете build_runner watch. Это реальный налог: более медленные холодные сборки, сгенерированный файл .gr.dart в вашем дереве и периодическая необходимость удалять сгенерированный вывод и пересобирать, когда всё рассинхронизируется. Стоит ли типизированная навигация шага кодогенерации — это центральный компромисс во всём этом сравнении.

Решение в виде матрицы

Аспектgo_router 17.3.0auto_route 11.1.0Чистый Navigator 2.0
МейнтейнерКоманда Flutter (flutter.dev)Сообщество (Milad Akarie)Фреймворк Flutter
Вызовы навигацииСтроки (context.go('/x')), типизация опционально через builderТипизированы, проверяются компилятором по умолчаниюОпределяете вы
КодогенерацияНет (опционально для типизированных маршрутов)Да (build_runner)Нет
Глубокие ссылки / веб-URLВстроеноВстроеноРеализуете вы
Постоянная оболочка / вкладкиStatefulShellRouteAutoTabsRouterРеализуете вы
Контроль доступаКолбэк redirectAutoRouteGuardРеализуете вы
Шаблонный кодМалоОт малого до среднего (плюс сгенерированные файлы)Много
Лучше всего, когдаБольшинство приложений; веб; глубокие ссылкиБольшое приложение, много типизированных аргументов, вам нравится codegenПо-настоящему нестандартная модель навигации

Строки, которые должны определять ваш выбор, — это “вызовы навигации” и “кодогенерация”. Если проверяемая на этапе компиляции навигация достаточно важна, чтобы оправдать шаг сборки, — auto_route. Если вы предпочли бы не запускать генератор и вам комфортно со строковыми путями (или вы позже подключите go_router_builder), — go_router. Всё остальное оба пакета делают компетентно.

Когда чистый Navigator 2.0 — правильный выбор (редко)

Не хватайтесь за написанный вручную RouterDelegate просто чтобы чувствовать себя в контроле. Ситуации, где он действительно окупается, узки:

Для этих случаев чистый API даёт вам полный контроль над List<Page> и циклом разбора и восстановления. Для всего остального шаблонный код, который вы бы написали, — это шаблонный код, который пакеты уже написали, протестировали и поставляют. Указания документации Flutter однозначны: приложения с продвинутыми потребностями в навигации “должны использовать пакет маршрутизации, такой как go_router”, а чистый API представлен как аварийный выход для тех, кто явно хочет полного контроля.

Не забудьте: для небольшого приложения Navigator 1.0 всё ещё подходит

Есть режим отказа, при котором приложение из пяти экранов принимает пакет маршрутизации, генератор кода и абстракцию таблицы маршрутов, которые ему никогда не были нужны. Если у вашего приложения нет веб-цели, нет глубоких ссылок и нет требования реконструировать экран из URL, императивная навигация — это не устаревшая ошибка, это инструмент подходящего размера:

// Flutter 3.44 - imperative navigation, still correct for simple apps
Navigator.of(context).push(
  MaterialPageRoute<void>(
    builder: (context) => const SecondScreen(),
  ),
);

Официальная документация явно благословляет это для “небольших приложений без сложных глубоких ссылок”. В тот момент, когда вы добавляете веб-сборку, ссылки для обмена или нижнюю навигацию, которая должна переживать переключения вкладок, переходите на go_router или auto_route. Не раньше.

Подводные камни, которые вручает вам каждый выбор

Несколько вещей кусают людей после того, как они уже определились:

Если вы всё ещё определяетесь со своим более широким стеком, а не только с маршрутизацией, то же напряжение “официальное-и-скучное против богатого-возможностями-от-сообщества” проявляется и в управлении состоянием, которое я разбираю в Provider против Riverpod против Bloc, и в самом выборе платформы в Flutter против React Native против MAUI.

Резюме, которое переживает контакт с реальным проектом: go_router — это выбор по умолчанию, потому что он официальный, стабильный и URL-нативный. auto_route оправдывает себя на больших приложениях, где проверяемая на этапе компиляции навигация стоит генератора кода. Чистый Navigator 2.0 — для редкого приложения, чья модель навигации не укладывается в таблицу маршрутов. А обычный Navigator.push всё ещё остаётся правильным ответом для небольшого приложения, которому ничего из этого не нужно. Выбирайте самый нижний уровень, который решает вашу реальную проблему, а не самый мощный.

Sources

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Назад