05-超级App软件平台@路由规则设计-【声明式路由与Navigator2.0详解】

7 阅读3分钟

本专题为软件平台路由规则设计(含移动端、Web、电脑、手表与手环等智能穿戴)。本文对应「声明式路由与 Navigator 2.0」阶段,介绍状态驱动路由栈、Flutter Navigator 2.0 三件套、go_router 用法,以及 iOSAndroidHarmonyOSMacOSWinOSWebAppReactNativeWatchOS 等平台的声明式/状态驱动思路,含多端代码示例与流程图。多端对照见 08-软件体系与多平台路由对照


一、概念与定位

1.1 命令式 vs 声明式路由

  • 命令式:开发者显式调用「push / pop / replace」等 API,直接操作导航栈;栈状态由多次调用累积而成,难以从「当前状态」反推完整栈。
  • 声明式:用一个状态(如「当前要展示的页面列表」)描述整个路由栈,框架根据该状态推导出应对栈做的增删,从而与「状态驱动 UI」的范式一致,便于深链接、多 Tab、复杂返回栈和 Web URL 同步。

Navigator 2.0 即 Flutter 提供的声明式导航 API:由应用维护 List<Page>(或等价状态),Navigator 根据该列表与旧列表的差异自动执行 push/pop/replace,实现「状态即路由栈」。

1.2 知识结构(思维导图)

mindmap
  root((声明式路由 / Navigator 2.0))
    核心思想
      状态描述栈
      框架推导操作
      URL 与状态同步
     Flutter 三件套
      Router
      RouteInformationParser
      RouterDelegate
    go_router
      path / query / redirect
      refreshListenable
    其他平台 九端
      iOS Android HarmonyOS
      MacOS WinOS WebApp
      ReactNative WatchOS
    应用场景
      深链接
      多 Tab / 底部栏
      Web 同构

二、Flutter Navigator 2.0 架构流程图

flowchart TB
    subgraph 外部
        A[系统 / 浏览器 URL]
        B[RouteInformation]
    end
    subgraph Flutter 层
        C[Router]
        D[RouteInformationParser]
        E[RouterDelegate]
        F[Navigator + pages]
    end
    subgraph 状态
        G[AppRouteState / List of Page]
    end
    A --> B --> C
    C --> D
    D --> G
    G --> E
    E --> F
    F --> H[当前页面树]
    H --> I[用户操作 / 返回]
    I --> G

三、Flutter 核心组件与数据流(泳道图)

flowchart LR
    subgraph 系统
        S1[RouteInformation]
    end
    subgraph Parser
        P1[parseRouteInformation]
        P2[restoreRouteInformation]
    end
    subgraph Delegate
        D1[setNewRoutePath]
        D2[build → Navigator]
        D3[popRoute]
    end
    subgraph 状态
        ST[pages / routeState]
    end
    S1 --> P1 --> ST
    ST --> D2
    D1 --> ST
    D3 --> ST
    P2 --> S1

四、Flutter 代码示例:RouterDelegate + RouteInformationParser

4.1 路由状态

// route_state.dart
class AppRouteState {
  final List<Page<dynamic>> pages;
  const AppRouteState({this.pages = const []});

  AppRouteState push(Page<dynamic> page) {
    return AppRouteState(pages: [...pages, page]);
  }

  AppRouteState pop() {
    if (pages.length <= 1) return this;
    return AppRouteState(pages: pages.sublist(0, pages.length - 1));
  }

  AppRouteState replaceAll(List<Page<dynamic>> newPages) {
    return AppRouteState(pages: newPages);
  }
}

4.2 RouteInformationParser:URL ↔ 状态

