例如有一个这样的类:
const app = new class App {
constructor() {
this.items = {};
return new Proxy(this, this as any);
}
get (target, prop) {
if (prop === 'bind') return target.bind;
return target.items[prop];
}
bind (name, data) {
this.items[name] = data;
return this;
}
}
如何在具有泛型的打字稿中实现这样的构造以及以后将提供的内容如下:
app.bind('version', '1.0.0');
app.bind('configs', {path: '/', env: 'local'});
app.version.length;
app.configs.path.length;
let is_local = app.configs.env === 'local';
我已经拼命寻找((((
答案 0 :(得分:0)
我真的看不到class
如何为您提供这种动态代理,因此我将在不使用class
的情况下编写某些内容,但仍然给出我认为可能的期望行为。 / p>
将app
视为expando对象的问题在于TypeScript的静态类型系统实际上并不想允许您这样做。如果事先不知道app
的类型为version
,则TypeScript的编译器将不希望您读取此类属性。您可以使用string index signature来允许任何属性,但这对于您的用例而言可能太松懈了。理想情况下,我认为您想先调用app.bind(key, value)
,然后再调用 编译器知道app
的索引类型为key
的属性为typeof value
。
这几乎可以通过将bind()
对象的app
方法设置为assertion function来实现,从而缩小app
的类型。关于断言功能,有很多警告。为了使用它们,您必须显式注释app
的类型(请参见microsoft/TypeScript#33580)。
这里是一种方法:
interface _App<T> {
bind<K extends PropertyKey, V>(key: K, val: V): asserts this is App<Id<T & Record<K, V>>>;
}
type Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
type App<T = {}> = Readonly<T> & _App<T>;
function makeApp(): App {
const items: any = {};
const bind = (name: any, data: any) => items[name] = data;
return new Proxy({} as any, {
get(target, prop) {
if (prop === 'bind') return bind;
return items[prop];
}
})
}
const app: App = makeApp();
// ~~~~~ <----- this annotation is required
类型App<T>
被视为Readonly<T>
(意味着您可以读取与T
相同的所有属性)和_App<T>
。 _App<T>
是具有bind<K, V>(key: K, val: V)
方法的接口,该方法充当声明函数,该函数将具有键K
和值V
的属性添加到T
。 / p>
makeApp()
函数返回一个App<{}>
或一个App
,尚无额外的expando属性。它通过Proxy
完成。请注意,get()
处理程序必须是传递到new Proxy()
的第二个对象的一部分。我有items
作为一个闭包隐藏对象,它位于makeApp()
函数的主体内,当您读取和写入expando属性时,您正在处理的是这个items
对象,而不是this.items
。
让我们看看它是否有效:
// const app: App<{}>
app.bind('version', '1.0.0');
// const app: App<{ version: string; }>
app.bind('configs', { path: '/', env: 'local' });
// const app: App<{ version: string; configs: { path: string; env: string; };}>
console.log(app.version.length); // 5
console.log(app.configs.path.length); // 1
let is_local = app.configs.env === 'local';
console.log(is_local); // true
看起来不错。调用app.bind('version', '1.0.0')
之后,app
的类型将从App<{}>
变为App<{ version: string; }
。因此,编译器理解了这一点,随后,编译器允许您从string
读取app.version
值。
我不知道这是否满足您的所有用例,但希望它至少是一条前进的道路。