跳转到正文
YGJoon 技术博客

Flutter 命名路由详解

发布于:
·1.1k 字 · 约 3 分钟·作者:
routes 路由表集中注册、pushNamed 跳转、onUnknownRoute 兜底未知页、清栈返回登录页,一套命名路由组合拳
Flutter 命名路由详解文章封面
目录

简介

命名路由是一种通过字符串名称来标识和导航到页面的机制。它可以让路由管理更加清晰和集中。

基本配置

注册路由

MaterialApp(
  // 注册路由表
  routes: {
    '/': (context) => HomePage(),
    '/login': (context) => LoginPage(),
    '/settings': (context) => SettingsPage(),
    '/profile': (context) => ProfilePage(),
  },
  // 设置初始路由
  initialRoute: '/',
  // 处理未知路由
  onUnknownRoute: (settings) {
    return MaterialPageRoute(
      builder: (context) => NotFoundPage(),
    );
  },
);

基本导航

// 导航到命名路由
Navigator.pushNamed(context, '/login');

// 替换当前路由
Navigator.pushReplacementNamed(context, '/home');

// 清空路由栈并导航
Navigator.pushNamedAndRemoveUntil(
  context,
  '/home',
  (route) => false,
);

路由参数

传递参数

// 导航时传递参数
Navigator.pushNamed(
  context,
  '/detail',
  arguments: {
    'id': '123',
    'title': 'Product Detail',
  },
);

// 接收参数
class DetailPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as Map;
    return Scaffold(
      appBar: AppBar(
        title: Text(args['title']),
      ),
      body: Center(
        child: Text('ID: ${args['id']}'),
      ),
    );
  }
}

完整示例

// 路由配置
class AppRoutes {
  static const String home = '/';
  static const String login = '/login';
  static const String profile = '/profile';
  static const String settings = '/settings';
  static const String detail = '/detail';
  
  static Map<String, WidgetBuilder> get routes => {
    home: (context) => HomePage(),
    login: (context) => LoginPage(),
    profile: (context) => ProfilePage(),
    settings: (context) => SettingsPage(),
    detail: (context) => DetailPage(),
  };
  
  static Route<dynamic>? onUnknownRoute(RouteSettings settings) {
    return MaterialPageRoute(
      builder: (context) => NotFoundPage(),
    );
  }
}

// 主应用
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Named Routes Demo',
      routes: AppRoutes.routes,
      initialRoute: AppRoutes.home,
      onUnknownRoute: AppRoutes.onUnknownRoute,
    );
  }
}

// 首页
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(
                context,
                AppRoutes.login,
              ),
              child: Text('Go to Login'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.pushNamed(
                context,
                AppRoutes.detail,
                arguments: {
                  'id': '123',
                  'title': 'Product Detail',
                },
              ),
              child: Text('View Detail'),
            ),
          ],
        ),
      ),
    );
  }
}

// 详情页
class DetailPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as Map;
    
    return Scaffold(
      appBar: AppBar(
        title: Text(args['title']),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('ID: ${args['id']}'),
            ElevatedButton(
              onPressed: () => Navigator.pop(context),
              child: Text('Go Back'),
            ),
          ],
        ),
      ),
    );
  }
}

路由生成

onGenerateRoute

MaterialApp(
  onGenerateRoute: (settings) {
    if (settings.name == '/detail') {
      final args = settings.arguments as Map;
      return MaterialPageRoute(
        builder: (context) => DetailPage(
          id: args['id'],
          title: args['title'],
        ),
      );
    }
    return null;
  },
);

最佳实践

  1. 路由名收敛到一个文件:RouteNames 类全 static const,跳转只写 Get.toNamed(RouteNames.profile)——实战案例的教训:字符串散落各处,/idcard_verify 写成 /id_card_verify,按钮点了没反应查半天
  2. 路由表集中注册:getPages: RoutePages.list,新增页面只改这一个地方;路由名用小写+下划线(/id_card_verify),别混驼峰
  3. 配 unknownRoute 兜底 404:拼错的路由名跳 404 页而不是白屏/没反应——把”我点的按钮没反应”变成”看得见的错误”
  4. 需要登录的页面挂 middleware:GetPage(middlewares: [AuthMiddleware()]) 统一拦截,别在每个按钮 onPressed 里判登录(进阶篇实战:十几个入口漏了两个,被测试抓包)

注意事项

  1. 路由名全局唯一:/detail 在两个 GetPage 里各注册一次,后注册的覆盖先注册的,跳过去的页面不对还很难查
  2. 带参命名路由用 Get.parameters 取参:/detail?id=123 的 123 用 parameters 取,路由名本身保持干净的 /detail,别把参数拼进路由名字符串
  3. 命名路由和直接传 widget 定一种为主:团队约定好(作者:需深链接/统一管理的走命名,临时弹页走 Get.to(() => Page()))——混用可以,但要有规则
  4. 改路由名用 IDE 重构:static const 的好处就是改名能全局重构;直接改字符串漏一处就是线上 bug

实战案例

命名路由我最大的体会是:路由名一定要收敛到一个文件。在 某物流冷链项目 里我建了 RouteNames 类:static const main = '/'、static const idCardVerify = '/id_card_verify'、static const driverVerify = '/driver_verify' 等十几个,全是 static const。然后 GetMaterialApp(getPages: RoutePages.list) 集中注册。跳转时写 Get.toNamed(RouteNames.profile),IDE 能跳转、改名能重构。反面教材是我早期项目里直接写字符串 '/profile',有一次把 /idcard_verify 写成 /id_card_verify,跑起来点按钮没反应,查了半天。所以命名路由的重点根本不是”怎么命名”,而是”怎么不让字符串散落各处”。

总结

命名路由提供了一种更加结构化的页面导航方式。通过合理使用命名路由,可以让应用的导航逻辑更加清晰和易于维护。


分享这篇文章:


相关文章

上一篇
Flutter 基础导航详解
下一篇
Flutter 路由参数传递详解