typescript - 从类工厂函数创建清晰的 TypeScript 类
问题描述
我有一个第三方库,它返回OpaqueObject
包含对打字稿不透明的字段的对象(类型)。对于任何给定OpaqueObject
的,我知道定义和/或需要哪些字段,但 Typescript 一无所知。给定的“模式”OpaqueObject
简单地列出了定义的字段,并带有一个布尔标志,指示它们是否需要存在:
type OpaqueObjectSchema = { [field: string]: boolean }
字段是OpaqueObject
通过getField
函数检索的。getField
使用类型谓词来调整返回类型,具体取决于是否需要该字段。获取不存在的必填字段会引发错误。这是签名:
getField<B extends boolean>(
obj: OpaqueObject, field: string, isRequired: B,
): B extends true ? string : (string | undefined)
我有一个类工厂函数 ( opaqueObjectWrapperFactory
),它接收OpaqueObjectSchema
并输出一个包装类。例如,我有一种类型,OpaqueObject
它有两个字段:f1
和f2
. f1
是必须的; f2
可能未定义。我工厂生产的类的硬编码版本是:
class OpaqueObjectWrapper {
private obj: OpaqueObject
constructor(obj: OpaqueObject) {
this.obj = obj;
}
get f1(): string { return getField(this.obj, 'f1', true) }
get f2(): string | undefined { return getField(this.obj, 'f2', false) }
}
// OpaqueObjectWrapper is equivalent to the return value of:
opaqueObjectWrapperFactory({
f1: true,
f2: false,
});
我的问题是我无法弄清楚如何使这些生成的类对 Typescript 清晰可见。工厂应该像这样工作:
// this declaration should have the same effect as hardcoding `OpaqueObjectWrapper`
class OpaqueObjectWrapper extends opaqueObjectWrapperFactory({ f1: true, f2: false });
显然我需要以某种方式使用泛型,但我不确定如何从输入模式派生返回接口。这可能吗?
解决方案
派生对象类型
AnOpaqueObject
是所有值string
都是必需的,而其他键是可选的。让我们首先定义一个类型,假设我们已经知道哪个是哪个:
type OpaqueObject<AllKeys extends string, RequiredKeys extends string> = Partial<Record<AllKeys, string>> & Record<RequiredKeys, string>
如果我们想从一个模式转到一个对象,我们知道我们需要找到所有的键——这很容易keyof Schema
——以及所需的键。如果模式中的值是 ,我们知道如果需要的键是true
.
重要提示:我们必须as const
在创建模式时使用才能与 分开true
,false
否则我们只知道我们有一个boolean
.
架构所需的键S
是:
type RequiredKeys<S> = {
[K in keyof S]: S[K] extends true ? K : never;
}[keyof S]
所以我们现在可以为一个OpaqueObject
只依赖于模式的类型写一个类型S
。
type OpaqueObject<S> = Partial<Record<keyof S, string>> & Record<RequiredKeys<S>, string>
获取字段
现在到getField
功能。我们不想传入一个 boolean required 标志,因为我们应该已经知道这一点。相反,让我们让它依赖于泛型 schemaS
和 key K
:
function getField<S, K extends keyof S>(
obj: OpaqueObject<S>, field: K
): OpaqueObject<S>[K] {
return obj[field];
}
但老实说,如果我们有一个正确类型的对象,那么整个函数就变得不必要了,因为我们可以通过直接访问属性获得正确的返回类型。
const exampleSchema = {
f1: true,
f2: false,
} as const;
type ExampleObject = OpaqueObject<typeof exampleSchema>
class OpaqueObjectWrapper {
private obj: ExampleObject
constructor(obj: ExampleObject) {
this.obj = obj;
}
get f1(): string { return this.obj.f1 }
get f2(): string | undefined { return this.obj.f2 }
}
未知数
关于是否可能,我对模式的来源感到困惑as const
。这些是来自外部来源的变量吗?还是您通过在代码中写出对象来定义它们?
让我对您的问题感到困惑的部分是在课堂get f1()
内get f2()
以动态的方式实施。与 PHP 等其他语言不同,Javascript 没有属性值未知的动态 getter。您只能通过Proxy
.
代理对象
我知道如何动态获取属性的唯一方法是使用Proxy。我已经解决了这个问题。我仍然缺少的部分是如何在代理的伪类上实现构造签名。
这个代理接受一个类的实例,该实例将对象存储为属性obj
,并允许我们直接访问属性obj
。为了让 typescript 理解添加的属性,我们必须断言可以访问as Constructable<T> & T
的所有属性。T
const proxied = <T,>(inst: Constructable<T> ) => {
return new Proxy( inst, {
get: function <K extends keyof T>(oTarget: Constructable<T>, sKey: K): T[K] {
return oTarget.obj[sKey];
},
}) as Constructable<T> & T
}
我用来存储对象的底层类是
// stores an object internally, but allows it to be created by calling new()
class Constructable<T> {
private _obj: T
constructor(obj: T) {
this._obj = obj;
}
// object is readonly
get obj(): T {
return this._obj;
}
}
所以现在我们要创建一个基于模式的代理类。我们想要这个:
interface ProxiedConstructable<T> {
// pass in an object T and get something which can access of the properties of T
new( args: T ): Readonly<T>;
}
我告诉过你我并不是一路走来,这是因为代理适用于类的实例而不是类本身,所以我坚持让我们的工厂返回一些“新的”,但这就是我有:
const makeProxied = <S extends { [field: string]: boolean }>(schema: S) =>
(obj: OpaqueObject<S>) => {
return proxied( new Constructable(obj) );
}
像这样工作:
const test = makeProxied({
f1: true,
f2: false
} as const);
const testObj = test({f1: "hello world"});
const f1: string = testObj.f1;
const f2: string | undefined = testObj.f2;
推荐阅读
- python - 在给定时间码之间从视频中每秒保存 10 帧的更有效方法 - Python
- javascript - 使用内容跨源创建 iFrame
- javascript - Firebase onCreate 令人困惑
- android - xamarin android sqlite insert List objectes ti sqlite async
- c# - .NET 密码学中 MACTripleDES 和 HMAC 的区别
- c# - log4net 抛出异常?
- express - node-express 如何同时监听 localhost 和本地 IP?
- php - WordPress 自定义用户配置文件字段
- angular - 'npm i @auth0/angular-jwt' 中不存在 angular2-jwt 中的 AuthHttp 类
- node.js - CLI 应用程序(用 Node 编写)应该在哪里缓存东西?