// app_route_parser.dart
class AppRouteInformationParser extends RouteInformationParser<AppRouteState> {
  @override
  Future<AppRouteState> parseRouteInformation(RouteInformation info) async {
    final uri = Uri.parse(info.location ?? '/');
    final path = uri.path;
    final query = uri.queryParameters;
    final pages = <Page<dynamic>>[
      MaterialPage(child: HomePage(), name: '/'),
    ];
    if (path.startsWith('/detail/')) {
      final id = path.split('/').last;
      pages.add(MaterialPage(
        child: DetailPage(id: id, extra: query['from']),
        name: '/detail/$id',
      ));
    } else if (path == '/list') {
      pages.add(MaterialPage(child: ListPage(), name: '/list'));
    }
    return AppRouteState(pages: pages);
  }

  @override
  RouteInformation? restoreRouteInformation(AppRouteState state) {
    if (state.pages.isEmpty) return null;
    final last = state.pages.last;
    return RouteInformation(location: last.name);
  }
}

4.3 RouterDelegate:状态 → Navigator

// app_router_delegate.dart
class AppRouterDelegate extends RouterDelegate<AppRouteState>
    with PopNavigatorRouterDelegateMixin<AppRouteState>, ChangeNotifier {
  AppRouterDelegate() : _state = AppRouteState();

  AppRouteState _state;
  AppRouteState get state => _state;

  @override
  GlobalKey<NavigatorState> get navigatorKey => _navigatorKey;
  final _navigatorKey = GlobalKey<NavigatorState>();

  void push(Page<dynamic> page) {
    _state = _state.push(page);
    notifyListeners();
  }

  void pop() {
    _state = _state.pop();
    notifyListeners();
  }

  @override
  AppRouteState get currentConfiguration => _state;

  @override
  Future<void> setNewRoutePath(AppRouteState configuration) async {
    _state = configuration;
    notifyListeners();
  }

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: _state.pages,
      onPopPage: (route, result) {
        if (!route.didPop(result)) return false;
        pop();
        return true;
      },
    );
  }
}

4.4 MaterialApp 接入

// main.dart
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final _delegate = AppRouterDelegate();
  final _parser = AppRouteInformationParser();

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerDelegate: _delegate,
      routeInformationParser: _parser,
    );
  }
}

4.5 业务侧「声明式」跳转

不直接操作栈,而是通过更新状态(由 Delegate 暴露的方法)驱动:

// 获取 delegate 可通过 InheritedWidget 或全局
routerDelegate.push(MaterialPage(
  child: DetailPage(id: '123'),
  name: '/detail/123',
));

五、go_router 使用示例(推荐实践)

go_router 在 Navigator 2.0 之上封装了 path/query、redirect、refreshListenable,更贴近 Web 路由与深链接。

5.1 基本配置(Dart)

// go_router_config.dart
final goRouter = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(path: '/', builder: (_, __) => HomePage()),
    GoRoute(
      path: '/detail/:id',
      builder: (_, state) => DetailPage(
        id: state.pathParameters['id']!,
        from: state.uri.queryParameters['from'],
      ),
    ),
    GoRoute(path: '/list', builder: (_, __) => ListPage()),
  ],
  redirect: (context, state) {
    // 未登录时重定向到登录页
    if (!AuthManager.isLoggedIn && state.matchedLocation != '/login') {
      return '/login';
    }
    return null;
  },
  refreshListenable: AuthNotifier(), // 登录态变化时重新执行 redirect
);

5.2 跳转与传参(Dart)

// 跳转
context.go('/detail/123');
context.go('/detail/123?from=home');
context.push('/list');

// 带 extra
context.push('/detail/123', extra: MyData());
// 在 builder 中: state.extra as MyData

5.3 与深链接统一

