【问题标题】:how to extend a class with generic in typescript?如何在打字稿中使用泛型扩展一个类?
【发布时间】:2020-11-04 04:30:54
【问题描述】:

例如有这样一个类:

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

我已经绝望地寻找(((

【问题讨论】:

标签: typescript


【解决方案1】:

我真的不明白 class 如何帮助您使用这样的动态代理,所以我将不使用 class 写一些东西,它仍然可以提供我认为可能是所需的行为。

app 视为expando 对象的问题在于TypeScript 的静态类型系统并不想让您这样做。如果事先不知道 app 有一个名为 version 的类型,那么 TypeScript 的编译器不会让你读取这样的属性。您可以使用string index signature 来允许任何属性,但这对于您的用例来说可能过于宽松。理想情况下,我认为,您想调用app.bind(key, value),然后之后编译器知道app 在索引key 处有一个属性,其值类型为typeof value。 p>

这几乎可以通过将app 对象的bind() 方法设为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&lt;T&gt; 类型被视为Readonly&lt;T&gt;(意味着您可以读取与T 相同的所有属性)和_App&lt;T&gt;_App&lt;T&gt; 是一个带有 bind&lt;K, V&gt;(key: K, val: V) 方法的接口,该方法充当断言函数,该函数将带有键 K 和值 V 的属性添加到 T

makeApp() 函数返回一个App&lt;{}&gt;,或一个没有额外扩展属性的App。它通过Proxy 执行此操作。请注意,get() 处理程序必须是传递给new Proxy() 的第二个对象的一部分。我有 items 作为位于 makeApp() 函数主体内的闭包隐藏对象,当您读取和写入扩展属性时,您正在处理这个 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&lt;{}&gt; 更改为App&lt;{ version: string; }。所以编译器理解了这一点,然后编译器允许你从app.version 中读取string 值。


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

Playground link to code

【讨论】:

  • 如果我添加一个具有相同条件的新方法:compute&lt;K extends PropertyKey, V&gt;(key: K, val: V): asserts this is App&lt;Id&lt;T &amp; Record&lt;K, V&gt;&gt;&gt;; 那么自动完成在 ide 中对我不起作用。
  • 这个后续问题需要minimal reproducible example (“对我不起作用”并没有足够的信息)并且可能是它自己的帖子,因为它超出了原始问题的范围。祝你好运!
猜你喜欢
  • 1970-01-01
  • 2020-03-26
  • 1970-01-01
  • 2016-11-11
  • 1970-01-01
  • 2021-10-04
  • 2014-06-11
  • 1970-01-01
  • 2021-04-03
相关资源
最近更新 更多