Flutter开发:在 Dart 3.5 中创建自己的宏,而不是代码生成

发布于 1周前 作者 yibo5220 来自 Flutter

Flutter 开发:在 Dart 3.5 中创建自己的宏,而不是代码生成

问题

Flutter开发:在 Dart 3.5 中创建自己的宏,而不是代码生成。

答案

Dart 3.5 引入了一个重大新特性:宏。宏可以看作是在编译时完全在内存中发生的代码生成,不需要临时文件。这一特性目前还处于测试阶段,因此不建议在生产环境中使用。不过,我们可以尝试创建和使用宏来熟悉这个特性。

以下是如何设置实验环境、编写并运行自己的宏的详细步骤。

设置实验环境 Dart 3.5
  1. 下载 Dart 3.5 测试版: 按照官方指示切换到 Dart 3.5 测试版:Dart 3.5 宏设置指南

  2. 安装 Dart 插件: VSCode 需要安装最新的 Dart 插件来查看宏生成的代码。

创建 pubspec.yaml

要使用示例宏,必须至少使用 Dart 3.5.0-154。创建如下的 pubspec.yaml 文件:

name: macro_example
version: 1.0.0
dependencies:
  # 可以在这里添加其他依赖
dev_dependencies:
  build_runner: ^2.0.0 # 确保使用支持宏的版本

environment:
  sdk: ">=3.5.0-154 <4.0.0"
创建 analysis_options.yaml

由于你正在编写代码时使用的是实验特性,分析器会给出警告,因此需要告诉它你正在实验这个特性。创建以下的 analysis_options.yaml 文件:

analyzer:
  enable-experiment:
    - macros
编写代码

使用 Dart 团队提供的示例代码。

main.dart

@JsonCodable
class User {
  String name;
  int age;
}

void main() {
  var user = User.fromJson({'name': 'John', 'age': 30});
  print(user.toJson());
}

注意@JsonCodable 是一个示例宏,它将在未来稳定。

运行代码

通过终端运行代码,启用实验标志:

dart --enable-experiment=macros run main.dart

或者配置 VSCode 来进行实验。打开 settings.json 并进行如下修改:

{
  "dart.enableExperiments": ["macros"]
}

现在它就能正常工作并打印以下内容:

{"name":"John","age":30}

注意,类 User 只有 6 行代码,而使用 json_serializable 的等效代码需要 16 行。

查看生成的代码

在 VSCode 中,你可以点击 “Go to Augmentation” 来查看生成的代码。与旧的代码生成方式不同,生成的代码并不是保存在真实的文件中,而是在内存中。你不能直接编辑这些代码,但是当你更改 main.dart 中的内容时,生成的代码会自动更新,所以你不需要单独运行生成器。

宏的工作原理:增强

增强是通过添加成员或替换类体外部的代码来修改类或函数。宏所做的实际操作是生成一个包含增强的文件。这个文件现在是内存中的,而不是一个 .g.dart 的物理文件。

创建自己的 hello-world 宏

创建 hello.dart 文件,写入以下代码来实现宏:

import 'dart:_internal' as internal;
import 'package:meta/meta.dart';

@macro
class HelloMacro implements ClassDeclarationsMacro {
  const HelloMacro();

  void buildDeclarationsForClass(
      ClassBuilder builder,
      ClassDeclaration classDecl
  ) {
    var className = classDecl.name;
    var fields = classDecl.fields;

    var helloMethodCode = '''
      void hello() {
        print('Hello, I am $className!');
        ${fields.map((field) => "print('Field: ${field.name}');").join('\n')}
      }
    ''';

    var printIdentifier = builder.resolveIdentifier('print');
    var helloMethod = builder.declareInType(
        classDecl.type,
        internal.MethodDeclaration(
            name: builder.name('hello'),
            returnType: builder.coreType('void'),
            isStatic: false,
            isAbstract: false,
            body: internal.FunctionBody.block(
                [
                    internal.Statement.expression(
                        internal.Expression.invocation(
                            receiver: printIdentifier,
                            arguments: [
                                internal.StringLiteral(
                                    "'Hello, I am $className!'"
                                )
                            ]
                        )
                    ),
                    ...fields.map((field) => internal.Statement.expression(
                        internal.Expression.invocation(
                            receiver: printIdentifier,
                            arguments: [
                                internal.StringLiteral(
                                    "'Field: ${field.name}'"
                                )
                            ]
                        )
                    )).toList()
                ]
            )
        )
    );

    builder.addMember(helloMethod);
  }
}

