跳转到正文
YGJoon 技术博客

Flutter 注解详解

发布于:
·1.8k 字 · 约 5 分钟·作者:
@override、@deprecated 到 required 关键字:内置注解逐个讲清用法,再手写一个自定义注解配合代码生成
Flutter 注解详解文章封面
目录

简介

注解(Annotations)是 Dart 语言中的元数据标记,用于为代码添加额外信息,可以被编译器、开发工具或运行时环境识别和处理。Flutter 框架中广泛使用注解来优化代码、提供编译时检查和增强开发体验。

基本概念

什么是注解

注解是以 @ 符号开头的标记,可以应用于类、方法、变量、参数等代码元素上,为它们提供额外的元数据信息。

@deprecated
void oldMethod() {
  // 已废弃的方法
}

@override
void someMethod() {
  // 重写父类方法
}

注解的作用

  1. 提供编译时信息:帮助编译器进行代码检查和优化
  2. 运行时反射:在运行时获取类型信息
  3. 代码生成:通过注解处理器生成新代码
  4. 文档生成:为 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);
}

最佳实践

  1. 先分清注解的消费者:@override、@immutable 是给分析器/lint 看的,零运行时成本,随便用;@JsonSerializable() 是给代码生成器看的,需要 build_runner 步骤;靠 dart:mirrors 在运行时读注解的——Flutter 直接禁用(下文注意事项)。三类混为一谈是大多数注解坑的根源。
  2. 代码生成类注解,版本先和 Dart SDK 对齐:json_serializable、freezed 这类包对 Dart SDK 版本敏感,升级 SDK 前先查生成器支不支持,否则 build_runner 跑出一堆看不懂的报错。
  3. .g.dart 文件的归属定好:提交进仓库还是 gitignore,二选一写进 README。最糟的是团队里一半人提交、一半人忽略,CI 永远在”生成代码过期”的报错里打转。
  4. 注解是元数据,不是注释:写之前先问”谁来消费这个注解”,答不上来就别写——没人消费的注解,信息量和注释一样,还多了维护成本(见实战案例)。

注意事项

  1. Flutter 不支持 dart:mirrors:任何”运行时扫描注解做序列化/路由”的方案,在 Flutter 里编译就挂。Java 系转过来的最容易踩这个坑——Flutter 的注解只走编译期(生成代码)或分析器。
  2. 生成代码也要有测试:fromJson 手写测试覆盖几个典型 JSON,别以为”生成的代码不会错”。字段改名后 @JsonKey(name:) 没同步,生成器可不会提醒你。
  3. build_runner 的构建时间要正视:小项目几秒,大项目几十秒。CI 上给构建缓存留好位置;本地开发 watch 模式常驻,别每次手动跑全量。

实战案例

注解(annotation)在我的项目里出场率极低——ygking_json_to_dart_model 就是个反例:143 行的 JsonConverter,零注解、零 build_runner,纯字符串模板生成 Dart 类。为什么不用 @JsonSerializable()?因为场景是”调试时随手转一段 JSON”,引入代码生成框架是杀鸡用牛刀。但我也用过注解:在 GetX 项目里见过 @override 满天飞(不算),真正”吃”注解的是 json_serializable 这类。我的体会:注解的本质是”给代码生成器看的元数据”,如果你的项目没有代码生成步骤,注解就是注释。先问自己”谁来消费这个注解”,答不上来就别写。

总结

注解是 Flutter 和 Dart 开发中的强大工具,可以简化代码、提高可维护性并启用各种高级功能。通过合理使用内置注解和第三方注解库,可以显著提高开发效率和代码质量。


分享这篇文章:


相关文章

上一篇
Flutter 插件开发高级篇
下一篇
Flutter集成地图服务