[译]Flutter Favorite之路由包beamer
原文链接:beamer | Flutter Package (flutter-io.cn)
多有不足,不吝赐教
beamer
beamer.dev 支持空安全
处理应用在所有平台上的路由,并同步浏览器 URL 地址栏和其它内容。 Beamer 基于 Router 并实现了所有的底层逻辑。
快速开始
快速开始
最简单的使用是用 RoutesLocationBuilder
实现,这种方式产出的代码最少。对于导航场景较少的应用或者页面栈浅的应用(即页面很少堆叠在一起)来说,是很棒的选择。
class MyApp extends StatelessWidget {
final routerDelegate = BeamerDelegate(
locationBuilder: RoutesLocationBuilder(
routes: {
// Return either Widgets or BeamPages if more customization is needed
// 返回 Widgets 或 BeamPages(如果需要更多定制的话)
'/': (context, state, data) => HomeScreen(),
'/books': (context, state, data) => BooksScreen(),
'/books/:bookId': (context, state, data) {
// Take the path parameter of interest from BeamState
// 从 BeamState 获取路径参数
final bookId = state.pathParameters['bookId']!;
// Collect arbitrary data that persists throughout navigation
// 收集在整个导航过程中持续存在的任意数据
final info = (data as MyObject).info;
// Use BeamPage to define custom behavior
// 使用 BeamPage 来自定义行为
return BeamPage(
key: ValueKey('book-$bookId'),
title: 'A Book #$bookId',
popToNamed: '/',
type: BeamPageType.scaleTransition,
child: BookDetailsScreen(bookId, info),
);
}
},
),
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationParser: BeamerParser(),
routerDelegate: routerDelegate,
);
}
}
RoutesLocationBuilder
会根据路径对 routes
进行选择和排序。
例如,导航到 /books/1
会匹配 routes
里的全部3个实体,然后把它们堆叠在一起。导航到 /books
会匹配routes
的前两个实体。
对应的页面被放入到 Navigator.pages
中,BeamerDelegate
(重新)构建 Navigator
,在屏幕上显示选中的页面栈。
为什么我们有一个 locationBuilder
? BeamLocation
是什么?它的输出是什么?
BeamLocation
是一个实体,它基于它的 state
来决定哪个页面要进入到 Navigator.pages
里。locationBuilder
选择适当的 BeamLocation
来进一步处理收到的 RouteInformation
。
这大多数是通过验证 BeamLocation.pathPatterns
来实现。
RoutesLocationBuilder
返回 BeamLocation
的一个特殊类型 - RoutesBeamLocation
,它有用于绝大多数常用的导航场景的实现。
如果 RoutesLocationBuilder
没有提供所需的行为或者足够的定制,可以扩展 BeamLocation
为进入到 Navigator.pages
的任意数量的页面栈来定义和组织行为。
深入阅读: BeamLocation [中文],BeamState[中文]。
导航
导航是用 "beam" 来完成的。可以认为是在应用中传送(beam)到其它地方。
类似于 Navigator.of(context).pushReplacementNamed('/my-route')
,但是 Beamer 并不限于单个页面,或者是推入栈本身。
BeamLocation
创建页面的栈,当 beam 到某个页面时,页面会被构建。
Beaming 感觉像是同时使用了多个 Navigator
的 push/pop
(入栈/出栈)方法。
// Basic beaming
Beamer.of(context).beamToNamed('/books/2');
// Beaming with an extension method on BuildContext
// 使用 BuildContext 的扩展方法 Beaming
context.beamToNamed('/books/2');
// Beaming with additional data that persist
// throughout navigation withing the same BeamLocation
// 用同一个 BeamLocation 的导航过程中的数据 Beaming。
context.beamToNamed('/book/2', data: MyObject());
导航返回
这里有两种返回的类型,即 reverse navigation(反转导航); 向上 和 反转时序.
向上 (从栈中弹出页面)
向上导航是指导航到当前页面栈的前一个页面。就是大家熟知的弹出,通过 Navigator
的 pop
/maybePop
方法来完成。如果不指定其它处理,默认的 AppBar
的 BackButton
返回按钮会调用这个方法。
Navigator.of(context).maybePop();
反转时序 ( beam 到前一个状态)
反转时序导航会导航到前面访问过的任意地方。在深度链接的情况(例如:从 /authors/3
导航到 /books/2
,而不是从 /books
导航到 /books/2
)下,这和弹出是不一样的。 Beamer 在 beamingHistory
历史中保持着导航历史,所以它能够导航到 beamingHistory
中的前一个时间点的入口。这称作 "beam back" (回光返照?皮一下)。
Beamer.of(context).beamBack();
Android 返回按键
集成 beam 的 Android 返回按键通过在 MaterialApp.router
中设置 backButtonDispatcher
来实现。这个分发器需要指向同一个为routerDelegate
设置的 BeamerDelegate
的引用。
MaterialApp.router(
...
routerDelegate: beamerDelegate,
backButtonDispatcher: BeamerBackButtonDispatcher(delegate: beamerDelegate),
)
BeamerBackButtonDispatcher
会首先尝试 pop
(弹出),如果弹出不可用,会改为 beamBack
。如果 beamBack
返回 false
(没有地方可返回),Android 的返回按钮会关闭应用,也可能是返回前一个使用的应用(通过 deep-link (深度链接)打开当前应用)。 BeamerBackButtonDispatcher
可以配置为 alwaysBeamBack
(意思是不会尝试 pop
(弹出))或 fallbackToBeamBack
(意思是不会尝试 beamBack
)。
访问最近的 Beamer
要在组件中访问路由的属性(例如,用于构建 BookDetailsScreen
的 bookId
)可以使用:
@override
Widget build(BuildContext context) {
final beamState = Beamer.of(context).currentBeamLocation.state as BeamState;
final bookId = beamState.pathParameters['bookId'];
...
}
使用 "Navigator 1.0"
注意 "Navigator 1.0"(命令式的 push/pop 和类似的函数)可以和 Beamer 一起使用。我们已经看到 Navigator.pop
用来向上导航。这告诉我们是在使用同样的 Navigator
,只是使用了不同的 API 。
用 Navigator.of(context).push
(或任何类似的动作) 入栈不会反映到 BeamLocation
的状态,这意味着浏览器的 URL 不会改变。可以通过 Beamer.of(context).updateRouteInformation(...)
来只更新 URL 。当然在移动端使用 Beamer 时不会有这个问题,因为看不到 URL 。
通常,每个导航场景应该是可实现的声明式(定义页面栈),而不是命令式(入栈),但是做到这一点的难度会有所不同。
对于中级和高级的用法,现在介绍一些核心概念: BeamLocation
和 BeamState
。
核心概念
从最顶层来看,Beamer
是 Router
的包装,它使用 了自身的对 RouterDelegate
和 RouteInformationParser
的实现。Beamer 的目标是分离【用不同的状态为 Navigator.pages
的多个类来构建页面栈】的职责,代替所有页面栈使用一个全局状态。
例如,我们想要处理所有个人资料相关的页面栈如:
[ ProfilePage ]
(个人资料页面),[ ProfilePage, FriendsPage]
(个人资料页面,好友页面),[ ProfilePage, FriendsPage, FriendDetailsPage ]
(个人资料页面,好友页面,好友详细页面),[ ProfilePage, SettingsPage ]
(个人资料页面,设定页面),- ...
用一些 "ProfileHandler" 来知道哪个状态对应哪个页面栈。类似地,我们想要一个 "ShopHandler" 来处理所有商店关联的页面栈。这些页面如:
[ ShopPage ]
(商店页面),[ ShopPage, CategoriesPage ]
(商店页面,品类页面),[ ShopPage, CategoriesPage, ItemsPage ]
(商店页面,品类页面,商品页面),[ ShopPage, CategoriesPage, ItemsPage, ItemDetailsPage ]
(商店页面,品类页面,商品页面,商品详细页面),[ ShopPage, ItemsPage, ItemDetailsPage ]
(商店页面,商品页面,商品详细页面),[ ShopPage, CartPage ]
(商店页面,购物车页面),- ...
这些 "Handlers" 被称为 BeamLocation
。
BeamLocation
自身无法工作。当 RouteInformation
作为 beaming 的初始状态或者结果通过深度链接进入应用时,需要决定哪个 BeamLocation
如何进一步处理 RouteInformation
和为 Navigator
构建页面。 这是 BeamerDelegate.locationBuilder
的工作,它会接收 RouteInformation
, 然后根据其 pathPatterns
(路径模式)传给正确的 BeamLocation
。
之后 BeamLocation
会从 RouteInformation
创建和保存属于它的状态,用于构建一个页面栈。
BeamLocation
Beamer 中最重要的构成是 BeamLocation
, 它呈现一个页面或多个页面的状态。
BeamLocation
有三个重要角色:
- 知道它能处理哪些 URI :
pathPatterns
- 知道如何构建页面栈 :
buildPages
- 保持状态(
state
),状态为上面两者之间提供链接。
BeamLocation
是一个抽象类,需要被实现。具有多个 BeamLocation
的目的是为了在应用中从架构上分离不相关的 “场所” 。例如,BooksLocation
可以处理所有和书相关的页面, ArticlesLocation
可以处理所有和文章相关的内容。
以下是 BeamLocation
的一个例子:
class BooksLocation extends BeamLocation<BeamState> {
@override
List<Pattern> get pathPatterns => ['/books/:bookId'];
@override
List<BeamPage> buildPages(BuildContext context, BeamState state) {
final pages = [
const BeamPage(
key: ValueKey('home'),
child: HomeScreen(),
),
if (state.uri.pathSegments.contains('books'))
const BeamPage(
key: ValueKey('books'),
child: BooksScreen(),
),
];
final String? bookIdParameter = state.pathParameters['bookId'];
if (bookIdParameter != null) {
final bookId = int.tryParse(bookIdParameter);
pages.add(
BeamPage(
key: ValueKey('book-$bookIdParameter'),
title: 'Book #$bookIdParameter',
child: BookDetailsScreen(bookId: bookId),
),
);
}
return pages;
}
}
BeamState
BeamState
是一个预制的状态,它可以用于自定义的 BeamLocation
。
它保持着各种 URI 的属性如 pathPatternSegments
(选择路径模式的片段,一个 BeamLocation
能支持多个这样的片段)、pathParameters
和 queryParameters
。
自定义状态
任何类都可以用作 BeamLocation
的状态,例如 ChangeNotifier
。唯一的要求是 BeamLocation
的状态混合(mix) RouteInformationSerializable
, 后者会强制实现 fromRouteInformation
和 toRouteInformation
。
完事的示例参考 这里。
一个自定义的 BooksState
:
class BooksState extends ChangeNotifier with RouteInformationSerializable {
BooksState([
bool isBooksListOn = false,
int? selectedBookId,
]) : _isBooksListOn = isBooksListOn,
_selectedBookId = selectedBookId;
bool _isBooksListOn;
bool get isBooksListOn => _isBooksListOn;
set isBooksListOn(bool isOn) {
_isBooksListOn = isOn;
notifyListeners();
}
int? _selectedBookId;
int? get selectedBookId => _selectedBookId;
set selectedBookId(int? id) {
_selectedBookId = id;
notifyListeners();
}
void updateWith(bool isBooksListOn, int? selectedBookId) {
_isBooksListOn = isBooksListOn;
_selectedBookId = selectedBookId;
notifyListeners();
}
@override
BooksState fromRouteInformation(RouteInformation routeInformation) {
final uri = Uri.parse(routeInformation.location ?? '/');
if (uri.pathSegments.isNotEmpty) {
_isBooksListOn = true;
if (uri.pathSegments.length > 1) {
_selectedBookId = int.parse(uri.pathSegments[1]);
}
}
return this;
}
@override
RouteInformation toRouteInformation() {
String uriString = '';
if (_isBooksListOn) {
uriString += '/books';
}
if (_selectedBookId != null) {
uriString += '/$_selectedBookId';
}
return RouteInformation(location: uriString.isEmpty ? '/' : uriString);
}
}
然后使用上面的状态的 BeamLocation
会查找以下内容。注意如果自定义状态不是 ChangeNotifier
的话,并不是所有这些都需要重写。
class BooksLocation extends BeamLocation<BooksState> {
BooksLocation(RouteInformation routeInformation) : super(routeInformation);
@override
BooksState createState(RouteInformation routeInformation) =>
BooksState().fromRouteInformation(routeInformation);
@override
void initState() {
super.initState();
state.addListener(notifyListeners);
}
@override
void updateState(RouteInformation routeInformation) {
final booksState = BooksState().fromRouteInformation(routeInformation);
state.updateWith(booksState.isBooksListOn, booksState.selectedBookId);
}
@override
void disposeState() {
state.removeListener(notifyListeners);
super.disposeState();
}
@override
List<Pattern> get pathPatterns => ['/books/:bookId'];
@override
List<BeamPage> buildPages(BuildContext context, BooksState state) {
final pages = [
const BeamPage(
key: ValueKey('home'),
child: HomeScreen(),
),
if (state.isBooksListOn)
const BeamPage(
key: ValueKey('books'),
child: BooksScreen(),
),
];
if (state.selectedBookId != null) {
pages.add(
BeamPage(
key: ValueKey('book-${state.selectedBookId}'),
title: 'Book #${state.selectedBookId}',
child: BookDetailsScreen(bookId: state.selectedBookId),
),
);
}
return pages;
}
}
使用自定义 BooksState
时,可以通过下面的写法完全使用声明式:
onTap: () {
final state = context.currentBeamLocation.state as BooksState;
state.selectedBookId = 3;
},
注意 Beamer.of(context).beamToNamed('/books/3')
会生成同样的结果。
用法
使用 Beamer (或任何 Router
),必须构造带 .router
构造器(更多内容可查看Router documentation)的 *App
组件。和所有 *App
的常规属性一起,我们必须提供:
routeInformationParser
解析传入的 URI 。routerDelegate
控制(重新)构建Navigator
。
这里我们使用 BeamerParser
和 BeamerDelegate
在 Beamer 中的实现,给这两者传递所需的 LocationBuilder
。用最简单的形式,LocationBuilder
只是一个函数,它接收当前的 RouteInformation
(和 BeamParameters
(在这里并 不重要) ) ,返回基于 URI 或 其它状态属性的 BeamLocation
。
class MyApp extends StatelessWidget {
final routerDelegate = BeamerDelegate(
locationBuilder: (routeInformation, _) {
if (routeInformation.location!.contains('books')) {
return BooksLocation(routeInformation);
}
return HomeLocation(routeInformation);
},
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: routerDelegate,
routeInformationParser: BeamerParser(),
backButtonDispatcher:
BeamerBackButtonDispatcher(delegate: routerDelegate),
);
}
}
如果我们不想自定义一个 locationBuilder
函数,这里还有两个可用的选项。
BeamLocation 列表
BeamerLocationBuilder
可以指定 BeamLocation
列表。该构建器将根据其pathPatterns
(路径模式)自动选择正确的 BeamLocation
。
final routerDelegate = BeamerDelegate(
locationBuilder: BeamerLocationBuilder(
beamLocations: [
HomeLocation(),
BooksLocation(),
],
),
);
路由 Map
我们可以给 RoutesLocationBuilder
指定路由 Map ,如 快速开始[中文]中提到的。
这会完全移除自定义 BeamLocation
的需要,但是也会提供一个最少量的定制。尽管如此,通配符和路径参数会和其它所有选项一起被支持。
final routerDelegate = BeamerDelegate(
locationBuilder: RoutesLocationBuilder(
routes: {
'/': (context, state, data) => HomeScreen(),
'/books': (context, state, data) => BooksScreen(),
'/books/:bookId': (context, state, data) =>
BookDetailsScreen(
bookId: state.pathParameters['bookId'],
),
},
),
);
守护
要守护特定的路由,例如防止未授权的用户访问,全局的 BeamGuard
可通过 BeamerDelegate.guards
属性设置。 一个很常用的例子是如果用户未被授权的话, BeamGuard
来守护任意未 /login
(登录) 的路由,然后重定向到 /login
。
BeamGuard(
// on which path patterns (from incoming routes) to perform the check
// 通过路径模式(传入的路由)来进行 检查
pathPatterns: ['/login'],
// perform the check on all patterns that **don't** have a match in pathPatterns
// 对所有路径模式里未匹配的模式进行检查
guardNonMatching: true,
// return false to redirect
// 返回 false 来重定向
check: (context, location) => context.isUserAuthenticated(),
// where to redirect on a false check
// 返回 false 时重定向的位置
beamToNamed: (origin, target) => '/login',
)
注意本例中 guardNonMatching
的使用。这很重要,因为守护(这里有很多守护,每一个守护不同的方面)会递归运行在前一个守护的输出上直到到达一个 “安全” 的路由。一个常见的错误是安装带 pathBlueprints:['*']
的守护来守护所有,但是所有也包括 /login
(这是一个 “安全” 的路由),这样就导致陷入了一个无限循环:
- 检查
/login
- 用户未授权
- beam 到
/login
- 检查
/login
- 用户未授权
- beam 到
/login
- 。。。
当然,需要不使用 guardNonMatching
。有时我们只想守护少量明确指定的路由。这里有个和上面同样角色的守护,默认实现为 guardNonMatching: false
:
BeamGuard(
pathBlueprints: ['/profile/*', '/orders/*'],
check: (context, location) => context.isUserAuthenticated(),
beamToNamed: (origin, target) => '/login',
)
嵌套导航
需要嵌套导航时,可以把 Beamer
放在组件树中进行嵌套导航的任何位置。这并不会限制一个应用里有多少 Beamer
。 常用的使用场景是底部导航栏(查看示例),如下:
class MyApp extends StatelessWidget {
final routerDelegate = BeamerDelegate(
initialPath: '/books',
locationBuilder: RoutesLocationBuilder(
routes: {
'/*': (context, state, data) {
final beamerKey = GlobalKey<BeamerState>();
return Scaffold(
body: Beamer(
key: beamerKey,
routerDelegate: BeamerDelegate(
locationBuilder: BeamerLocationBuilder(
beamLocations: [
BooksLocation(),
ArticlesLocation(),
],
),
),
),
bottomNavigationBar: BottomNavigationBarWidget(
beamerKey: beamerKey,
),
);
}
},
),
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: routerDelegate,
routeInformationParser: BeamerParser(),
);
}
}
一般注意事项
-
当扩展
BeamLocation
时,需要实现两个方法:pathPatterns
和buildPages
。-
buildPages
返回页面栈,当 beam 到这些页面时,Navigator
会构建页面。然后Beamer
的pathPatterns
会决定哪个BeamLocation
处理哪个 URI 。 -
BeamLocation
会将 URI 的查询和路径模式保持在其BeamState
中。如果要从浏览器获取路径参数,那pathPatterns
中的:
是必须的。
-
-
BeamPage
的 child 是呈现应用屏幕/界面的任意组件。-
key
对于Navigatar
优化重新构建是重要的。这在 "页面状态" 中应该是唯一值。 -
BeamPage
默认创建MaterialPageRoute
, 但是也可以通过把BeamPage.type
设置为可用的BeamPageType
来选择其它的变换。
-
提示和常见问题
- 可在
runApp()
之前调用Beamer.setPathUrlStrategy()
移除 URL 中的#
。 BeamPage.title
默认用来设置浏览器的标签页的标题,可以通过将BeamerDelegate.setBrowserTabTitle
设置为false
来选择移除。- 热加载中状态丢失
示例
在这里查看所有示例(带 gif )
- Location Builders: 基于这篇文章 重新创建示例应用,可以在这里学习很多关于 Navigator 2.0 的知识。该示例展示了
locationBuilder
的所有3个选项。
-
Advanced Books: 作为进阶,添加了更多流程来展示 Beamer 的强大能力。
-
Deep Location: 在已经堆入栈的多个页面中,可以在应用中马上 beam 到一个位置,然后一个接一个地弹出它们或简单地
beamBack
到跳转来的地方。注意beamToNamed
的beamBackOnPop
参数有助于覆写使用beamBack
进行AppBar
的pop
。
ElevatedButton(
onPressed: () => context.beamToNamed('/a/b/c/d'),
//onPressed: () => context.beamToNamed('/a/b/c/d', beamBackOnPop: true),
child: Text('Beam deep'),
),
- Provider: 可以覆写
BeamLocation.builder
来提供一些数据用于整个 location,即用于所有的pages
(页面)。
// 在你的 BeamLocation 实现中
@override
Widget builder(BuildContext context, Navigator navigator) {
return MyProvider<MyObject>(
create: (context) => MyObject(),
child: navigator,
);
}
- Guards: 可以定义全局守护(例如,授权守护)或用于保持特定的状态安全的
BeamLocation.guards
。
// BeamerDelegate 中的全局守护
BeamerDelegate(
guards: [
// 如果用户没有授权,则通过 beam 到 /login 来守护 /books 和 /books/* :
BeamGuard(
pathBlueprints: ['/books', '/books/*'],
check: (context, location) => context.isAuthenticated,
beamToNamed: (origin, target) => '/login',
),
],
...
),
// BeamLocation 中的当前守护
@override
List<BeamGuard> get guards => [
// 如果用户尝试进入 books/2 则显示禁止页面。
BeamGuard(
pathBlueprints: ['/books/2'],
check: (context, location) => false,
showPage: forbiddenPage,
),
];
- Authentication Bloc: 该示例展示了如何使用
BeamGuard
和 flutter_bloc 为授权流程进行状态管理。 - Bottom Navigation: 该示例是使用底部导航栏时把
Beamer
放到组件树里。 - Bottom Navigation With Multiple Beamers: 每个标签页有一个
Beamer
。 - Nested Navigation: 嵌套路由的侧边抽屉栏。
注意: 在所有的嵌套 Beamer
中,定义 BeamLocation
时和 beam 时必须指定完整的路径。
- Animated Rail: 使用 animated_rail 包的示例。
迁移
从 0.14 迁移到 1.0.0
Medium 的这篇文章 说明了两个版本间的变化,并提供了一个迁移向导。最值得注意的破坏性变化:
- 如果使用
SimpleLocationBuilder
:
代替
locationBuilder: SimpleLocationBuilder(
routes: {
'/': (context, state) => MyWidget(),
'/another': (context, state) => AnotherThatNeedsState(state)
}
)
现在是
locationBuilder: RoutesLocationBuilder(
routes: {
'/': (context, state, data) => MyWidget(),
'/another': (context, state, data) => AnotherThatNeedsState(state)
}
)
- 如果使用一个自定义的
BeamLocation
:
代替
class BooksLocation extends BeamLocation {
@override
List<Pattern> get pathBlueprints => ['/books/:bookId'];
...
}
现在是
class BooksLocation extends BeamLocation<BeamState> {
@override
List<Pattern> get pathPatterns => ['/books/:bookId'];
...
}
从 0.13 迁移到 0.14
代替
locationBuilder: SimpleLocationBuilder(
routes: {
'/': (context) => MyWidget(),
'/another': (context) {
final state = context.currentBeamLocation.state;
return AnotherThatNeedsState(state);
}
}
)
现在是
locationBuilder: SimpleLocationBuilder(
routes: {
'/': (context, state) => MyWidget(),
'/another': (context, state) => AnotherThatNeedsState(state)
}
)
从 0.12 迁移到 0.13
BeamerRouterDelegate
重命名为BeamerDelegate
BeamerRouteInformationParser
重命名为BeamerParser
pagesBuilder
重命名为buildPages
Beamer.of(context).currentLocation
重命名为Beamer.of(context).currentBeamLocation
从 0.11 迁移到 0.12
- 不再有
RootRouterDelegate
,只是重命名为BeamerDelegate
。如果你在使用它的homeBuilder
,使用SimpleLocationBuilder
然后改为routes: {'/': (context) => HomeScreen()}
. beamBack
的行为改为跳转到前一个BeamState
,而不是BeamLocation
。如果这不是你想要的,使用popBeamLocation()
和原来的beamback
的行为是一样的。
从 0.10 迁移到 0.11
BeamerDelegate.beamLocations
现在是locationBuilder
。看下BeamerLocationBuilder
用于最简单的迁移。Beamer
现在接收BeamerDelegate
,而不是直接接收BeamLocations
。buildPages
现在也可以携带state
。
从 0.9 迁移到 0.10
-
BeamLocation
构造器现在只接收BeamState state
。(这里没有必要定义特殊的构造器,如果使用beamToNamed
,可以调用super
)。 -
原来
BeamLocation
的大多数属性现在在BeamLocation.state
里。通过BeamLocation
来访问它们:pathParameters
现在是state.pathParameters
queryParameters
现在是state.queryParameters
data
现在是state.data
pathSegments
现在是state.pathBlueprintSegments
uri
现在是state.uri
从 0.7 迁移到 0.8
pages
重命名为BeamLocation
里的buildPages
- 传递
beamLocations
给BeamerDelegate
代替BeamerParser
。 查看 用法
从 0.4 迁移到 0.5
- 代替使用
Beamer
包装MaterialApp
,使用*App.router()
。 String BeamLocation.pathBlueprint
现在是List<String> BeamLocation.pathBlueprints
- 移除
BeamLocation.withParameters
构造器, 所有的参数用一个构造器处理。如果需要super
,请查看示例。 BeamPage.page
现在称作BeamPage.child
。
帮助和联络
有任何问题、疑问、建议、好玩的想法。。。,在 Discord 上加入我们。
贡献
如果您注意到任何 BUG , 但是没有在 issues 中,请创建一个新 issue 。 如果您想自己进行修复或加强,非常欢迎您提出 PR ,提出 PR 之前:
- 如果想要解决一个存在的 issue ,请首先在 issue 的评论里告诉我们。
- 如果是有其它的加强的想法,先创建一个 issue ,这样我们可以讨论您的想法。
期待在我们尊敬的贡献者列表里看到您!
- devj3ns
- ggirotto
- youssefali424
- schultek
- hatem-u
- matuella
- jeduden
- omacranger
- spicybackend
- ened
- AdamBuchweitz
- nikitadol
- gabriel-mocioaca
- piyushchauhan
- britannio
- satyajitghana
- Zambrella
- luketg8
- timshadel
- definev
任何一种技艺达到完美~ 都会令人无法抗拒~
转载自:https://juejin.cn/post/7043058554869137439