如何在打字稿中使用泛型扩展类?

时间:2020-11-03 19:40:54

标签: typescript

例如有一个这样的类:

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';

我已经拼命寻找((((

1 个答案:

答案 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值。


我不知道这是否满足您的所有用例,但希望它至少是一条前进的道路。

Playground link to code

相关问题