系统传入的 RouteInformation(如 https://example.com/detail/1)经 go_router 解析为 path /detail/1 与 query,与 App 内 context.go('/detail/1') 行为一致,实现「一条 URL、一套状态」。


六、状态差异与栈操作推导(伪代码)

声明式路由的核心:根据「目标页面列表」与「当前栈」的差异,推导出应执行的 push/pop/replace。

函数 computeStackDiff(currentPages, targetPages):
  // currentPages、targetPages 均为页面标识列表(如 path 或 name)
  // 返回待执行操作序列: [{ op: "push", page }, { op: "pop" }, { op: "replace", page }]
  1. 找到最长公共前缀长度 prefixLen(从前往后逐项比较)
  2. 若 targetPages.length > currentPages.length:
       对 i = prefixLen 到 targetPages.length-1,生成 push(targetPages[i])
  3. 若 targetPages.length < currentPages.length:
       生成 (currentPages.length - targetPages.length) 次 pop
  4. 若 prefixLen < 两者最小值 且 某项不同:
       可生成 replace(targetPages[prefixLen]) 再 push 后续
  5. 返回操作序列

七、iOS / Android 的声明式思路简述

7.1 iOS:用状态描述「要展示的 VC 栈」

不直接 push/pop,而是维护一个「要展示的 VC 列表」状态,根据状态与当前栈的差异做 push/pop:

// 状态
struct AppRouteState {
    var stack: [RouteItem]  // [.home, .detail(id: "123")]
}

// 根据 state 与当前 navigationController.viewControllers 的差异,执行 push/pop
func apply(state: AppRouteState) {
    let current = nav.viewControllers.map { ... }
    let target = state.stack.map { createVC($0) }
    // 计算 diff,执行 setViewControllers 或 push/pop
}

7.2 Android:用状态描述「Activity 栈或 Fragment 栈」

同理,用「要展示的页面列表」作为唯一真实来源,通过 NavController + 自定义 BackStack 或 Single Activity + Fragment 栈的 state 驱动:

// 例如 Jetpack Navigation 的 NavBackStackEntry 列表即「状态」
// 通过 NavController.navigate(uri) 或 NavController.popBackStack() 更新栈
// 深链接时:NavController.setGraph() 或 handleDeepLink(intent) 设置与 URL 一致的状态

八、声明式路由与 URL 同步时序图

sequenceDiagram
    participant U as 用户/系统
    participant P as Parser
    participant State as RouteState
    participant D as Delegate
    participant Nav as Navigator

    U->>P: RouteInformation (URL)
    P->>State: parseRouteInformation → new state
    State->>D: setNewRoutePath(state)
    D->>D: notifyListeners
    D->>Nav: build(pages: state.pages)
    Nav->>Nav: 对比旧 pages,执行 push/pop/replace

    Note over U,Nav: 用户点击返回
    Nav->>D: onPopPage
    D->>State: pop()
    D->>P: restoreRouteInformation(state)
    P-->>U: RouteInformation (更新 URL)

九、各平台声明式/状态驱动路由

平台声明式/状态驱动体现
FlutterNavigator 2.0、go_router(state → pages)
WebAppReact Router / Vue Router(URL/state 即路由)
ReactNativeReact Navigation(state 驱动栈)
iOS / MacOS可自建「目标 VC 列表」状态,diff 后 setViewControllers
AndroidJetpack Navigation、Single Activity + Fragment 状态
HarmonyOS页面栈与 state 对应,可声明式更新栈
WinOS框架内 Frame 与 state 绑定
WatchOS层级少,可简单 state 驱动界面栈

十、小结

  • 声明式路由:用「状态」描述整棵路由栈,由框架根据状态差异推导 push/pop/replace,便于与 URL、深链接、多 Tab 一致。
  • Flutter Navigator 2.0Router + RouteInformationParser(URL ↔ 状态)+ RouterDelegate(状态 → Navigator.pages),实现状态驱动栈;go_router 在此基础上提供 path/query、redirect、refreshListenable,推荐在新项目或需要深链接时使用。
  • iOS/Android:可借鉴「用状态描述栈 + 根据状态 diff 更新 VC/Activity/Fragment 栈」的思路,与 Flutter 声明式模型对齐。

十一、参考文献

  • Flutter. Understanding Navigator 2.0.
  • Flutter. go_router.
  • 《01-软件平台路由规则设计-总纲》§2.2、§6.3.