使用这个新宏:

main.dart

import 'hello.dart';

@HelloMacro
class MyClass {
  String name;
  int age;
}

void main() {
  var myClass = MyClass();
  myClass.hello();
}

点击 “Go to Augmentation” 查看生成的代码。注意,print 函数前面加上了 prefix0,这是引入 dart:core 时的标识符前缀。

运行代码:

dart --enable-experiment=macros run main.dart

你将看到它输出:

Hello, I am MyClass!
Field: name
Field: age
真实有用的宏

以下是两个可以深入学习的实际宏:

  1. JsonCodable: 这是 Dart 团队发布的第一个宏,帮助我们了解这一特性。

  2. Args: 如果你开发的是命令行应用,可以创建 Args 宏来从数据类生成解析器,并在编译时提供类型安全。

总结

虽然实现宏相对较为复杂,但它在编译器中的实现要难得多。增强(augmentation)是宏的一个关键特性,它允许我们在编译时修改类和函数,从而简化代码并提高安全性。


更多关于Flutter开发:在 Dart 3.5 中创建自己的宏,而不是代码生成的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter开发:在 Dart 3.5 中创建自己的宏,而不是代码生成的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在 Dart 3.5 中,虽然宏(Macros)的概念尚未正式引入 Dart 语言,但你可以通过一些现有的特性来模拟类似宏的行为,尤其是通过代码生成工具来实现。虽然这不是真正的宏系统,但可以在编译时或构建时动态生成代码,从而达到类似的效果。

在 Flutter 开发中,你可以使用 build_runnerbuild 包来创建自定义的构建步骤,这些步骤可以在构建过程中生成 Dart 代码。下面是一个简要的示例,展示如何使用这些工具来生成代码。

1. 添加依赖

首先,在你的 pubspec.yaml 文件中添加必要的依赖:

dependencies:
  flutter:
    sdk: flutter

dev_dependencies:
  build_runner: ^2.1.7
  build: ^2.1.1
  build_config: ^1.0.0

2. 创建构建配置

在项目的根目录下创建一个 build.yaml 文件,用于配置构建步骤:

targets:
  $default:
    builders:
      generate_code:
        enabled: true

builders:
  generate_code:
    import: "package:your_package_name/builder.dart"
    builder_factories: ["generateCode"]
    build_extensions: { ".dart": [".g.dart"] }
    auto_apply: root_package
    build_to: source

注意:将 your_package_name 替换为你的实际包名。

3. 编写代码生成器

lib 目录下创建一个 builder.dart 文件,并编写代码生成逻辑:

import 'dart:async';
import 'package:build/build.dart';

Builder generateCode(BuilderOptions options) {
  return new AsyncMultiBuilder([
    new PartBuilder(
      [new _CodeGenerator()],
      '.g.dart'
    ),
  ]);
}

class _CodeGenerator implements Generator {
  @override
  FutureOr<String> generate(AssetId id, Context context) async {
    if (id.extension.endsWith('.dart')) {
      var content = await context.readAsString(id);
      // 这里添加你的代码生成逻辑
      var generatedContent = """
      // This is generated code
      part of '${id.pathSegments.lastWithoutExtension}';

      void generatedFunction() {
        print('Hello from generated code!');
      }
      """;
      var outputId = id.changeExtension('.g.dart');
      await context.writeAsString(outputId, generatedContent);
    }
    return null;
  }
}

4. 使用生成的代码

在你的 Dart 文件中,你可以这样使用生成的代码:

// example.dart
part 'example.g.dart';

void main() {
  generatedFunction(); // 调用生成的函数
}

5. 运行构建

最后,在项目根目录下运行以下命令来生成代码:

flutter pub run build_runner build

这将生成 .g.dart 文件,并在其中包含你定义的生成代码。

总结

虽然 Dart 3.5 尚未正式引入宏功能,但通过使用 build_runnerbuild 包,你可以在构建过程中动态生成代码,从而模拟类似宏的行为。这种方法虽然不如真正的宏系统灵活,但在许多情况下已经足够使用。

回到顶部