← Назад к вопросам

Как работает go router?

2.0 Middle🔥 131 комментариев
#Навигация

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает go_router?

go_router — это современный решение для маршрутизации в Flutter, созданное командой Google. Это декларативный роутер, заменивший устаревший Navigator.

Основные концепции

1. Декларативная маршрутизация vs Императивная

// ❌ Старый способ (Navigator 1.0 - императивный)
Navigator.of(context).push(
  MaterialPageRoute(builder: (context) => DetailsPage(id: 123)),
);

// ✅ Новый способ (go_router - декларативный)
context.go('/details/123');

go_router основана на деклараторе маршрутов, как в веб-приложениях.

Базовая настройка

import 'package:go_router/go_router.dart';

// Определяем маршруты
final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomePage(),
    ),
    GoRoute(
      path: '/details/:id',
      builder: (context, state) {
        final id = state.pathParameters['id']!;
        return DetailsPage(id: id);
      },
    ),
    GoRoute(
      path: '/profile',
      builder: (context, state) => ProfilePage(),
    ),
  ],
);

// Используем в MaterialApp
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: router,  // Передаём конфиг
    );
  }
}

Навигация

Простая навигация:

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          ElevatedButton(
            onPressed: () => context.go('/details/123'),
            child: Text('Go to Details'),
          ),
          ElevatedButton(
            onPressed: () => context.go('/profile'),
            child: Text('Go to Profile'),
          ),
        ],
      ),
    );
  }
}

Навигация с параметрами:

// В определении маршрута
GoRoute(
  path: '/details/:id',  // :id — параметр пути
  builder: (context, state) {
    final id = state.pathParameters['id']!;
    return DetailsPage(id: id);
  },
)

// Использование
context.go('/details/42');
context.go('/details/user-abc-123');

Query параметры:

// В определении маршрута
GoRoute(
  path: '/search',
  builder: (context, state) {
    final query = state.uri.queryParameters['q'];
    final page = state.uri.queryParameters['page'] ?? '1';
    return SearchPage(query: query, page: int.parse(page));
  },
)

// Использование
context.go('/search?q=flutter&page=2');

Вложенные маршруты (Sub-routes)

GoRoute(
  path: '/home',
  builder: (context, state) => ShellPage(child: state.navigationContainer),
  routes: [  // Вложенные маршруты
    GoRoute(
      path: 'feed',
      builder: (context, state) => FeedPage(),
    ),
    GoRoute(
      path: 'messages',
      builder: (context, state) => MessagesPage(),
    ),
    GoRoute(
      path: 'settings',
      builder: (context, state) => SettingsPage(),
    ),
  ],
)

// Использование
context.go('/home/feed');      // FeedPage
context.go('/home/messages');  // MessagesPage
context.go('/home/settings');  // SettingsPage

Shell Routes (Bottom Navigation, Drawer)

final router = GoRouter(
  initialLocation: '/home/feed',
  routes: [
    ShellRoute(
      builder: (context, state, child) {
        return ScaffoldWithBottomNav(
          child: child,
          onTabChanged: (index) {
            // Навигация при клике на табы
            switch (index) {
              case 0:
                context.go('/home/feed');
              case 1:
                context.go('/home/search');
              case 2:
                context.go('/home/profile');
            }
          },
        );
      },
      routes: [
        GoRoute(
          path: '/home/feed',
          builder: (context, state) => FeedPage(),
        ),
        GoRoute(
          path: '/home/search',
          builder: (context, state) => SearchPage(),
        ),
        GoRoute(
          path: '/home/profile',
          builder: (context, state) => ProfilePage(),
        ),
      ],
    ),
  ],
);

class ScaffoldWithBottomNav extends StatefulWidget {
  final Widget child;
  final Function(int) onTabChanged;
  
  const ScaffoldWithBottomNav({
    required this.child,
    required this.onTabChanged,
  });
  
  @override
  State<ScaffoldWithBottomNav> createState() => _ScaffoldWithBottomNavState();
}

class _ScaffoldWithBottomNavState extends State<ScaffoldWithBottomNav> {
  int _currentIndex = 0;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: widget.child,
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() => _currentIndex = index);
          widget.onTabChanged(index);
        },
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Feed'),
          BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
        ],
      ),
    );
  }
}

Redirect для авторизации

final router = GoRouter(
  initialLocation: '/',
  redirect: (context, state) async {
    // Получаем статус авторизации
    final isLoggedIn = await getAuthStatus();
    final isLoginPage = state.matchedLocation == '/login';
    
    // Если не авторизован и не на странице логина
    if (!isLoggedIn && !isLoginPage) {
      return '/login';
    }
    
    // Если авторизован и на странице логина
    if (isLoggedIn && isLoginPage) {
      return '/';
    }
    
    return null;  // Нет редиректа, продолжить как есть
  },
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomePage(),
    ),
    GoRoute(
      path: '/login',
      builder: (context, state) => LoginPage(),
    ),
  ],
);

Error handling

final router = GoRouter(
  initialLocation: '/',
  routes: [...],
  errorBuilder: (context, state) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('404 - Page not found'),
            Text('Location: ${state.uri}'),
            ElevatedButton(
              onPressed: () => context.go('/'),
              child: Text('Go Home'),
            ),
          ],
        ),
      ),
    );
  },
);

Передача данных между маршрутами

Способ 1: Through path parameters

context.go('/details/123');

GoRoute(
  path: '/details/:id',
  builder: (context, state) {
    final id = state.pathParameters['id']!;
    return DetailsPage(id: id);
  },
)

Способ 2: Through query parameters

context.go('/search?q=flutter&sort=recent');

GoRoute(
  path: '/search',
  builder: (context, state) {
    final query = state.uri.queryParameters['q'];
    final sort = state.uri.queryParameters['sort'];
    return SearchPage(query: query, sort: sort);
  },
)

Способ 3: Through extra (не в URL)

context.go(
  '/details',
  extra: User(id: '123', name: 'John'),
);

GoRoute(
  path: '/details',
  builder: (context, state) {
    final user = state.extra as User?;
    return DetailsPage(user: user);
  },
)

Навигационный стек и pop

class DetailsPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Details'),
        leading: BackButton(
          onPressed: () => context.pop(),  // Вернуться назад
        ),
      ),
      body: Column(
        children: [
          Text('Details page'),
          ElevatedButton(
            onPressed: () => context.pop('result_value'),  // Pop с результатом
            child: Text('Return'),
          ),
        ],
      ),
    );
  }
}

// В HomePage
ElevatedButton(
  onPressed: () async {
    final result = await context.push<String>('/details');
    print('Result: $result');  // result_value
  },
  child: Text('Open Details'),
)

Лучшие практики

// ✅ Используйте именованные маршруты для типобезопасности
final router = GoRouter(
  routes: [
    GoRoute(
      path: '/details/:id',
      name: 'details',  // Дайте имя
      builder: (context, state) => DetailsPage(
        id: state.pathParameters['id']!,
      ),
    ),
  ],
);

// Навигация по имени
context.goNamed('details', pathParameters: {'id': '123'});

// ✅ Кэшируйте router в верхнем уровне приложения
final router = GoRouter(...);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: router,
    );
  }
}

// ❌ Не создавайте новый router каждый раз
class BadExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final router = GoRouter(...);  // ❌ Пересоздаётся каждый раз!
    return MaterialApp.router(routerConfig: router);
  }
}

go_router — это modern, type-safe решение для навигации в Flutter приложениях, обеспечивающее deep linking и лучший контроль над навигационным стеком.

Как работает go router? | PrepBro