跳转到正文
YGJoon 技术博客

Flutter 自定义注解详解

发布于:
·1.0k 字 · 约 3 分钟·作者:
用 const 类自定义注解,配合 build_runner 在编译期生成代码:从注解定义到 source_gen 处理器的完整链路
Flutter 自定义注解详解文章封面
目录

简介

自定义注解是 Flutter 和 Dart 中的一个强大特性,它允许开发者创建自己的元数据标记,用于代码生成、运行时反射等场景。本文将详细介绍如何创建和使用自定义注解。

基本概念

注解的定义

在 Dart 中,注解是一个类,通常使用 const 构造函数。注解可以包含参数,用于传递额外的信息。

class CustomAnnotation {
  final String value;
  final int priority;
  
  const CustomAnnotation(this.value, {this.priority = 0});
}

注解的使用位置

注解可以应用在以下位置:

创建自定义注解

基本注解

// 简单的标记注解
class Todo {
  const Todo();
}

// 带参数的注解
class Route {
  final String path;
  final String name;
  
  const Route(this.path, {this.name = ''});
}

复杂注解

class Entity {
  final String table;
  final List<String> indices;
  final bool autoIncrement;
  
  const Entity({
    required this.table,
    this.indices = const [],
    this.autoIncrement = true,
  });
}

class Column {
  final String name;
  final String type;
  final bool nullable;
  final bool primary;
  
  const Column({
    required this.name,
    required this.type,
    this.nullable = false,
    this.primary = false,
  });
}

注解处理器开发

基本结构

import 'package:source_gen/source_gen.dart';
import 'package:analyzer/dart/element/element.dart';

class MyAnnotationGenerator extends GeneratorForAnnotation<MyAnnotation> {
  @override
  String generateForAnnotatedElement(
    Element element,
    ConstantReader annotation,
    BuildStep buildStep,
  ) {
    // 处理注解
    return ''; // 返回生成的代码
  }
}

完整示例

// 注解定义
class ApiEndpoint {
  final String path;
  final String method;
  final List<String> params;
  
  const ApiEndpoint({
    required this.path,
    this.method = 'GET',
    this.params = const [],
  });
}

// 注解处理器
class ApiEndpointGenerator extends GeneratorForAnnotation<ApiEndpoint> {
  @override
  String generateForAnnotatedElement(
    Element element,
    ConstantReader annotation,
    BuildStep buildStep,
  ) {
    if (element is! ClassElement) {
      throw InvalidGenerationSourceError(
        'ApiEndpoint can only be applied to classes',
        element: element,
      );
    }
    
    final path = annotation.read('path').stringValue;
    final method = annotation.read('method').stringValue;
    final params = annotation.read('params').listValue
        .map((e) => e.toStringValue())
        .whereType<String>()
        .toList();
    
    return '''
    // Generated code for ${element.name}
    class ${element.name}Client {
      final HttpClient client;
      
      ${element.name}Client(this.client);
      
      Future<Response> call(${_generateParams(params)}) async {
        return await client.$method(
          '$path${_generateQueryParams(params)}',
        );
      }
    }
    ''';
  }
  
  String _generateParams(List<String> params) {
    return params.map((p) => 'String $p').join(', ');
  }
  
  String _generateQueryParams(List<String> params) {
    if (params.isEmpty) return '';
    return '?' + params.map((p) => '$p=\$$p').join('&');
  }
}

使用自定义注解

基本用法

@Todo()
void implementThis() {
  throw UnimplementedError();
}

@Route('/users', name: 'users_page')
class UsersPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

数据库实体示例

@Entity(table: 'users')
class User {
  @Column(name: 'id', type: 'INTEGER', primary: true)
  final int id;
  
  @Column(name: 'name', type: 'TEXT')
  final String name;
  
  @Column(name: 'email', type: 'TEXT', nullable: true)
  final String? email;
  
  const User({
    required this.id,
    required this.name,
    this.email,
  });
}

API 客户端示例

@ApiEndpoint(
  path: '/api/users',
  method: 'GET',
  params: ['page', 'limit'],
)
class UsersApi {
  // 生成的代码将创建 UsersApiClient 类
}

// 使用生成的代码
void main() async {
  final client = HttpClient();
  final usersApi = UsersApiClient(client);
  
  // 调用生成的方法
  final response = await usersApi.call(
    page: '1',
    limit: '10',
  );
}

代码生成配置

build.yaml 配置

targets:
  $default:
    builders:
      my_annotations|annotations:
        enabled: true
        generate_for:
          - lib/**/*.dart

builders:
  annotations:
    import: "package:my_annotations/builder.dart"
    builder_factories: ["annotationsBuilder"]
    build_extensions: {".dart": [".g.dart"]}
    auto_apply: dependents
    build_to: source

运行代码生成

## 一次性生成
flutter pub run build_runner build

## 监听文件变化
flutter pub run build_runner watch

实战案例

自定义注解我没写过——原因和上一篇一样:我的项目里没有需要”编译期代码生成”的场景。ygking_json_to_dart_model 的生成器是运行时的字符串拼接,不需要注解;GetX 项目里路由、依赖注入全是运行时的,不需要注解。自定义注解的真正用武之地是框架开发(比如写一个自己的序列化框架、路由生成器),而不是业务开发。我见过有人为了”炫技”在业务项目里自定义注解+source_gen,结果 build_runner 每次改代码都要跑 30 秒,开发体验崩了。业务项目里,YAGNI(你不需要它)原则对注解尤其适用。真要写框架,再去看 source_gen 的文档。

总结

自定义注解是一个强大的工具,可以帮助我们自动化代码生成、提供编译时检查等功能。通过合理设计和使用自定义注解,可以显著提高开发效率和代码质量。在使用时需要注意性能影响和维护成本,确保注解真正为项目带来价值。


分享这篇文章:


相关文章

上一篇
Flutter集成Sentry日志收集
下一篇
Flutter 插件开发高级篇