说起宏编程可能大家并不陌生,但是这对于 Flutter 和 Dart 开发者来说它一直是一个「遗憾」,这个「遗憾」体现在编辑过程的代码修改支持上,其中最典型的莫过于 Dart 的 JSON 序列化。
举个例子,目前 Dart 语言的 JSON 序列化高度依赖 build_runner 去生成 Dart 代码,例如在实际使用中我们需要:
- 依赖
json_serializable,通过注解声明一个Event对象 - 运行
flutter packages pub run build_runner build生成文件 - 得到
Event.g.dart文件,在项目中使用它去实现 JSON 的序列化和反序列化
这里最大的问题在于,我们需要通过命令行去生成一个项目文件,并且这个文件我们还可以随意手动修改,从开发角度来说,这并不优雅也不方便。
而宏声明是用户定义的 Dart 类,它可以实现一个或多个新的内置宏接口,Dart 中的宏是用正常的命令式 Dart 代码来开发,不存在单独的“宏语言” 。
大多数宏并不是简单地从头开始生成新代码,而是根据程序的现有属性去添加代码,例如向 Class 添加 JSON 序列化的宏,可能会查看 Class 声明的字段,并从中合成一个
toJson(),将这些字段序列化为 JSON 对象。
我们首先看一段官方的 Demo , 如下代码所示,可以看到 :
MyState添加了一个自定义的@AutoDispose()注解,这是一个开发者自己实现的宏声明,并且继承了State对象,带有dispose方法。- 在
MyState里有多个a、a2、b和c三个对象,其中a、a2、b都实现了Disposable接口,都有dispose方法 - 虽然
a、a2、b和MyState的dispose();方法来自不同基类实现,但是基于@AutoDispose()的实现,在代码调用state.dispose();时,a、a2、b变量的dispose方法也会被同步调用
1 | dart复制代码import 'package:macro_proposal/auto_dispose.dart'; |
如下图所示,可以看到,尽管 MyState 没用主动调用 a、a2 、b 变量的 dispose 方法,并且它们和 MyState 的 dispose 也来自不同基类,但是最终执行所有 dispose 方法都被成功调用,这就是@AutoDispose() 的宏声明实现在编译时对代码进行了调整。
如下图所示是 @AutoDispose() 的宏编程实现,其中 macro 就是一个标志性的宏关键字,剩下的代码可以看到基本就是 dart 脚本的实现, macro 里主要是实现 ClassDeclarationsMacro 和buildDeclarationsForClass方法,如下代码可以很直观看到关于 super.dispose(); 和 disposeCalls 的相关实现。
1 | typescript复制代码import 'package:_fe_analyzer_shared/src/macros/api.dart'; |
到这里大家应该可以直观感受到宏编程的魅力,上述 Demo 来自 dart-language 的 macros/example/auto_dispose_main ,其中 bin/ 目录下的代码是运行的脚本示例,lib/ 目录下的代码是宏编程实现的示例:
当然,因为现在是实验性阶段,API 和稳定性还有待商榷,所以想运行这些 Demo 还需要一些额外的处理,比如版本强关联,例如上述的 auto_dispose_main 例子:
- 需要 dart sdk 3.4.0-97.0.dev ,目前你可以通过 master 分支下载这个 dark-sdk storage.googleapis.com/dart-archiv…
- 将 sdk 配置到环境变量,或者进入到 dart sdk 的 bin 目录执行 ./dart –version 检查版本
- 进入上诉的 example 下执行 dart pub get,过程可能会有点长
- 最后,执行
dart --enable-experiment=macros bin/auto_dispose_main.dart,记得这个 dart 是你指定版本的 dart 。
另外,还有一个第三方例子是来自 millsteed 的 macros ,这是一个简单的 JSON 序列化实现 Demo ,并且可以直接不用额外下载 dark-sdk,通过某个 flutter 内置 dart-sdk 版本就可以满足条件:3.19.0-12.0.pre :
在本地 Flutter 目录下,切换到
git checkout 3.19.0-12.0.pre,然后执行 flutter doctor 初始化 dark sdk 即可。
代码的实现很简单,首先看 bin 下的示例,通过 @Model() 将 GetUsersResponse 和 User 声明为 JSON 对象,然后在运行时,宏编程会自动添加 fromJson 和 toJson 方式。
1 | dart复制代码import 'dart:convert'; |
而 Model 的宏实现就相对复杂一些,但是实际上就是将类似 freezed/ json_serializable 是实现调整到宏实现了,而最终效果就是,开发者使用起来更加优雅了。
1 | dart复制代码// ignore_for_file: depend_on_referenced_packages, implementation_imports |
目前宏还处于试验性质的阶段,所以 API 还在调整,这也是为什么上面的例子需要指定 dart 版本的原因,另外宏目前规划里还有一些要求,例如
- 所有宏构造函数都必须标记为
const - 所有宏必须至少实现其中一个
Macro接口 - 宏不能是抽象对象
- 宏 class 不能由其他宏生成
- 宏 class 不能包含泛型类型参数
- 每个宏接口都需要声明宏类必须实现的方法,例如,在声明阶段应用的
ClassDeclarationsMacro及其buildDeclarationsForClass方法。
未来规划里,宏 API 可能会作为 Pub 包提供,通过库 dart:_macros来提供支持 ,具体还要等正式发布时 dart 团队的决策。
总的来说,这对于 dart 和 flutter 是一个重大的厉害消息,虽然宏编程并不是什么新鲜概念,该是 dart 终于可以优雅地实现 JSON 序列化,并且还是用 dart 来实现,这对于 flutter 开发者来说,无疑是最好的新年礼物。
所以,新年快乐~我们节后再见~
本文转载自: 掘金