TypeScript 的类型系统在编译为 JavaScript 时会被删除。这意味着任何单独使用标准tsc编译器来生成运行时类型保护type或interface定义的努力都不会成功;在运行时没有任何这些定义可供您使用。所以ofType<T>()无法实施。
那么你能做什么呢?
如果您愿意在构建系统中使用其他一些编译步骤,您可以编写或使用转换器,在这些定义被擦除之前为您生成类型保护。例如,typescript-is会这样做。
或者您可以改用class定义;这使得在运行时检查变得容易(只需使用instanceof),但困难的部分是将 JSON 反序列化为类实例并在反序列化时捕获错误,而无需自己手动编写。所有这一切都是将您的问题从实现ofType<Book>(someObj)转移到实现类构造函数的 myDeserializerFunction(Book, someObj)位置。Book
这里至少可以使用装饰器和类元数据来生成程序化反序列化所需的代码。您可以自己编写,也可以使用现有的库,例如json2typescript.
最后,你可能决定从类型保护开始,让 TypeScript从中推断出你的type定义。也就是说,不是定义Book并希望从中获得类型保护bookGuard(),而是编写类型保护bookGuard()并Book根据typeof bookGuard.
这种类型保护可以通过将现有的更简单的类型保护组合在一起来构建,因此它看起来更像是一个声明性类型定义,而不是一个数据检查函数。您可以自己编写,也可以使用现有的库,例如io-ts.
对于这种方法,看看如何编写这样的库是很有启发性的。这是一种可能的实现:
export type Guard<T> = (x: any) => x is T;
export type Guarded<T extends Guard<any>> = T extends Guard<infer V> ? V : never;
const primitiveGuard = <T>(typeOf: string) => (x: any): x is T => typeof x === typeOf;
export const gString = primitiveGuard<string>("string");
export const gNumber = primitiveGuard<number>("number");
export const gBoolean = primitiveGuard<boolean>("boolean");
export const gNull = (x: any): x is null => x === null;
export const gObject =
<T extends object>(propGuardObj: { [K in keyof T]: Guard<T[K]> }) =>
(x: any): x is T => typeof x === "object" && x !== null &&
(Object.keys(propGuardObj) as Array<keyof T>).
every(k => (k in x) && propGuardObj[k](x[k]));
export const gArray =
<T>(elemGuard: Guard<T>) => (x: any): x is Array<T> => Array.isArray(x) &&
x.every(el => elemGuard(el));
export const gUnion = <T, U>(tGuard: Guard<T>, uGuard: Guard<U>) =>
(x: any): x is T | U => tGuard(x) || uGuard(x);
在这里,我们导出了一些类型保护和组成现有类型保护的函数。, gString(), gNumber(),gBoolean()和gNull()函数只是类型保护,而gObject(), gArray(), 和gUnion()使用现有的类型保护来制作新的类型保护。您可以看到如何gObject()获取一个充满类型保护属性的对象并创建一个新的类型保护,其中每个属性都根据相应的保护进行检查。您可以添加其他组合功能,例如gIntersection()or gPartial(),但这里的功能对于您的示例来说已经足够了。
现在你的Book和Writer定义看起来像这样(假设上面已经被导入为 namespace G):
const _gWriter = G.gObject({
name: G.gString,
age: G.gNumber,
});
interface Writer extends G.Guarded<typeof _gWriter> { }
const gWriter: G.Guard<Writer> = _gWriter;
const _gBook = G.gObject({
id: G.gNumber,
name: G.gString,
tags: G.gUnion(G.gArray(G.gString), G.gNull),
writers: G.gArray(gWriter)
})
interface Book extends G.Guarded<typeof _gBook> { }
const gBook: G.Guard<Book> = _gBook;
如果您眯着眼睛看,您会发现它类似于您的示例Writer和Book定义。但在我们的例子中,基本对象是类型保护gWriter和gBook类型Writer,并且Book是从它们派生的。然后你可以gBook直接使用而不是不存在的ofType<Book>():
const book = JSON.parse('{"id":1,"name":"Avangers","tags":["marvel","fun"],' +
'"writers":[{"name":"Max","age":25},{"name":"Max","age":25}]}');
if (gBook(book)) {
console.log(book.name.toUpperCase() + "!"); // AVANGERS!
}
好的,希望有帮助;祝你好运!
Playground 代码链接