问题描述
对我来说,大多数时候,需要动态检查来验证 fetch 响应.我在想,这可以通过用户定义的类型保护以通用方式完成,用于具有多个道具和附加检查的任何类型的对象,因此可以使用以下内容:
Most of the time for me, dynamic check is needed for verification of fetch response. And i was thinking, can this be done with user defined typeguard in a generic way for any type of object with multiple props and additional checks, so it can be used something like:
.
// ================= shared exported =================
type Writer = {
name: string
age: number
}
type Book = {
id: number
name: string
tags: string[] | null
writers: Writer[]
}
// function to check object with multiple props general shape, to not do it by hand
function ofType<T>(obj: any): obj is T {
if (!obj) return false;
// how to?
return true // or false
}
// ================= used and defined in components =================
function isBook(obj: any): obj is Book {
if (!ofType<Book>(obj)) return false //checking for shape and simple types
// cheking for specific values and ranges
if (obj.id < 1) return false
if (obj.writers && obj.writers.some(( { age } )=> age < 5 || age > 150)) return false
return true
}
const book = {
id: 1,
name: 'Avangers',
tags: ['marvel', 'fun'],
writers: [ {name: 'Max', age: 25}, {name: 'Max', age: 25}]
}
console.log(isBook(book)) // true or false
推荐答案
TypeScript 的类型系统是 擦除 编译为 JavaScript 时.这意味着任何单独使用标准 tsc
编译器从 type
或 interface
定义生成运行时类型保护的努力都不会成功;在运行时没有任何这些定义可供您使用.所以 ofType()
无法实现.
TypeScript's type system is erased when compiled to JavaScript. That implies any effort to use the standard tsc
compiler by itself to generate runtime type guards from type
or interface
definitions will not succeed; there's nothing of these definitions left at runtime for you to use. So ofType<T>()
cannot be implemented.
那你能做什么?
如果您愿意在构建系统中使用其他一些编译步骤,您可以编写或使用一个转换器,在这些定义被擦除之前为您制作类型保护.例如,typescript-is
将执行此操作.
If you're willing to use some other compilation step in your build system, you can write or use a transformer that makes type guards for you from these definitions before they are erased. For example, typescript-is
will do this.
或者你可以使用 class
定义代替;这使得在运行时检查变得容易(只需使用 instanceof
),但困难的部分是将 JSON 反序列化为类实例并在反序列化时捕获错误,而无需自己手动编写.所有这些都是将您的问题从实现 ofType(someObj)
转移到实现 myDeserializerFunction(Book, someObj)
其中 Book
是一个类构造函数.
Or you could use class
definitions instead; this makes checking easy at runtime (just use instanceof
) but the hard part is deserializing JSON into a class instance and catching errors upon deserialization without writing this yourself manually. All this does is move your problem from implementing ofType<Book>(someObj)
to implementing myDeserializerFunction(Book, someObj)
where Book
is a class constructor.
这里至少你可以使用 decorators 和 类元数据 生成程序化反序列化所需的代码.您可以自己编写,也可以使用现有的库,例如 json2typescript
.
Here at least you can use decorators and class metadata to generate the code needed for programmatic deserialization. You can write this yourself, or use an existing library such as json2typescript
.
最后,您可能决定从类型保护开始,让 TypeScript 推断您的 type
定义.也就是说,不是定义 Book
并希望从中获得类型保护 bookGuard()
,而是编写类型保护 bookGuard()
和根据typeof bookGuard
定义Book
.
Finally, you might decide to start with the type guards and let TypeScript infer your type
definitions from them. That is, instead of defining Book
and hoping to get a type guard bookGuard()
from it, you write the type guard bookGuard()
and define Book
in terms of typeof bookGuard
.
这个类型保护可以通过将现有的更简单的类型保护组合在一起来构建,所以它看起来更像是一个声明性的类型定义而不是一个数据检查函数.您可以自己编写,也可以使用现有的库,例如 io-ts
.
This type guard could be built by composing existing simpler type guards together, so it looks more like a declarative type definition than a data-checking function. You can write this yourself, or use an existing library such as io-ts
.
对于这种方法,看看如何编写这样的库是有益的.这是一种可能的实现:
For this approach, it's instructive to look at how one might write such a library. Here's one possible implementation:
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()
或 gPartial()
,但这里的函数对于您的示例来说已经足够了.
Here we are exporting a few type guards and functions which compose existing type guards. The gString()
, gNumber()
, gBoolean()
, and gNull()
functions are just type guards, while gObject()
, gArray()
, and gUnion()
take existing type guards to make new type guards out of them. You can see how gObject()
takes an object full of type guard properties and makes a new type guard where each property is checked against the corresponding guard. You could add other composition functions like gIntersection()
or gPartial()
, but the ones here are enough for your example.
现在你的 Book
和 Writer
定义看起来像这样(假设上面已经被导入为命名空间 G
):
Now your Book
and Writer
definitions look like this (assume the above has been imported as 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()
:
If you squint at that you'll see that it's analogous to your example Writer
and Book
definitions. But in our case the fundamental objects are type guards gWriter
and gBook
and the types Writer
and Book
are derived from them. And then you can use gBook
directly instead of the non-existent 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!
}
好的,希望有帮助;祝你好运!
Okay, hope that helps; good luck!
这篇关于打字稿在运行时按类型或接口检查对象,并在 2020 年以上使用打字机的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!