Tạo Annotation Nâng Cao với @Target
Trong các bài trước, chúng ta đã biết cách tạo ra một Annotation tuỳ chỉnh bằng cách định nghĩa một Class với const constructor:
class MyAnnotation {
const MyAnnotation();
}Tuy nhiên, với cách khai báo này, lập trình viên có thể đặt @MyAnnotation ở bất kỳ đâu: trên class, method, biến, tham số… điều này có thể dẫn đến việc đặt sai vị trí mà trình phân tích mã (Analyzer) không thể báo lỗi ngay lập tức. Cú pháp lỗi chỉ bị phát hiện muộn khi chạy build_runner.
Để khắc phục, Dart cung cấp một Annotation đặc biệt có sẵn là @Target nằm trong package meta. Nhờ có @Target, bạn có thể quy định rõ Annotation của bạn chỉ được phép đặt ở những loại cấu trúc code nào.
1. Import Package meta
Package meta cung cấp rất nhiều annotation hữu ích cho việc phân tích code (như @required, @protected, @visibleForTesting,…). Trong số đó có @Target.
Thường thì package meta đã có sẵn khi bạn làm việc với Flutter hoặc Dart SDK, bạn chỉ cần import nó vào file định nghĩa Annotation của bạn:
import 'package:meta/meta.dart';2. Các loại TargetKind phổ biến
@Target nhận vào một danh sách (Set) các giá trị Enum thuộc kiểu TargetKind. Dưới đây là các loại TargetKind hay dùng:
TargetKind.classType: Chỉ được đặt trên Class, Mixin hoặc Enum.TargetKind.method: Chỉ được đặt trên phương thức (Method).TargetKind.function: Chỉ được đặt trên hàm (Function tự do).TargetKind.field: Chỉ được đặt trên thuộc tính (Field / Biến instance).TargetKind.parameter: Chỉ được đặt trên tham số của hàm/phương thức.TargetKind.topLevelVariable: Chỉ được đặt trên biến toàn cục ngoài cục.TargetKind.type: Chỉ được đặt trên Type (ví dụ như typedef).
3. Thực hành áp dụng @Target
Giả sử chúng ta đang viết một thư viện định tuyến (Routing). Chúng ta muốn tạo một annotation @ApiRoute(String path) và Annotation này chỉ được phép gán lên các phương thức (method) hoặc hàm (function). Nếu ai gán nó lên Class thì sẽ bị báo lỗi ngay lập tức.
Bước 3.1: Định nghĩa Annotation với @Target
Tạo file api_route.dart:
// api_route.dart
import 'package:meta/meta.dart';
/// 1. Sử dụng @Target để giới hạn chỗ đặt Annotation này
@Target({TargetKind.method, TargetKind.function})
class ApiRoute {
final String path;
// 2. Vẫn luôn phải có const constructor
const ApiRoute(this.path);
}Bước 3.2: Kiểm tra kết quả
Bay giờ hãy thử lấy @ApiRoute đi gán lung tung:
// main.dart
import 'api_route.dart';
// LỖI COMPILER: Lập tức bị báo lỗi vi phạm TargetKind
// Lỗi: The annotation 'ApiRoute' can only be used on methods, functions.
@ApiRoute('/users')
class UserController {
// LỖI COMPILER: Biến không được hỗ trợ
@ApiRoute('/name')
String name = "Bumbii";
// HỢP LỆ: Gắn lên phương thức (Method)
@ApiRoute('/get-users')
void getUsers() {
print("Fetching users...");
}
}
// HỢP LỆ: Gắn lên hàm tự do (Function)
@ApiRoute('/login')
void login() {
print("Logging in...");
}4. Lợi ích
- Báo lỗi tức thời (Fail fast): Lỗi được gạch chân đỏ trong IDE ngay khi lập trình viên vừa gõ xong, thay vì phải mất thời gian chờ chạy
build_runnermới phát hiện lỗi ở Code Generator. - Tự tài liệu hóa (Self-documenting): Khi người khác nhìn vào định nghĩa file Annotation của bạn, họ biết ngay ý đồ và cách sử dụng của nó mà không cần đọc thêm document dài dòng.
- Giúp Code Generator an toàn hơn: Bạn không cần phải viết thêm khối lệnh
if (element is! ClassElement)(kiểm tra rườm rà) khi viết generator nữa, vì Trình phân tích mã (Analyzer) đã đảm bảo không bao giờ có chuyện người dùng đặt sai thể loại truyền vào hàm generator của bạn.
Kết luận: Hãy luôn nhớ sử dụng
@Targetmỗi khi bạn thiết kế một Custom Annotation nếu bạn muốn quy định rõ ràng vùng hoat động của nó nhé!