简介
注解(Annotations)是 Dart 语言中的元数据标记,用于为代码添加额外信息,可以被编译器、开发工具或运行时环境识别和处理。Flutter 框架中广泛使用注解来优化代码、提供编译时检查和增强开发体验。
基本概念
什么是注解
注解是以 @ 符号开头的标记,可以应用于类、方法、变量、参数等代码元素上,为它们提供额外的元数据信息。
@deprecated
void oldMethod() {
// 已废弃的方法
}
@override
void someMethod() {
// 重写父类方法
}
注解的作用
- 提供编译时信息:帮助编译器进行代码检查和优化
- 运行时反射:在运行时获取类型信息
- 代码生成:通过注解处理器生成新代码
- 文档生成:为 API 文档提供额外信息
内置注解
@override
用于标记一个方法覆盖了父类中的方法,如果父类中不存在该方法,编译器会报错。
class Parent {
void doSomething() {}
}
class Child extends Parent {
@override
void doSomething() {
// 重写父类方法
super.doSomething();
print('Child implementation');
}
}
@deprecated
标记 API 为已废弃,建议不再使用。可以提供替代方案的信息。
@deprecated
void oldFunction() {
// 已废弃的函数
}
@Deprecated('Use newFunction() instead')
void anotherOldFunction() {
// 提供替代方案的废弃函数
}
@required 和 @required 的替代
在 Flutter 中,@required 注解用于标记必须提供的参数。在 Dart 2.12 引入空安全后,推荐使用 required 关键字替代。
// 旧方式
Widget build({@required Widget child}) {
return Container(child: child);
}
// 新方式(Dart 2.12+)
Widget build({required Widget child}) {
return Container(child: child);
}
@immutable
标记一个类为不可变类,所有实例变量都应该是 final 的。
@immutable
class ImmutablePoint {
final double x;
final double y;
const ImmutablePoint(this.x, this.y);
}
@visibleForTesting
标记一个本应是私有的成员为可见,以便于测试。
class MyClass {
@visibleForTesting
void internalMethod() {
// 本应是私有的方法,但为了测试而暴露
}
}
自定义注解
创建自定义注解
自定义注解是通过创建类并使用 const 构造函数实现的。
class Todo {
final String message;
final String assignee;
const Todo(this.message, {this.assignee = ''});
}
@Todo('Implement this method', assignee: 'John')
void unimplementedMethod() {
throw UnimplementedError();
}
注解处理器
要处理自定义注解,通常需要使用反射或代码生成工具。在 Flutter 中,常用的代码生成工具包括 build_runner、source_gen 等。
// 定义注解(示例用名,避免与 json_annotation 包的真 JsonSerializable 混淆)
class MySerializable {
const MySerializable();
}
// 使用注解
@MySerializable()
class User {
final String name;
final int age;
User(this.name, this.age);
}
// 生成的代码(通过代码生成器)
User _$UserFromJson(Map<String, dynamic> json) {
return User(
json['name'] as String,
json['age'] as int,
);
}
Map<String, dynamic> _$UserToJson(User instance) => {
'name': instance.name,
'age': instance.age,
};
常用第三方注解库
json_serializable
用于 JSON 序列化和反序列化的注解库。
@JsonSerializable()
class Person {
final String name;
final int age;
Person(this.name, this.age);
factory Person.fromJson(Map<String, dynamic> json) =>
_$PersonFromJson(json);
Map<String, dynamic> toJson() => _$PersonToJson(this);
}
freezed
用于创建不可变数据类的注解库。
@freezed
class User with _$User {
factory User({
required String name,
required int age,
String? email,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
injectable
用于依赖注入的注解库。
@injectable
class ApiService {
final HttpClient client;
ApiService(this.client);
Future<Response> getData() {
return client.get('/data');
}
}
@injectable
class UserRepository {
final ApiService apiService;
UserRepository(this.apiService);
}
完整示例
import 'package:json_annotation/json_annotation.dart';
import 'package:meta/meta.dart';
part 'user_model.g.dart';
@JsonSerializable()
@immutable
class User {
final int id;
final String name;
final String email;
@JsonKey(name: 'phone_number')
final String phoneNumber;
@JsonKey(includeIfNull: false)
final String? address;
@JsonKey(ignore: true)
final String? temporaryToken;
const User({
required this.id,
required this.name,
required this.email,
required this.phoneNumber,
this.address,
this.temporaryToken,
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
User copyWith({
int? id,
String? name,
String? email,
String? phoneNumber,
String? address,
}) {
return User(
id: id ?? this.id,
name: name ?? this.name,
email: email ?? this.email,
phoneNumber: phoneNumber ?? this.phoneNumber,
address: address ?? this.address,
temporaryToken: temporaryToken,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is User &&
other.id == id &&
other.name == name &&
other.email == email &&
other.phoneNumber == phoneNumber &&
other.address == address;
}
@override
int get hashCode {
return id.hashCode ^
name.hashCode ^
email.hashCode ^
phoneNumber.hashCode ^
address.hashCode;
}
}
// 使用示例
void main() {
final user = User(
id: 1,
name: 'John Doe',
email: '[email protected]',
phoneNumber: '+1234567890',
address: '123 Main St',
);
final json = user.toJson();
print(json);
final userFromJson = User.fromJson(json);
print(userFromJson);
}
最佳实践
- 先分清注解的消费者:
@override、@immutable是给分析器/lint 看的,零运行时成本,随便用;@JsonSerializable()是给代码生成器看的,需要build_runner步骤;靠dart:mirrors在运行时读注解的——Flutter 直接禁用(下文注意事项)。三类混为一谈是大多数注解坑的根源。 - 代码生成类注解,版本先和 Dart SDK 对齐:
json_serializable、freezed这类包对 Dart SDK 版本敏感,升级 SDK 前先查生成器支不支持,否则build_runner跑出一堆看不懂的报错。 .g.dart文件的归属定好:提交进仓库还是 gitignore,二选一写进 README。最糟的是团队里一半人提交、一半人忽略,CI 永远在”生成代码过期”的报错里打转。- 注解是元数据,不是注释:写之前先问”谁来消费这个注解”,答不上来就别写——没人消费的注解,信息量和注释一样,还多了维护成本(见实战案例)。
注意事项
- Flutter 不支持
dart:mirrors:任何”运行时扫描注解做序列化/路由”的方案,在 Flutter 里编译就挂。Java 系转过来的最容易踩这个坑——Flutter 的注解只走编译期(生成代码)或分析器。 - 生成代码也要有测试:
fromJson手写测试覆盖几个典型 JSON,别以为”生成的代码不会错”。字段改名后@JsonKey(name:)没同步,生成器可不会提醒你。 build_runner的构建时间要正视:小项目几秒,大项目几十秒。CI 上给构建缓存留好位置;本地开发watch模式常驻,别每次手动跑全量。
实战案例
注解(annotation)在我的项目里出场率极低——ygking_json_to_dart_model 就是个反例:143 行的 JsonConverter,零注解、零 build_runner,纯字符串模板生成 Dart 类。为什么不用 @JsonSerializable()?因为场景是”调试时随手转一段 JSON”,引入代码生成框架是杀鸡用牛刀。但我也用过注解:在 GetX 项目里见过 @override 满天飞(不算),真正”吃”注解的是 json_serializable 这类。我的体会:注解的本质是”给代码生成器看的元数据”,如果你的项目没有代码生成步骤,注解就是注释。先问自己”谁来消费这个注解”,答不上来就别写。
总结
注解是 Flutter 和 Dart 开发中的强大工具,可以简化代码、提高可维护性并启用各种高级功能。通过合理使用内置注解和第三方注解库,可以显著提高开发效率和代码质量。
