美文网首页
法法路由 4.0

法法路由 4.0

作者: 法的空间 | 来源:发表于2020-09-15 18:01 被阅读0次

    前言

    法法路由发布1年多了,总感觉有一些弊端,或者说用起来不舒服的地方。

    • 之前是利用命名参数,通过路由的 arguments (Map<String, dynamic>),利用 key 设置对应的命名参数。需要去注解参数的名字,如下图。
    import 'package:ff_annotation_route/ff_annotation_route.dart';
    
    @FFRoute(
      name: "fluttercandies://picswiper",
      routeName: "PicSwiper",
      argumentNames: ["index", "pics"],
      showStatusBar: false,
      pageRouteType: PageRouteType.transparent,
    )
    class PicSwiper extends StatefulWidget {
      final int index;
      final List<PicSwiperItem> pics;
      PicSwiper({this.index, this.pics});
      // ...
    }
    
    
    • 因为是命名参数,参数也可能是可选非必填。 但是生成的代码中,我不知道你有没有传递这个参数。但是根据注解生成的代码依然会从 arguments 中去试图拿到参数对应的值,这就会造成这个参数被设置成 null。所以在平时使用的时候,就必须在 pushName 的时候将可选参数也一起传递才行。

    • 如果一个页面有多个构造,那怎么办呢?是否可以选择使用哪个构造呢?之前是不支持的。

    • 构造如果是 const 的,之前是没法在生成的代码中也创建 const 的构造。

    4.0

    4.0 版本中,我解决了上面4个问题。

    • 通过对 analyzer, ast 更多的了解,通过 ast 我们可以直接拿到 ConstructorDeclaration ,它是对构造的定义. 代码地址 analyzer/lib/dart/ast/ast.dart , 下面是主要用的一些参数。
    abstract class ConstructorDeclaration implements ClassMember {
    
      /// Return the token for the 'const' keyword, or `null` if the constructor is
      /// not a const constructor.
      /// 构造是否是 const
      Token get constKeyword;
    
      /// Set the token for the 'const' keyword to the given [token].
      set constKeyword(Token token);
    
      /// Return the name of the constructor, or `null` if the constructor being
      /// declared is unnamed.
      /// 构造的名字,默认构造这个值为null
      /// factory TestPageE.deafult(), 这种的话,返回 deafult
      SimpleIdentifier get name;
    
      /// Set the name of the constructor to the given [identifier].
      set name(SimpleIdentifier identifier);
    
      /// Return the parameters associated with the constructor.
      /// 返回参数的集合
      FormalParameterList get parameters;
    
      /// Set the parameters associated with the constructor to the given list of
      /// [parameters].
      set parameters(FormalParameterList parameters);
    }
    
    abstract class FormalParameter implements AstNode {
      /// The 'covariant' keyword, or `null` if the keyword was not used.
      Token get covariantKeyword;
    
      /// Return the element representing this parameter, or `null` if this
      /// parameter has not been resolved.
      ParameterElement get declaredElement;
    
      /// Return the name of the parameter being declared.
      SimpleIdentifier get identifier;
    
      /// Return `true` if this parameter was declared with the 'const' modifier.
      bool get isConst;
    
      /// Return `true` if this parameter was declared with the 'final' modifier.
      ///
      /// Parameters that are declared with the 'const' modifier will return
      /// `false` even though they are implicitly final.
      bool get isFinal;
    
      /// Return `true` if this parameter is a named parameter.
      ///
      /// Named parameters can either be required or optional.
      bool get isNamed;
    
      /// Return `true` if this parameter is an optional parameter.
      ///
      /// Optional parameters can either be positional or named.
      bool get isOptional;
    
      /// Return `true` if this parameter is both an optional and named parameter.
      bool get isOptionalNamed;
    
      /// Return `true` if this parameter is both an optional and positional
      /// parameter.
      bool get isOptionalPositional;
    
      /// Return `true` if this parameter is a positional parameter.
      ///
      /// Positional parameters can either be required or optional.
      bool get isPositional;
    
      /// Return `true` if this parameter is a required parameter.
      ///
      /// Required parameters can either be positional or named.
      ///
      /// Note: this will return `false` for a named parameter that is annotated
      /// with the `@required` annotation.
      bool get isRequired;
    
      /// Return `true` if this parameter is both a required and named parameter.
      ///
      /// Note: this will return `false` for a named parameter that is annotated
      /// with the `@required` annotation.
      bool get isRequiredNamed;
    
      /// Return `true` if this parameter is both a required and positional
      /// parameter.
      bool get isRequiredPositional;
    
      /// Return the kind of this parameter.
      @deprecated
      ParameterKind get kind;
    
      /// Return the annotations associated with this parameter.
      NodeList<Annotation> get metadata;
    
      /// The 'required' keyword, or `null` if the keyword was not used.
      Token get requiredKeyword;
    }
    
    • 上面4个问题白嫖官方,自己再把逻辑搞清楚就行了,ast 很强力。但是也发现一个问题,想判断下这个参数是否是 dart core 库的,因为如果是类或者枚举,我希望能有个提示,让用户设置 import 地址。

    虽然可以扫描全部的 dart 文件,但是考虑到还有三方引用,所以没这样做。找到 DartType 参数,但是这个值一直为 null,最后用 dart:mirrors 反射做了,但是发现最后因为 Flutter 要引入法法路由,会报错,就放弃了。

    话说,法法路由只是 dev_dependencies 中引用,为啥会影响 Flutter 的运行时呢?ff_annotation_route 中暴露了一些类给 Flutter 使用,所以说运行时 ff_annotation_route 也是参加编译了? 有大佬知道吗?

    dev_dependencies:
      ff_annotation_route:
        path: ../
    

    使用

    增加引用

    添加引用到dev_dependencies,及你需要注解的 project/packages 到pubspec.yaml

    dev_dependencies:
      ff_annotation_route: latest-version
    

    执行 flutter packages get 下载

    添加注解

    空构造

    import 'package:ff_annotation_route/ff_annotation_route.dart';
    
    @FFRoute(
      name: "fluttercandies://mainpage",
      routeName: "MainPage",
    )
    class MainPage extends StatelessWidget
    {
      // ...
    }
    
    

    带参数构造

    工具会自动处理带参数的构造,不需要做特殊处理。唯一需要注意的是,你需要使用 argumentImports 为class/enum的参数提供 import 地址。

    import 'package:ff_annotation_route/ff_annotation_route.dart';
    
    @FFRoute(
      name: 'flutterCandies://testPageE',
      routeName: 'testPageE',
      description: 'This is test page E.',
      argumentImports: <String>[
        'import \'package:example/src/model/test_model.dart\';',
        'import \'package:example/src/model/test_model1.dart\';'
      ],
      exts: <String, dynamic>{
        'group': 'Complex',
        'order': 1,
      },
    )
    class TestPageE extends StatelessWidget {
      const TestPageE({
        this.testMode = const TestMode(
          id: 2,
          isTest: false,
        ),
        this.testMode1,
      });
      factory TestPageE.deafult() => TestPageE(
            testMode: TestMode.deafult(),
          );
    
      factory TestPageE.required({@required TestMode testMode}) => TestPageE(
            testMode: testMode,
          );
    
      final TestMode testMode;
      final TestMode1 testMode1;
    }
    

    FFRoute

    Parameter Description Default
    name 路由的名字 (e.g., "/settings") required
    showStatusBar 是否显示状态栏 true
    routeName 用于埋点收集数据的页面名字 ''
    pageRouteType 路由的类型 (material, cupertino, transparent) -
    description 路由的描述 ''
    exts 其他扩展参数. -
    argumentImports 某些参数的导入.有一些参数是类或者枚举,需要指定它们的导入地址 -

    生成文件

    环境

    添加 dart 的 bin 的路径到你的系统 $PATH.

    cache\dart-sdk\bin

    更多信息

    不清楚的可以看掘金

    激活

    pub global activate ff_annotation_route

    执行命令

    到你的项目根目录下面执行.

    ff_route <command> [arguments]

    命令参数

    可用的命令:

    command name description
    -h, --help 打印帮助信息.
    -p, --path [arguments] 执行命令的目录,没有就是当前目录.
    -rc, --route-constants 是否在根项目中的 xxx_route.dart 生成全部路由的静态常量
    -rh, --route-helper 生成 xxx_route_helper.dart 来帮助你处理路由
    -rn, --route-names 是否在根项目中的 xxx_route.dart 生成全部路由的名字
    -s, --save 是否保存命令到本地,如果保存了,下一次就只需要执行ff_route就可以了
    -na, --no-arguments FFRouteSettings 将没有 arguments 这个参数,这个是主要是为了适配 Flutter 低版本
    -g, --git package1,package2 是否扫描 git 引用的 package,你需要指定 package 的名字
    --package 这个是否是一个 package
    --no-is-initial-route FFRouteSettings 将没有 isInitialRoute 这个参数,这个是主要是为了适配 Flutter 高版本
    -o --output route和helper文件的输出目录路径,路径相对于主项目的lib文件夹
    -rfo --routes-file-output routes 文件的输出目录路径,路径相对于主项目的lib文件夹

    Main.dart

    • 如果运行的命令带有参数 --route-helper , FFNavigatorObserver/FFRouteSettings
      将会生成在 xxx_route_helper.dart 中,用于协助追踪页面和设置状态栏。

    • 如果运行的命令带有参数 --route-helperFFTransparentPageRoute 将会生成在
      xxx_route_helper.dart 中,可以使用它来 push 一个透明的 PageRoute

    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'ff_annotation_route demo',
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          initialRoute: Routes.fluttercandiesMainpage,
          onGenerateRoute: (RouteSettings settings) {
            //when refresh web, route will as following
            //   /
            //   /fluttercandies:
            //   /fluttercandies:/
            //   /fluttercandies://mainpage
            if (kIsWeb && settings.name.startsWith('/')) {
              return onGenerateRouteHelper(
                settings.copyWith(name: settings.name.replaceFirst('/', '')),
                notFoundFallback:
                    getRouteResult(name: Routes.fluttercandiesMainpage).widget,
              );
            }
            return onGenerateRouteHelper(settings,
                builder: (Widget child, RouteResult result) {
              if (settings.name == Routes.fluttercandiesMainpage ||
                  settings.name == Routes.fluttercandiesDemogrouppage) {
                return child;
              }
              return CommonWidget(
                child: child,
                result: result,
              );
            });
          },
        );
      }
    }
    

    Push

    Push name

      Navigator.pushNamed(context, Routes.fluttercandiesMainpage /* fluttercandies://mainpage */);
    

    Push name with arguments

    参数必须是一个 Map<String, dynamic>

      Navigator.pushNamed(
        context,
        Routes.flutterCandiesTestPageE,
        arguments: <String, dynamic>{
          constructorName: 'required',
          'testMode': const TestMode(
            id: 100,
            isTest: true,
          ),
        },
      );
    

    Code Hints

    你能这样使用路由 'Routes.flutterCandiesTestPageE', 并且在编辑器中看到代码提示。
    包括页面描述,构造,参数类型,参数名字,参数是否必填。

      /// 'This is test page E.'
      ///
      /// [name] : 'flutterCandies://testPageE'
      ///
      /// [routeName] : 'testPageE'
      ///
      /// [description] : 'This is test page E.'
      ///
      /// [constructors] :
      ///
      /// TestPageE : [TestMode testMode, TestMode1 testMode1]
      ///
      /// TestPageE.deafult : []
      ///
      /// TestPageE.required : [TestMode(required) testMode]
      ///
      /// [exts] : {group: Complex, order: 1}
      static const String flutterCandiesTestPageE = 'flutterCandies://testPageE';
    

    结语

    命名路由的另外一个问题就是参数不是强类型输入,对于使用者可能没有直接 new 构造器来的清晰舒服。但是我还是觉得不要再引入额外的参数类耦合代码,毕竟鱼和熊掌不可兼得。命名路由+注解能够有效的统一处理路由,还可以增加一些额外的辅助功能,还是挺香的。

    欢迎加入Flutter Candies,一起生产可爱的Flutter小糖果( <a target="_blank" href="https://jq.qq.com/?_wv=1027&k=5bcc0gy"><img border="0" src="//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/20fb581e8fe5453892c6e9517c92e5ec~tplv-k3u1fbpfcp-zoom-1.image" alt="flutter-candies" title="flutter-candies"></a>QQ群:181398081)

    最最后放上Flutter Candies全家桶,真香。

    [图片上传失败...(image-76d78f-1600164063164)]

    相关文章

      网友评论

          本文标题:法法路由 4.0

          本文链接:https://www.haomeiwen.com/subject/fdwfyktx.html