github pages

Flutter 基于文件的路由

我受到 remix.run 中的路由(包括嵌套布局和服务器端组件)的启发,所以我决定在 flutter 中进行实验。

由于这需要在编译时完成,我编写了一个生成器来解析 pages 目录以获取基于文件的路由名称,以定义类似 regex_router 的正则表达式。

演示

安装

您需要在本地安装 dart,然后可以在项目目录中运行以下命令:

dart generator/bin/main.dart

这将生成一个 generated.g.dart 文件,可用于导入生成的 widget 以运行应用程序。

import 'package:flutter/material.dart';

import 'generated.g.dart';

void main() {
  runApp(GeneratedApp(
    themeMode: ThemeMode.system,
    theme: ThemeData.light(),
    darkTheme: ThemeData.dark(),
  ));
}

我还包含了一个生成器所需的 router.dart 文件以及所有本地 widget。

定义基础布局

您可以使用根名称定义基础布局。例如:about.dart

import 'package:flutter/material.dart';

import '../generated.g.dart';

class AboutPage extends UiRoute<void> {
  @override
  Widget builder(BuildContext context, void data, Widget? child) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('About'),
      ),
      body: child,
    );
  }
}

如果您注意到,子项可以为 null,并用于嵌套布局。

这不是一个 widget,而是一个我们可以选择性加载数据的类。

定义索引路由

当不需要参数时,您可以为索引路由定义索引路由。例如:about/index.dart

import 'package:flutter/material.dart';

import '../../generated.g.dart';

class AboutDetails extends UiRoute<void> {
  @override
  Widget builder(BuildContext context, void data, Widget? child) {
    return const Center(
      child: Text('About'),
    );
  }
}

由于这是一个嵌套布局,您只需提供组件即可,它将继承自父布局 (about.dart)。

定义命名参数

如果某个路由不需要获取数据,您可以为其定义一个命名参数。例如:/about/guest.dart

import 'package:flutter/material.dart';

import '../../generated.g.dart';

class GuestPage extends UiRoute<void> {
  @override
  Widget builder(BuildContext context, void data, Widget? child) {
    return const Center(
      child: Text('Guest'),
    );
  }
}

这也只是一个组件。

定义动态参数

有时参数是在运行时生成的,或者需要从数据库中提取。例如:about/:id.dart

import 'package:flutter/material.dart';

import '../../generated.g.dart';

class AccountPage extends UiRoute<Map<String, String>> {
  @override
  loader(route, args) => args;

  @override
  Widget builder(
      BuildContext context, Map<String, String> data, Widget? child) {
    return Center(
      child: Text('ID: ${data['id']}'),
    );
  }
}

您可以看到我们使用冒号 (:) 作为前缀来定义要查找和匹配的参数。这将以 map 的形式提供。

加载器可用于从数据库中提取数据,但在本例中,它返回参数 map。默认情况下,它返回 null。

加载器在 widget 构建之前运行。

路由

要导航到另一个页面,您需要分发以下事件,而不是使用 Navigator.of(context)

RoutingRequest('ROUTE_HERE').dispatch(context)

ROUTE_HERE 应该是您的路由名称,例如 /about/30

存储状态

一切都应该是无状态的,但在示例中您可以看到,即使是底部标签导航的索引也可以仅通过路由来完成。

结论

这解决了各种布局问题,可以提供漂亮的 URL,同时只加载一次数据并在需要时进行缓存。

GitHub

查看 Github