前言
我相信大家一定遇到过这种情况,创建新组件继承另一个组件,光写构造参数就要写半天,不相信的话,你去继承个 TextField 试试,巨麻烦。
在 Dart 2.17 发布之后,支持超类的初始化构造(super parameters), 这真的是非常实用的一个支持。
但是这个改动,对于 ff_annotation_route | Dart Package (flutter-io.cn) 来说,就是一个新挑战了。
解析单个文件
之前的法法路由是利用 analyzer | Dart Package (flutter-io.cn) 库中的
parseFile
方法对单个 dart
文件进行解析, 并且找出其中包含的 @FFRoute
的类,对类和注解进行解释,最终生成路由。
ParseStringResult parseFile(
{required String path,
ResourceProvider? resourceProvider,
required FeatureSet featureSet,
bool throwIfDiagnostics = true}) {
resourceProvider ??= PhysicalResourceProvider.INSTANCE;
var content = (resourceProvider.getResource(path) as File).readAsStringSync();
return parseString(
content: content,
path: path,
featureSet: featureSet,
throwIfDiagnostics: throwIfDiagnostics);
}
解析全部 packages 和 sdk
通过 parseFile
对单个 dart
文件进行解析,是很难处理 super parameters
。实际上 analyzer | Dart Package (flutter-io.cn) 提供 AnalysisContextCollection
来帮助我们对 packages
和 sdk
一起来做解析。
factory AnalysisContextCollection({
required List includedPaths,
List? excludedPaths,
ResourceProvider? resourceProvider,
String? sdkPath,
}) = AnalysisContextCollectionImpl;
- 将你想要解析的路径传递丢进去,可以是一个文件夹,也可以是单个文件
AnalysisContextCollection collection = AnalysisContextCollection(
includedPaths: [libPath],
resourceProvider: PhysicalResourceProvider.INSTANCE,
);
- 这里是解析某个项目,所以传的是项目的
lib
文件夹,通过AnalysisContextCollection
获取到它对应的AnalysisContext
final AnalysisContext context = collection.contextFor(libPath);
for (final String filePath in context.contextRoot.analyzedFiles()) {
if (!filePath.endsWith('.dart')) {
continue;
}
// do something
}
- 获取某个文件的解析结果
CompilationUnitElement fileElement = (await analysisSession.getUnitElement(filePath) as UnitElementResult)
.element;
- 定义
TypeChecker
,下面是通过RuntimeType
或者Url
定义的例子,需要引入 source_gen | Dart Package (flutter-io.cn).
TypeChecker fFRouteTypeChecker = const TypeChecker.fromRuntime(FFRoute);
TypeChecker functionalWidgetTypeChecker = const TypeChecker.fromUrl(
'package:functional_widget_annotation/functional_widget_annotation.dart#FunctionalWidget');
- 通过
TypeChecker
去寻找注解
for (final ClassElement classElement in fileElement.classes) {
DartObject? annotation = fFRouteTypeChecker.firstAnnotationOf(classElement,throwOnUnresolved: true,);
}
- 通过
ConstantReader
获取注解的一些参数
if( annotation != null) {
final ConstantReader reader = ConstantReader(annotation);
// reader.peek('routeName')?.stringValue
// reader.peek('argumentImports')?.listValue.map((DartObject e) => e.toStringValue()!).toList()
}
这里啰嗦讲了下大概的流程,是因为讲这方面实在是太少了,全靠看源码,一步一步调试得来的,后面就是具体对 ClassElement
分析了,这里就不展开讲了。由于 analyzer | Dart Package (flutter-io.cn)的实现没有直接 export
出来,下面把用到的一些文件和类都列举一下。
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/constant/value.dart';
import 'package:analyzer/src/dart/element/type.dart';
DartObjectImpl
,InterfaceTypeImpl
,FunctionTypeImpl
VoidTypeImpl
,DynamicTypeImpl
, ElementAnnotationImpl
另外 xxx.library
约等该类型的父级,xxx.source
从这里找到该类型的路径。
小坑
法法路由是支持扫 git
方式引用的 package
, 但通过AnalysisContextCollection
返回的结果,只有简单的 ast
信息,没有注解信息,也没有构造器参数类型信息。没有搜索到相关 issue, 也没有在源码里面看到有相关处理,不知道是不是一个 bug
。
最后经过各种尝试之后,遇到 git package
的时候,先把它对应的文件夹复制到当前项目下面,比如 .dart_tool/ff_route/package_name
, 然后改为对该路径进行解析即可。
法法路由 10.0
本次更新,主要增加了新模式来支持支持超类的初始化构造以及可以自动增加参数的引用了。
模式 | 命令 | 描述 | 优点 | 缺点 |
---|---|---|---|---|
快速模式 | --fast-mode | 对单独文件进行解析 | 快速,稳定 | 只能通过注解 @FFArgumentImport() 和增加 argumentImports 来解决参数导入引用,不支持超类的初始化构造(只支持 super.key ) |
非快速模式 | --no-fast-mode | 对 packages 和 sdk 进行解析 |
自动增加参数导入引用和支持超类的初始化构造,解决相同类型名字在不同文件中的冲突 | 慢,可能有 bug |
使用
激活命令: dart pub global activate ff_annotation_route
可用的命令:
-h, --[no-]help 帮助信息。
-p, --path 执行命令的目录,默认当前目录。
-o, --output route 和 helper 文件的输出目录路径,路径相对于主项目的 lib 文件夹。
-n, --name 路由常量类的名称,默认为 `Routes`。
-g, --git 扫描 git 引用的 package,你需要指定 package 的名字,多个用 `,` 分开
--exclude-packages 排除某些 packages 被扫描
--routes-file-output routes 文件的输出目录路径,路径相对于主项目的lib文件夹
--const-ignore 使用正则表达式忽略一些const(不是全部const都希望生成)
--[no-]route-constants 是否在根项目中的 `xxx_route.dart` 生成全部路由的静态常量
--[no-]package 这个是否是一个 package
--[no-]super-arguments 是否生成路由参数帮助类
-s, --[no-]save 是否保存命令到本地。如果保存了,下一次就只需要执行 `ff_route` 就可以了。
--[no-]null-safety 是否支持空安全,默认 `true`
--[no-]fast-mode 快速模式: 只会对单独一个文件进行解析, 它更快.
非快速模式: 会对 packages 和 sdk 进行解析, 支持构造超级参数解析以及自动根据参数添加引用.
默认是快速模式
更详细的使用方法,请查看 ff_annotation_route | Dart Package (flutter-io.cn)。
结语
为啥开始不选择用 build_runner
来解析和生成路由呢?
- 慢,其实也不能怪它,毕竟官方考虑的更周全,扫了更多的文件。
- 默认只能扫当前的项目,有点尴尬。
-
build
整个过程比较复杂,使用的方式有点死板,把流程把握在自己手里才是王道。
最后大家该怎么选择模式呢?
追求快速的童鞋,快速模式快,稳定,快速模式是秒级别的。只是需要注意,需要注解路由的页面,别使用超类的初始化构造即可。
躺平(摸鱼)的童鞋,可以选非快速模式,你可以不用去关心参数需要引用哪个文件,想用新语法(超类的初始化构)就用,也不用担心参数类型的冲突。执行一下命令,然后喝一口茶即可(相同项目,非快速模式是快速模式时间的
18
倍左右,根据项目复杂度),有时候慢下来也是蛮好的。
时间真的过的好快,ff_annotation_route | Dart Package (flutter-io.cn) 都来到了 10.0
版本,想当年第一版,还是在 3
年前,还是 Dart 2.1
版本,现在版本都来到 Dart 2.17
版本了。
写 bug
的日子过的好快,入坑 Flutter
都 4
年,如果你还是一个人单排,欢迎加入糖果一起开黑。
- FlutterCandies (github.com)
91
个仓库,22
名成员。 -
46
个packages
Packages of publisher fluttercandies.com (flutter-io.cn)
爱 Flutter
,爱糖果
,欢迎加入Flutter Candies,一起生产可爱的Flutter小糖果[[图片上传失败...(image-90907b-1664327919019)]
最最后放上 Flutter Candies 全家桶,真香。
[图片上传失败...(image-38e7f1-1664327919019)]