Cảnh báo Chương này chỉ áp dụng cho cách tiếp cận code first.
Khi bạn xây dựng các tính năng như CRUD (Create/Read/Update/Delete), việc tạo ra các biến thể (variant) của một entity type cơ sở thường rất hữu ích. Nest cung cấp một số utility function thực hiện các phép biến đổi type để giúp công việc này thuận tiện hơn.
Partial
Khi xây dựng các type kiểm tra dữ liệu đầu vào (input validation, còn gọi là Data Transfer Object hay DTO), việc xây dựng biến thể create và update trên cùng một type thường rất hữu ích. Ví dụ, biến thể create có thể yêu cầu tất cả các field, trong khi biến thể update có thể làm cho tất cả field trở thành tùy chọn (optional).
Nest cung cấp hàm tiện ích PartialType() để giúp công việc này dễ dàng hơn và giảm thiểu code khung (boilerplate).
Hàm PartialType() trả về một type (class) với tất cả các thuộc tính của type đầu vào được đặt thành optional. Ví dụ, giả sử chúng ta có một type create như sau:
@InputType()
class CreateUserInput {
@Field()
email: string;
@Field()
password: string;
@Field()
firstName: string;
}
Theo mặc định, tất cả các field này đều bắt buộc. Để tạo một type có cùng các field, nhưng mỗi field đều là optional, dùng PartialType() truyền tham chiếu class (CreateUserInput) làm argument:
@InputType()
export class UpdateUserInput extends PartialType(CreateUserInput) {}
Gợi ý Hàm PartialType() được import từ package @nestjs/graphql.
Hàm PartialType() nhận một argument thứ hai tùy chọn là tham chiếu đến một decorator factory. Argument này có thể được dùng để thay đổi decorator function áp dụng cho class con (kết quả). Nếu không chỉ định, class con sẽ sử dụng cùng decorator như class cha (class được tham chiếu trong argument đầu tiên). Trong ví dụ trên, chúng ta đang mở rộng CreateUserInput, vốn được gắn decorator @InputType(). Vì chúng ta muốn UpdateUserInput cũng được xử lý như thể nó được gắn decorator @InputType(), chúng ta không cần truyền InputType làm argument thứ hai. Nếu type cha và type con khác nhau, (ví dụ, cha được gắn decorator @ObjectType), chúng ta sẽ truyền InputType làm argument thứ hai. Ví dụ:
@InputType()
export class UpdateUserInput extends PartialType(User, InputType) {}
Ghi chú người dịch Nếu bạn từng dùng generic của Go hoặc TypeScript utility type như Partial<T>, PartialType() làm việc tương tự nhưng ở mức decorator/metadata: nó không chỉ nới lỏng kiểu TypeScript (bỏ required), mà còn tự động copy lại toàn bộ metadata GraphQL (@Field, kiểu, mô tả…) từ class cha sang class con và gắn nullable: true cho từng field. Đây là lý do bạn không thể thay thế nó bằng Partial<CreateUserInput> thuần TypeScript — utility type đó không sinh ra metadata cho GraphQL schema.
Pick
Hàm PickType() xây dựng một type (class) mới bằng cách chọn ra một tập hợp thuộc tính từ type đầu vào. Ví dụ, giả sử chúng ta bắt đầu với một type như sau:
@InputType()
class CreateUserInput {
@Field()
email: string;
@Field()
password: string;
@Field()
firstName: string;
}
Chúng ta có thể chọn ra một tập hợp thuộc tính từ class này bằng hàm tiện ích PickType():
@InputType()
export class UpdateEmailInput extends PickType(CreateUserInput, [
'email',
] as const) {}
Gợi ý Hàm PickType() được import từ package @nestjs/graphql.
Omit
Hàm OmitType() xây dựng một type bằng cách chọn tất cả thuộc tính từ type đầu vào rồi loại bỏ một tập hợp key cụ thể. Ví dụ, giả sử chúng ta bắt đầu với một type như sau:
@InputType()
class CreateUserInput {
@Field()
email: string;
@Field()
password: string;
@Field()
firstName: string;
}
Chúng ta có thể sinh ra một type dẫn xuất có tất cả thuộc tính trừ email như bên dưới. Trong cấu trúc này, argument thứ hai của OmitType là một mảng tên thuộc tính.
@InputType()
export class UpdateUserInput extends OmitType(CreateUserInput, [
'email',
] as const) {}
Gợi ý Hàm OmitType() được import từ package @nestjs/graphql.
Intersection
Hàm IntersectionType() kết hợp hai type thành một type mới (class). Ví dụ, giả sử chúng ta bắt đầu với hai type như sau:
@InputType()
class CreateUserInput {
@Field()
email: string;
@Field()
password: string;
}
@ObjectType()
export class AdditionalUserInfo {
@Field()
firstName: string;
@Field()
lastName: string;
}
Chúng ta có thể sinh ra một type mới kết hợp tất cả thuộc tính của cả hai type.
@InputType()
export class UpdateUserInput extends IntersectionType(
CreateUserInput,
AdditionalUserInfo,
) {}
Gợi ý Hàm IntersectionType() được import từ package @nestjs/graphql.
Composition
Các hàm tiện ích type mapping này có thể kết hợp (composable) với nhau. Ví dụ, đoạn code sau sẽ tạo ra một type (class) có tất cả thuộc tính của type CreateUserInput ngoại trừ email, và các thuộc tính đó sẽ được đặt thành optional:
@InputType()
export class UpdateUserInput extends PartialType(
OmitType(CreateUserInput, ['email'] as const),
) {}
Ghi chú người dịch Cách kết hợp PartialType(OmitType(...)) này tương tự việc chain nhiều utility type trong TypeScript thuần (Partial<Omit<T, 'email'>>), hoặc tương tự việc struct embedding nhiều lớp trong Go rồi override lại field — mỗi hàm chỉ biến đổi một khía cạnh (bỏ field, nới lỏng required), và bạn lồng chúng vào nhau để có type cuối cùng đúng ý mà không phải viết lại DTO từ đầu.