[英]Typescript dynamic class functions from generics
I want to do something like我想做类似的事情
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
}
}
Is there a way to achieve this with typescript?有没有办法用 typescript 实现这个?
Currently I have something like目前我有类似的东西
class C<R extends Record<string, unknown>> {
f<K extends keyof R>(k: K, param: R[K]) {
console.log(k, param);
}
}
and I want to see if I can get rid of the f
since it's redundant/semantically not useful in my use case.我想看看我是否可以摆脱
f
因为它在我的用例中是多余的/在语义上没有用。
I read this post, but it only generates the names and not the implementations of the functions.我读了这篇文章,但它只生成名称而不是函数的实现。
I'm going to call your "current" class C<R>
:我会打电话给你的“当前” 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
}
and we can plan to define a new class D<R>
in terms of it that behaves how you want.我们可以计划根据它来定义一个新的 class
D<R>
来表现你想要的。 The idea will be that D
in some sense extends C
... so that every property of C
is also a property of D
(I've added an extra property for that reason), but D<R>
will also have all the properties from R
and will dispatch them to the f
method of C
.这个想法是
D
在某种意义上扩展C
... 这样C
的每个属性也是D
的一个属性(为此我添加了一个额外的属性),但D<R>
也将具有所有属性从R
并将它们分派到C
的f
方法。 That is, new D<{a: string}>().a("hello")
should act the same as new C<{a: string}>().f("a", "hello")
.也就是说,
new D<{a: string}>().a("hello")
应该和new C<{a: string}>().f("a", "hello")
一样。
TypeScript will only allow you to declare class
es or interface
s with "statically known" keys. TypeScript 将只允许您使用“静态已知”密钥声明
class
es 或interface
s。 Since R
is a generic type parameter, then keyof R
is not "statically known" so the compiler won't let you just use a class
statement directly for this purpose.由于
R
是泛型类型参数,因此keyof R
不是“静态已知”的,因此编译器不会让您直接为此目的使用class
语句。 Instead, you can describe the type of your D<R>
class and then use something like a type assertion to claim that some constructor has the right type.相反,您可以描述
D<R>
class 的类型,然后使用类型断言之类的东西来声明某些构造函数具有正确的类型。
It will look like它看起来像
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>;
where the stuff in ...
is the implementation. ...
中的内容是实现。 So this will work, but it's somewhat ugly (since it forces you to write out the type explicitly) and error prone (since the compiler can't verify type safety here).所以这会起作用,但它有点难看(因为它迫使你显式写出类型)并且容易出错(因为编译器不能在这里验证类型安全)。
Next, you can't implement a class
directly that will work this way.接下来,您不能直接实现将以这种方式工作的
class
。 If you have an instance i
of your desired class D<R>
, and you call ik(x)
where "k"
is in keyof T
, you want the instance to dispatch the call to something like f("k", x)
.如果你有一个你想要的 class
D<R>
的实例i
,并且你调用ik(x)
其中"k"
在keyof T
中,你希望实例将调用分派给类似f("k", x)
. Unless you have a list of all possible keys of R
at runtime somewhere, you can't make real methods for all of them on the prototype of D
.除非您在某个运行时拥有
R
的所有可能键的列表,否则您无法在D
的原型上为所有这些键创建真正的方法。 That implies the methods of D
need to have dynamic names, and that's not something a class
normally does.这意味着
D
的方法需要具有动态名称,而class
通常不会这样做。
If you want dynamic property names in JavaScript, you will want to use a Proxy
object which lets you write a get()
handler to intercept all property gets.如果您想要 JavaScript 中的动态属性名称,您将需要使用
Proxy
object ,它允许您编写一个get()
处理程序来拦截所有属性获取。 If you make the D
class constructor return a Proxy
then a call to new D()
will produce such a proxy (normally you don't return anything from class constructors, but if you do then new
will give you the returned thing instead of this
).如果你让
D
class 构造函数返回一个Proxy
然后调用new D()
将产生这样一个代理(通常你不会从 class 构造函数返回任何东西,但如果你这样做那么new
会给你返回的东西而不是this
).
So here's one way to do it:所以这是一种方法:
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>
If k
is a key already present in the C
instance, then you'll get the property at that key (hence the Reflect.get()
call).如果
k
是C
实例中已经存在的键,那么您将获得该键的属性(因此调用Reflect.get()
)。 Otherwise, you get a callback function which dispatches its parameter to the f()
method of the C
instance.否则,您会得到一个回调 function,它将其参数分派给
C
实例的f()
方法。
Note that this only works if you can correctly identify which properties should get dispatched to f()
at runtime.请注意,这仅在您可以正确识别应在运行时将哪些属性分派给
f()
时才有效。 In this example, we are sending everything which isn't already a property of C
.在此示例中,我们发送的所有内容都不是
C
的属性。 If you have a need for some more complicated dispatching logic, you will need to write that logic yourself somewhere, probably in the form of a bunch of hard-coded property keys.如果您需要一些更复杂的调度逻辑,您将需要自己在某个地方编写该逻辑,可能采用一堆硬编码属性键的形式。 But that's out of scope for the question as asked.
但是对于所问的问题,这是 scope 之外的。
So that might work, but again... it's unusual ( Proxy
and a return
in a constructor is not best practice) and error-prone ( did I implement it right? ).所以这可能有效,但同样......这是不寻常的(
Proxy
和构造函数中的return
不是最佳实践)并且容易出错(我是否正确实施了它?)。
Let's see if it works:让我们看看它是否有效:
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
Yay, it worked.耶,它奏效了。 But I'm not sure it's worth the complexity and fragility of the code just to save a caller a few keystrokes.
但我不确定代码的复杂性和脆弱性是否值得仅仅为了让调用者少敲几下键盘。 Personally I'd rather keep the code simple and clean and make people write
c.f("a", "hello")
instead of da("hello")
.就个人而言,我宁愿保持代码简单和干净,让人们写
c.f("a", "hello")
而不是da("hello")
。 But it's up to you.但这取决于你。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.