目录

宏(实验性)

Dart 宏系统是一项正在开发中的主要新语言特性,它为 Dart 语言增加了对静态元编程的支持。

Dart 宏是用户可定义的一段代码,它将其他代码作为参数,并实时对其进行操作,以创建、修改或添加声明。

您可以将宏系统分为两个部分:使用宏和编写宏。本页涵盖了每个部分(高层次的,因为该功能仍在预览中),具体如下

  • JsonCodable:一个现成的宏,您今天可以试用(在实验性标志之后),它为 Dart 中繁琐的 JSON 序列化和反序列化这一常见问题提供了一个无缝的解决方案。

  • 通用的宏特性:为什么我们要将宏添加到 Dart,激励性的用例,优于现有代码生成解决方案的优势,以及对未来该特性完成后的宏编写方式的粗略概述。

JsonCodable

#

JsonCodable 宏将用户定义的 Dart 类编码和解码为类型为 Map<String, Object?> 的 JSON 映射。它生成两个成员,一个 toJson 序列化方法和一个 fromJson 反序列化构造函数。

设置实验

#
  1. 切换到 Dart 开发通道Flutter master 通道

  2. 运行 dart --version 并确保您的 Dart 版本为 3.5.0-152 或更高版本。

  3. 编辑您的 pubspec 中的 SDK 约束,以要求 Dart 版本:sdk: ^3.5.0-152

  4. 将包 json 添加到 dependenciesdart pub add json

  5. 在您包的 analysis_options.yaml 文件中启用实验。项目根目录下的文件

    yaml
    analyzer:
     enable-experiment:
       - macros
  6. 在您计划使用它的文件中导入包

    dart
    import 'package:json/json.dart';
  7. 使用实验性标志运行您的项目

    dart run --enable-experiment=macros bin/my_app.dart

使用宏

#

要使用 JsonCodable 宏,请将注释附加到要序列化的类

dart
import 'package:json/json.dart';

@JsonCodable() // Macro annotation.
class User {
 final int? age;
 final String name;
 final String username;
}

该宏会内省 User 类,并使用 User 类的字段派生出 fromJsontoJson 的实现。

因此,无需自己定义它们,现在可以在带注释的类的对象上使用 toJsonfromJson

dart
void main() {
 // Given some arbitrary JSON:
 var userJson = {
   'age': 5,
   'name': 'Roger',
   'username': 'roger1337'
 };

 // Use the generated members:
 var user = User.fromJson(userJson);
 print(user);
 print(user.toJson());
}

查看生成的代码

#

有时,查看生成的代码可以更好地了解宏的工作原理,或者检查它提供的详细信息。

单击 IDE(在 VSCode 中受支持)中注释下方出现的“转到增强”链接,查看宏如何生成 toJsonfromJson

如果您在带注释的类中更改任何内容,您可以在应用程序代码旁边实时观看生成的增强调整

A side-by-side gif of the generated augmentation updating as the code it's augmenting is updated

触发自定义诊断

#

JsonCodable 宏具有内置的诊断功能,这些诊断功能的发出方式与语言本身的诊断功能相同。例如,如果您尝试在应用宏的地方手动声明一个 toJson 方法,分析器将发出错误

dart
@JsonCodable()
class HasToJson {
 void toJson() {}
 // Error: Cannot generate a toJson method due to this existing one.
}

您可以在 JsonCodable 的定义中搜索 "DiagnosticMessage",以查找宏将抛出的其他错误。例如,扩展一个也不可序列化的类,或者字段名称与给定 JSON 中的键名称不完全匹配。

宏语言特性

#

Dart 宏是一种静态元编程或代码生成解决方案。与运行时代码生成解决方案(如 build_runner)不同,宏完全集成到 Dart 语言中,并由 Dart 工具在后台自动执行。这使得宏比依赖辅助工具更有效率

  • 无需额外运行;宏会在您编写代码时实时构建。
  • 没有重复的工作或不断地重新编译损害性能;所有构建和代码生成都直接在编译器中自动发生。
  • 不写入磁盘,因此没有部分文件或指向生成引用的指针;宏直接增强现有类。
  • 没有混淆/难以理解的测试;自定义诊断的发出方式与分析器中的任何其他消息相同,直接在 IDE 中。

而且比自己手动编写这些类型问题的解决方案效率更高,而且更不容易出错。

用例

#

宏提供了可重用的机制来解决以繁琐的样板代码为特征的模式,并且通常需要迭代类的字段。我们希望将来使用宏解决的一些常见示例是

  • JSON 序列化。序列化 JSON 所需的额外工具,例如 json_serializable 包,效率不如应该的那么高。JsonCodable 宏提供了一种更简洁的方法来生成序列化代码;今天就试用一下

  • 数据类。 Dart 的 最受欢迎的功能是数据类,这些数据类会自动为每个字段提供构造函数以及 ==hashCodecopyWith() 方法的实现。使用宏实现该解决方案意味着用户可以根据自己的需要自定义其数据类。

  • 冗长的 Flutter 模式。一个例子是将复杂的 build 方法分解为较小的 widget 类聚合。它对于性能更好,并使代码更易于维护。不幸的是,编写所有这些较小的类需要大量的样板代码,这让用户望而却步。宏可能会提供一个解决方案,该解决方案会迭代复杂的 build 方法以生成较小的 widget 类,从而大大提高 Flutter 代码的生产力和质量。您可以在 Flutter 团队的提案中查看对该主题的探索。

宏的工作原理

#

要创建宏,您可以使用 macro 关键字编写类似于类的宏声明。宏声明还必须包含一个 implements 子句,以定义宏可以应用于哪个接口。

例如,一个适用于类并向类添加新声明的宏,将实现 ClassDeclarationsMacro 接口

dart
macro class MyMacro implements ClassDeclarationsMacro {
   const MyMacro();

   // ...
}

虽然该功能仍在开发中,但您可以在源代码中找到完整的宏接口列表。

上面示例中的 MyMacro 构造函数对应于您用来将宏应用于声明的注解。其语法与 Dart 现有的元数据注解语法相同。

dart
@MyMacro()
class A {}

在宏声明的主体中,您可以定义您希望宏生成的代码,以及您希望宏发出的任何诊断信息

从非常高的层面来说,编写宏本质上是通过使用构建器方法将声明的属性与这些属性上的标识符组合在一起。宏通过对程序的深度内省来收集这些信息。

宏仍在开发中,所以目前我们只能深入到这个程度。如果您有兴趣,或者想在实验性标志后自己尝试一下,最好的指导是查看现有宏的实现。

  • 请查看 JsonCodable 宏的定义
  • 或者语言仓库中提供的任何示例

时间线

#

宏的稳定发布日期目前未知。这是由于其实现的复杂性造成的。

宏的工作原理是对应用它们的程序进行深度内省。宏最终可能会遍历程序的远处部分,以收集有关它正在增强的声明的属性和类型注解的必要信息。

考虑到它们在大型代码库中的应用,在这些代码库中,多个宏可以在不同的位置不断地内省和增强基础代码,因此排序执行阶段的设计尤其具有挑战性,需要仔细考虑。

我们正在努力在今年晚些时候 (2024) 推出 JsonCodable 宏的稳定版本,并在明年年初 (2025) 推出完整的语言功能(即编写您自己的宏)的稳定版本。