网站首页flash制作,淮南网站seo,梵克雅宝官网旗舰店,品牌网站建设权威说起宏编程可能大家并不陌生#xff0c;但是这对于 Flutter 和 Dart 开发者来说它一直是一个「遗憾」#xff0c;这个「遗憾」体现在编辑过程的代码修改支持上#xff0c;其中最典型的莫过于 Dart 的 JSON 序列化。
举个例子#xff0c;目前 Dart 语言的 JSON 序列化高度依…说起宏编程可能大家并不陌生但是这对于 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 方法也会被同步调用
import package:macro_proposal/auto_dispose.dart;void main() {var state MyState(a: ADisposable(), b: BDisposable(), c: hello world);state.dispose();
}AutoDispose()
class MyState extends State {final ADisposable a;final ADisposable? a2;final BDisposable b;final String c;MyState({required this.a, this.a2, required this.b, required this.c});overrideString toString() MyState!;
}class State {void dispose() {print(disposing of $this);}
}class ADisposable implements Disposable {void dispose() {print(disposing of ADisposable);}
}class BDisposable implements Disposable {void dispose() {print(disposing of BDisposable);}
}
如下图所示可以看到尽管 MyState 没用主动调用 a 、a2 、b 变量的 dispose 方法并且它们和 MyState 的 dispose 也来自不同基类但是最终执行所有 dispose 方法都被成功调用这就是AutoDispose() 的宏声明实现在编译时对代码进行了调整。 如下图所示是 AutoDispose() 的宏编程实现其中 macro 就是一个标志性的宏关键字剩下的代码可以看到基本就是 dart 脚本的实现 macro 里主要是实现 ClassDeclarationsMacro 和buildDeclarationsForClass方法如下代码可以很直观看到关于 super.dispose(); 和 disposeCalls 的相关实现。
import package:_fe_analyzer_shared/src/macros/api.dart;// Interface for disposable things.
abstract class Disposable {void dispose();
}macro class AutoDispose implements ClassDeclarationsMacro, ClassDefinitionMacro {const AutoDispose();overridevoid buildDeclarationsForClass(ClassDeclaration clazz, MemberDeclarationBuilder builder) async {var methods await builder.methodsOf(clazz);if (methods.any((d) d.identifier.name dispose)) {// Dont need to add the dispose method, it already exists.return;}builder.declareInType(DeclarationCode.fromParts([// TODO: Remove external once the CFE supports it.external void dispose();,]));}overrideFuturevoid buildDefinitionForClass(ClassDeclaration clazz, TypeDefinitionBuilder builder) async {var disposableIdentifier // ignore: deprecated_member_useawait builder.resolveIdentifier(Uri.parse(package:macro_proposal/auto_dispose.dart),Disposable);var disposableType await builder.resolve(NamedTypeAnnotationCode(name: disposableIdentifier));var disposeCalls Code[];var fields await builder.fieldsOf(clazz);for (var field in fields) {var type await builder.resolve(field.type.code);if (!await type.isSubtypeOf(disposableType)) continue;disposeCalls.add(RawCode.fromParts([\n,field.identifier,if (field.type.isNullable) ?,.dispose();,]));}// Augment the dispose method by injecting all the new dispose calls after// either a call to augmented() or super.dispose(), depending on if// there already is an existing body to call.//// If there was an existing body, it is responsible for calling// super.dispose().var disposeMethod (await builder.methodsOf(clazz)).firstWhere((method) method.identifier.name dispose);var disposeBuilder await builder.buildMethod(disposeMethod.identifier);disposeBuilder.augment(FunctionBodyCode.fromParts([{\n,if (disposeMethod.hasExternal || !disposeMethod.hasBody)super.dispose();elseaugmented();,...disposeCalls,},]));}
}
到这里大家应该可以直观感受到宏编程的魅力上述 Demo 来自 dart-language 的 macros/example/auto_dispose_main 其中 bin/ 目录下的代码是运行的脚本示例lib/ 目录下的代码是宏编程实现的示例 https://github.com/dart-lang/language/tree/main/working/macros/example 当然因为现在是实验性阶段API 和稳定性还有待商榷所以想运行这些 Demo 还需要一些额外的处理比如版本强关联例如上述的 auto_dispose_main 例子 需要 dart sdk 3.4.0-97.0.dev 目前你可以通过 master 分支下载这个 dark-sdk https://storage.googleapis.com/dart-archive/channels/main/raw/latest/sdk/dartsdk-macos-arm64-release.zip 将 sdk 配置到环境变量或者进入到 dart sdk 的 bin 目录执行 ./dart --version 检查版本 进入上诉的 example 下执行 dart pub get过程可能会有点长 最后执行 dart --enable-experimentmacros 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 方式。
import dart:convert;import package:macros/model.dart;Model()
class User {User({required this.username,required this.password,});final String username;final String password;
}Model()
class GetUsersResponse {GetUsersResponse({required this.users,required this.pageNumber,required this.pageSize,});final ListUser users;final int pageNumber;final int pageSize;
}void main() {const body {users: [{username: ramon,password: 12345678}],pageNumber: 1,pageSize: 30};final json jsonDecode(body) as MapString, dynamic;final response GetUsersResponse.fromJson(json);final ramon response.users.first;final millsteed ramon.copyWith(username: millsteed, password: 87654321);final newResponse response.copyWith(users: [...response.users, millsteed]);print(const JsonEncoder.withIndent( ).convert(newResponse));
}而 Model 的宏实现就相对复杂一些但是实际上就是将类似 freezed/ json_serializable 是实现调整到宏实现了而最终效果就是开发者使用起来更加优雅了。
// ignore_for_file: depend_on_referenced_packages, implementation_importsimport dart:async;import package:_fe_analyzer_shared/src/macros/api.dart;macro class Model implements ClassDeclarationsMacro {const Model();static const _baseTypes [bool, double, int, num, String];static const _collectionTypes [List];overrideFuturevoid buildDeclarationsForClass(ClassDeclaration classDeclaration,MemberDeclarationBuilder builder,) async {final className classDeclaration.identifier.name;final fields await builder.fieldsOf(classDeclaration);final fieldNames String[];final fieldTypes String, String{};final fieldGenerics String, ListString{};for (final field in fields) {final fieldName field.identifier.name;fieldNames.add(fieldName);final fieldType (field.type.code as NamedTypeAnnotationCode).name.name;fieldTypes[fieldName] fieldType;if (_collectionTypes.contains(fieldType)) {final generics (field.type.code as NamedTypeAnnotationCode).typeArguments.map((e) (e as NamedTypeAnnotationCode).name.name).toList();fieldGenerics[fieldName] generics;}}final fieldTypesWithGenerics fieldTypes.map((name, type) {final generics fieldGenerics[name];return MapEntry(name,generics null ? type : $type${generics.join(, )},);},);_buildFromJson(builder, className, fieldNames, fieldTypes, fieldGenerics);_buildToJson(builder, fieldNames, fieldTypes);_buildCopyWith(builder, className, fieldNames, fieldTypesWithGenerics);_buildToString(builder, className, fieldNames);_buildEquals(builder, className, fieldNames);_buildHashCode(builder, fieldNames);}void _buildFromJson(MemberDeclarationBuilder builder,String className,ListString fieldNames,MapString, String fieldTypes,MapString, ListString fieldGenerics,) {final code [factory $className.fromJson(MapString, dynamic json) {.indent(2),return $className(.indent(4),for (final fieldName in fieldNames) ...[if (_baseTypes.contains(fieldTypes[fieldName])) ...[$fieldName: json[$fieldName] as ${fieldTypes[fieldName]},.indent(6),] else if (_collectionTypes.contains(fieldTypes[fieldName])) ...[$fieldName: (json[$fieldName] as Listdynamic).indent(6),.whereTypeMapString, dynamic().indent(10),.map(${fieldGenerics[fieldName]?.first}.fromJson).indent(10),.toList(),.indent(10),] else ...[$fieldName: ${fieldTypes[fieldName]}.fromJson(json[$fieldName] as MapString, dynamic),.indent(6),],],);.indent(4),}.indent(2),].join(\n);builder.declareInType(DeclarationCode.fromString(code));}void _buildToJson(MemberDeclarationBuilder builder,ListString fieldNames,MapString, String fieldTypes,) {final code [MapString, dynamic toJson() {.indent(2),return {.indent(4),for (final fieldName in fieldNames) ...[if (_baseTypes.contains(fieldTypes[fieldName])) ...[$fieldName: $fieldName,.indent(6),] else if (_collectionTypes.contains(fieldTypes[fieldName])) ...[$fieldName: $fieldName.map((e) e.toJson()).toList(),.indent(6),] else ...[$fieldName: $fieldName.toJson(),.indent(6),],],};.indent(4),}.indent(2),].join(\n);builder.declareInType(DeclarationCode.fromString(code));}void _buildCopyWith(MemberDeclarationBuilder builder,String className,ListString fieldNames,MapString, String fieldTypes,) {final code [$className copyWith({.indent(2),for (final fieldName in fieldNames) ...[${fieldTypes[fieldName]}? $fieldName,.indent(4),],}) {.indent(2),return $className(.indent(4),for (final fieldName in fieldNames) ...[$fieldName: $fieldName ?? this.$fieldName,.indent(6),],);.indent(4),}.indent(2),].join(\n);builder.declareInType(DeclarationCode.fromString(code));}void _buildToString(MemberDeclarationBuilder builder,String className,ListString fieldNames,) {final code [override.indent(2),String toString() {.indent(2),return $className(.indent(4),for (final fieldName in fieldNames) ...[if (fieldName ! fieldNames.last) ...[$fieldName: \$$fieldName, .indent(8),] else ...[$fieldName: \$$fieldName.indent(8),],],);.indent(8),}.indent(2),].join(\n);builder.declareInType(DeclarationCode.fromString(code));}void _buildEquals(MemberDeclarationBuilder builder,String className,ListString fieldNames,) {final code [override.indent(2),bool operator (Object other) {.indent(2),return other is $className .indent(4),runtimeType other.runtimeType .indent(8),for (final fieldName in fieldNames) ...[if (fieldName ! fieldNames.last) ...[$fieldName other.$fieldName .indent(8),] else ...[$fieldName other.$fieldName;.indent(8),],],}.indent(2),].join(\n);builder.declareInType(DeclarationCode.fromString(code));}void _buildHashCode(MemberDeclarationBuilder builder,ListString fieldNames,) {final code [override.indent(2),int get hashCode {.indent(2),return Object.hash(.indent(4),runtimeType,.indent(6),for (final fieldName in fieldNames) ...[$fieldName,.indent(6),],);.indent(4),}.indent(2),].join(\n);builder.declareInType(DeclarationCode.fromString(code));}
}extension on String {String indent(int length) {final space StringBuffer();for (var i 0; i length; i) {space.write( );}return $space$this;}
}目前宏还处于试验性质的阶段所以 API 还在调整这也是为什么上面的例子需要指定 dart 版本的原因另外宏目前规划里还有一些要求例如
所有宏构造函数都必须标记为 const所有宏必须至少实现其中一个 Macro 接口宏不能是抽象对象宏 class 不能由其他宏生成宏 class 不能包含泛型类型参数每个宏接口都需要声明宏类必须实现的方法例如在声明阶段应用的 ClassDeclarationsMacro 及其buildDeclarationsForClass 方法。
未来规划里宏 API 可能会作为 Pub 包提供通过库 dart:_macros 来提供支持 具体还要等正式发布时 dart 团队的决策。
总的来说这对于 dart 和 flutter 是一个重大的厉害消息虽然宏编程并不是什么新鲜概念该是 dart 终于可以优雅地实现 JSON 序列化并且还是用 dart 来实现这对于 flutter 开发者来说无疑是最好的新年礼物。
所以新年快乐我们节后再见