Ép kiểu đối tượng sang interface trong TypeScript
Tôi đang cố gắng thực hiện chuyển đổi kiểu trong mã của mình từ phần thân của yêu cầu trong express (sử dụng middleware body-parser) sang một interface, nhưng nó không thực thi an toàn kiểu.
Đây là giao diện của tôi:
export interface IToDoDto {
description: string;
status: boolean;
};
Đây là mã nơi tôi đang cố gắng thực hiện việc ép kiểu:
@Post()
addToDo(@Response() res, @Request() req) {
const toDo: IToDoDto = <IToDoDto> req.body; // <<< cast here
this.toDoService.addToDo(toDo);
return res.status(HttpStatus.CREATED).end();
}
Và cuối cùng, phương thức dịch vụ đang được gọi:
public addToDo(toDo: IToDoDto): void {
toDo.id = this.idCounter;
this.todos.push(toDo);
this.idCounter++;
}
Tôi có thể truyền bất kỳ đối số nào, ngay cả những đối số không hề gần giống với định nghĩa giao diện, và mã này sẽ hoạt động tốt. Tôi sẽ mong đợi, nếu việc ép kiểu từ thân phản hồi sang giao diện không khả thi, một ngoại lệ sẽ được ném ra khi chạy như Java hoặc C#.
Tôi đã đọc rằng trong TypeScript không tồn tại việc ép kiểu, chỉ có Khẳng định Kiểu, vì vậy nó chỉ nói với trình biên dịch rằng một đối tượng thuộc kiểu x, vậy... Tôi có sai không? Cách đúng để thực thi và đảm bảo an toàn kiểu là gì?
Câu trả lời #1
Không có kiểu ép trong javascript, vì vậy bạn không thể ném lỗi nếu "ép kiểu thất bại".
Typescript hỗ trợ ép kiểu nhưng điều đó chỉ áp dụng trong thời gian biên dịch, và bạn có thể làm như sau:
const toDo = req.body as IToDoDto;
// or
const toDo = <IToDoDto> req.body; // deprecated
Bạn có thể kiểm tra trong thời gian chạy nếu giá trị hợp lệ và nếu không hợp lệ thì ném ra một lỗi, tức là:
function isToDoDto(obj: any): obj is IToDoDto {
return typeof obj.description === "string" && typeof obj.status === "boolean";
}
@Post()
addToDo(@Response() res, @Request() req) {
if (!isToDoDto(req.body)) {
throw new Error("invalid request");
}
const toDo = req.body as IToDoDto;
this.toDoService.addToDo(toDo);
return res.status(HttpStatus.CREATED).end();
}
Chỉnh sửa
Như @huyz đã chỉ ra, không cần phải ép kiểu vì isToDoDto là một bộ bảo vệ kiểu, nên điều này là đủ:
if (!isToDoDto(req.body)) {
throw new Error("invalid request");
}
this.toDoService.addToDo(req.body);
Câu trả lời số 2
Đây là một cách khác để ép kiểu ngay cả giữa các kiểu và giao diện không tương thích mà trình biên dịch TS thường báo lỗi:
export function forceCast<T>(input: any): T {
// ... do runtime checks here
// @ts-ignore <-- forces TS compiler to compile this as-is
return input;
}
Sau đó bạn có thể sử dụng nó để ép kiểu các đối tượng sang một loại nhất định:
import { forceCast } from './forceCast';
const randomObject: any = {};
const typedObject = forceCast<IToDoDto>(randomObject);
Lưu ý rằng tôi đã bỏ qua phần bạn nên thực hiện kiểm tra thời gian chạy trước khi ép kiểu để giảm bớt độ phức tạp. Những gì tôi làm trong dự án của mình là biên dịch tất cả các tập tin giao diện .d.ts của tôi thành các lược đồ JSON và sử dụng ajv để xác thực trong thời gian chạy.
Câu trả lời số 3
Nếu điều đó giúp ai đó, tôi đã gặp vấn đề khi muốn xử lý một đối tượng như một loại khác có giao diện tương tự. Tôi đã thử những điều sau:
Không vượt qua kiểm tra lint
const x = new Obj(a as b);
Trình kiểm tra lỗi (linter) đã báo rằng a thiếu các thuộc tính mà b có. Nói cách khác, a có một số thuộc tính và phương thức của b, nhưng không phải tất cả. Để khắc phục điều này, tôi đã làm theo gợi ý của VS Code:
Đã qua kiểm tra linting và thử nghiệm
const x = new Obj(a as unknown as b);
Lưu ý rằng nếu mã của bạn cố gắng gọi một trong các thuộc tính tồn tại trên kiểu b mà không được triển khai trên kiểu a, bạn sẽ nhận thấy một lỗi thời gian chạy.
