Giới thiệu
Trong quá trình phát triển ứng dụng với TypeScript, việc sử dụng thư viện class-transformer để chuyển đổi dữ liệu giữa các lớp là rất phổ biến. Tuy nhiên, một số lập trình viên có thể gặp phải tình huống kỳ lạ khi sử dụng decorator @Transform. Trong bài viết này, chúng ta sẽ tìm hiểu nguyên nhân và cách khắc phục tình huống này để tối ưu hóa hiệu suất của mã nguồn.
Tình huống gặp phải
Khi sử dụng class-transformer với decorator @Transform, bạn có thể gặp trường hợp như sau:
typescript
import 'reflect-metadata';
import { plainToInstance, Transform } from 'class-transformer';
import { IsEmail, IsNotEmpty } from 'class-validator';
class MobileSignupBody {
@IsNotEmpty()
@IsEmail()
email!: string;
@Transform(({ obj }) => obj.email)
normalized_email?: string;
}
const dto = plainToInstance(MobileSignupBody, { email: 'abc@test.com' });
console.log(dto); // MobileSignupBody { email: 'abc@test.com' }
console.log(dto.normalized_email); // ❌ undefined
Trong đoạn mã trên, mặc dù bạn đã thêm decorator @Transform, nhưng biến normalized_email lại trả về undefined. Vậy nguyên nhân do đâu?
Nguyên nhân chính
Theo mặc định, class-transformer chỉ chạy @Transform trên các thuộc tính có trong đối tượng đầu vào.
Trong ví dụ trên, đối tượng đầu vào chỉ có:
json
{ "email": "abc@test.com" }
Do không có khóa normalized_email, class-transformer đã bỏ qua thuộc tính này hoàn toàn. Điều này là lý do mà biến normalized_email không được khởi tạo.
Cách khắc phục: Sử dụng @Expose
Để giải quyết vấn đề này, bạn cần sử dụng decorator @Expose để yêu cầu class-transformer luôn bao gồm thuộc tính đó, ngay cả khi nó không có trong đối tượng đầu vào.
typescript
import { Expose, Transform } from 'class-transformer';
class MobileSignupBody {
@Expose() // Luôn bao gồm trong đối tượng class/plain -> được chuyển đổi
@IsNotEmpty()
@IsEmail()
email!: string;
@Expose()
@Transform(({ obj }) => obj.email)
normalized_email?: string;
}
const dto = plainToInstance(MobileSignupBody, { email: 'abc@test.com' });
console.log(dto.normalized_email); // ✅ "abc@test.com"
Giờ đây, đoạn mã hoạt động bình thường vì @Expose yêu cầu normalized_email được đưa vào quy trình chuyển đổi.
Tùy chọn cấu hình toàn cục
Nếu bạn không muốn phải thêm @Expose() ở mọi nơi, bạn có một vài chiến lược toàn cục:
excludeExtraneousValues
typescript
const dto = plainToInstance(MobileSignupBody, { email: 'abc@test.com' }, {
excludeExtraneousValues: true,
});
Trong chế độ này, chỉ các thuộc tính có @Expose() mới được giữ lại. Nhược điểm là bạn phải đánh dấu tất cả các thuộc tính mà bạn muốn giữ lại.
strategy: 'exposeAll'
typescript
@Expose({ strategy: 'exposeAll' })
class MobileSignupBody {
email!: string;
@Expose()
@Transform(({ obj }) => obj.email)
normalized_email?: string;
}
Điều này làm cho tất cả các thuộc tính đều được công khai theo mặc định, và bạn chỉ cần sử dụng @Expose() nơi bạn muốn chuyển đổi.
Giải pháp thay thế: Sử dụng Getter
Đôi khi, giải pháp đơn giản nhất là chỉ cần một thuộc tính tính toán:
typescript
class MobileSignupBody {
email!: string;
get normalized_email(): string | undefined {
return this.email;
}
}
Không cần @Expose, không cần @Transform, luôn hoạt động.
Những điều cần nhớ
@Transformchỉ hoạt động nếu thuộc tính tồn tại trong đối tượng đầu vào.- Sử dụng
@Exposeđể buộc thuộc tính vào quy trình chuyển đổi. - Đối với hành vi toàn cục, sử dụng
excludeExtraneousValueshoặcstrategy: 'exposeAll'. Hoặc bỏ qua tất cả và sử dụng getters nếu bạn chỉ muốn một thuộc tính tính toán.
Các thực tiễn tốt nhất
- Đảm bảo rằng bạn hiểu cấu trúc dữ liệu đầu vào để tránh các lỗi không mong muốn khi sử dụng các decorator.
- Thử nghiệm với các tùy chọn toàn cục để tối ưu hóa mã nguồn của bạn.
Các cạm bẫy thường gặp
- Bỏ quên việc sử dụng
@Exposecó thể dẫn đến việc biến không được khởi tạo. - Không kiểm tra các thuộc tính đầu vào có thể dẫn đến lỗi khó phát hiện.
Mẹo hiệu suất
- Giảm thiểu việc sử dụng các decorator không cần thiết để tăng hiệu suất chuyển đổi.
- Xem xét việc sử dụng getters cho các thuộc tính tính toán khi không cần sử dụng
@Transform.
Khắc phục sự cố
- Nếu bạn gặp phải lỗi với các thuộc tính không được khởi tạo, hãy kiểm tra lại cấu trúc đối tượng đầu vào và đảm bảo rằng bạn đã sử dụng đúng các decorator.
Kết luận
Việc hiểu rõ cách thức hoạt động của class-transformer và các decorator như @Transform và @Expose có thể giúp bạn tối ưu hóa mã nguồn và tránh những lỗi không đáng có. Hãy thử áp dụng các cách khắc phục và chiến lược toàn cục để cải thiện hiệu suất công việc của bạn.
Nếu bạn đã từng gặp phải vấn đề này với @Transform, hãy chia sẻ cách bạn giải quyết và những kinh nghiệm của bạn trong việc xử lý các trường hợp tương tự trong các DTO. Để lại ý kiến của bạn ở dưới nhé! 👇