繁体   English   中英

Typescript 动态 class 功能来自 generics

[英]Typescript dynamic class functions from generics

我想做类似的事情

class C<R extends Record<string, unknown>> {
  [K extends keyof R](param: R[K]) {
    console.log(K, param); // have access to both key and value
  }
}

有没有办法用 typescript 实现这个?

目前我有类似的东西

class C<R extends Record<string, unknown>> {
  f<K extends keyof R>(k: K, param: R[K]) {
    console.log(k, param);
  }
}

我想看看我是否可以摆脱f因为它在我的用例中是多余的/在语义上没有用。

我读了这篇文章,但它只生成名称而不是函数的实现。

我会打电话给你的“当前” class C<R>

class C<R extends Record<string, unknown>> {
  f<K extends keyof R>(k: K, param: R[K]) {
    console.log(k, param);
  }
  z = 1; // example of some other property of C
}

我们可以计划根据它来定义一个新的 class D<R>来表现你想要的。 这个想法是D在某种意义上扩展C ... 这样C的每个属性也是D的一个属性(为此我添加了一个额外的属性),但D<R>也将具有所有属性从R并将它们分派到Cf方法。 也就是说, new D<{a: string}>().a("hello")应该和new C<{a: string}>().f("a", "hello")一样。


TypeScript 将只允许您使用“静态已知”密钥声明class es 或interface s。 由于R是泛型类型参数,因此keyof R不是“静态已知”的,因此编译器不会让您直接为此目的使用class语句。 相反,您可以描述D<R> class 的类型,然后使用类型断言之类的东西来声明某些构造函数具有正确的类型。

它看起来像

type D<R extends Record<string, unknown>> =
  C<R> & { [K in keyof R]: (param: R[K]) => void };

const D = ... as new <R extends Record<string, unknown>>() => D<R>;

...中的内容是实现。 所以这会起作用,但它有点难看(因为它迫使你显式写出类型)并且容易出错(因为编译器不能在这里验证类型安全)。


接下来,您不能直接实现将以这种方式工作的class 如果你有一个你想要的 class D<R>的实例i ,并且你调用ik(x)其中"k"keyof T中,你希望实例将调用分派给类似f("k", x) . 除非您在某个运行时拥有R的所有可能键的列表,否则您无法在D的原型上为所有这些键创建真正的方法。 这意味着D的方法需要具有动态名称,而class通常不会这样做。

如果您想要 JavaScript 中的动态属性名称,您将需要使用Proxy object ,它允许您编写一个get()处理程序来拦截所有属性获取。 如果你让D class 构造函数返回一个Proxy然后调用new D()将产生这样一个代理(通常你不会从 class 构造函数返回任何东西,但如果你这样做那么new会给你返回的东西而不是this ).

所以这是一种方法:

const D = class D {
  constructor() {
    return new Proxy(new C(), {
      get(t, k, r) {
        return ((typeof k === "string") && !(k in t)) ?
          (param: any) => t.f(k, param)
          : Reflect.get(t, k, r);
      }
    });
  }
} as new <R extends Record<string, unknown>>() => D<R>

如果kC实例中已经存在的键,那么您将获得该键的属性(因此调用Reflect.get() )。 否则,您会得到一个回调 function,它将其参数分派给C实例的f()方法。

请注意,这仅在您可以正确识别应在运行时将哪些属性分派给f()时才有效。 在此示例中,我们发送的所有内容都不是C的属性。 如果您需要一些更复杂的调度逻辑,您将需要自己在某个地方编写该逻辑,可能采用一堆硬编码属性键的形式。 但是对于所问的问题,这是 scope 之外的。

所以这可能有效,但同样......这是不寻常的( Proxy和构造函数中的return不是最佳实践)并且容易出错(我是否正确实施了它?)。


让我们看看它是否有效:

const d = new D<{ a: string, b: number, c: boolean }>();

d.a("hello") // "a", "hello"
d.b(123); // "b", 123
console.log(d.z.toFixed(2)) // 1.00

耶,它奏效了。 但我不确定代码的复杂性和脆弱性是否值得仅仅为了让调用者少敲几下键盘。 就个人而言,我宁愿保持代码简单和干净,让人们写c.f("a", "hello")而不是da("hello") 但这取决于你。


游乐场代码链接

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM