内容

宏 (实验性)

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

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

你可以将宏系统分成两部分:使用宏和编写宏。本页面涵盖了这两部分(处于较高层级,因为该功能仍然处于预览阶段),以下各节将进行介绍。

  • JsonCodable:一个你可以立即尝试(在实验性标志下)的现成宏,它为 Dart 中常见的繁琐 JSON 序列化和反序列化问题提供了一个无缝解决方案。

  • 宏特性概述:我们为什么要在 Dart 中添加宏,用例的动机,与现有代码生成解决方案相比的优势,以及编写宏在该功能完成后将如何运作的粗略概述。

JsonCodable

#

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

设置实验

#
  1. 切换到 Dart dev 频道Flutter master 频道

  2. 运行 dart --version 并确保你拥有 Dart 版本 3.5.0-152 或更高版本。

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

  4. 添加包 jsondependenciesdart 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 年)发布完整语言特性的稳定版本(即,编写自己的宏)。