我正在尝试为 Web 组件编写一个包装器,它允许您通过引用它们的密钥在 JSX 中使用它们;如果尚未注册自定义元素,这也会隐式注册,这样您就不会忘记注册。
是否可以有一些定义静态方法的抽象基类,并有扩展此类的子类,并在访问该方法时具有正确的类型。
例如:
abstract class Base {
static _key: string
static get key() {
console.log(this._key)
return this._key
}
}
class Child extends Base {
static _key = "child" as const
}
// Want key to be of type "child", not string
const key = Child.key
如果可能的话,我更愿意做这样的事情(因为这将允许我输入道具而不必到处修改
IntrinsicElements
),但我注意到类似的问题
abstract class Base {
abstract props: Record<string, unknown>
static Render<T extends typeof Base>(this: T, props: InstanceType<T>["props"]) {
return "jsx-representation-of-element"
}
}
class Child extends Base {
props = { hello: "world" }
}
// Some other file, test.tsx
// Expecting that the type is inferred to need Child's props, not Base's
// It doesn't care that hello is missing because the typeof this is
// is inferred to be Base
Child.Render({ hello: "yb" }) // okay 👍
{ <Child.Render hello="yb" /> } // okay 👍
Child.Render({ hello: 1 }) // error 👍
{ <Child.Render hello={1} /> } // NO ERROR?! 😢
我已经能够通过将
static key: typeof this._key
添加到 Child
来解决这个问题,但这需要我在任何地方添加它,所以希望我能找到一个不需要这个的解决方案。
对于第一部分,我研究了使用
this
作为参数,但这不能在吸气剂上完成。我意识到我可以通过使其成为一个普通方法来解决这个问题,然后在组件函数之外调用它一次,在 jsx 标记内引用该变量,但这似乎相当冗长。
我还尝试让 Base
采用泛型参数并在 Base.key
中返回它,忽略有关静态成员和泛型类型的错误,但这只是使类型 T
(实际上是 any
)
对于第二部分,我可以使用
this
参数,但如上所述,Child.Render
函数认为 this
类型是 Base
,因为它尚未被调用。
显然,一旦我调用
Child.Render()
,类型就可以了,但如果在 jsx 标签内使用,这将不起作用。
我可以使用一些 tsconfig 选项将其默认为
Child
的 this
吗?
this
类成员的多态static
类型。 microsoft/TypeScript#5863 上有一个长期悬而未决的问题,要求提供此类功能,但它目前不是该语言的一部分。看起来您尝试了在 generic静态方法上使用
this
参数 的“标准”解决方法。
当您直接调用该方法时,这是可行的,但正如您所看到的,这对于 JSX 元素来说,不适用于。这是 TypeScript 中的一个已知错误,其中函数中的
this
参数未使用 JSX 元素内的类型参数正确实例化。此问题已在 microsoft/TypeScript#55431 中报告,目前仍未修复。
所以你必须放弃或解决它。目前我能想到的唯一解决方法是更改泛型类型参数的范围,以便该方法已经知道它,并且可以直接引用而无需
this
参数。这意味着您希望 Base
是通用的......但这并不完全有效,因为静态成员无法访问类型参数。相反,您可以将 Base
制作为 class factory 函数,以 returns 类构造函数。 像这样:
function Base<T extends Record<string, unknown>>(initProps: T) {
return class {
props: T = initProps
static Render(props: T) {
return "jsx-representation-of-element"
}
}
}
所以这里
Base
本身并不是一个类。相反,您必须 call Base
并传递初始的 props
。然后它重新调整一个类,该类已知 T
的类型 props
:
class Child extends Base({ hello: "world" }) { }
所以
Base({hello: "world"})
返回的类是不是通用的。类型 {hello: string}
只是该类类型的一部分,因此 Base({hello: "world"}).Render()
只接受类型为 {hello: string}
的参数。 因此 Child
也是如此,因为它是 Base({hello: "world"})
的(当前空白)扩展。
这会产生您正在寻找的行为:
Child.Render({ hello: "yb" }) // okay 👍
{ <Child.Render hello="yb" /> } // okay 👍
Child.Render({ hello: 1 }) // error 👍
{ <Child.Render hello={1} /> } // error 👍
这回答了所提出的问题。但有一个警告:类工厂函数与超类不同。
请注意,类工厂在运行时并不真正使用继承,因此您不能轻松地使用(例如)
instanceof
运算符来检查某些内容是否是Base
的后代。当然 child instanceof Base
将会是 false,因为 Base
不是一个类,并且 child instanceof Base({hello: "yb"})
也会是 false。但由于每次调用 Base()
都会生成一个新类,因此两个不同的 Base()
输出没有共同的父类。 您也许可以尝试一下并让它发挥作用,但这并不简单。