Elm*_*lmo 2 javascript types class typescript
我有一些代码,例如:
const methodsList = [
'foo',
'bar',
// ... 20 other items ...
]
export class Relayer {
constructor() {
for (const methodName of methodsList) {
this[methodName] = (...args) => {
// console.log('relaying call to', methodName, args)
// this is same for all methods
}
}
}
}
const relayer = new Relayer()
relayer.foo('asd') // TS error
relayer.bar('jkl', 123) // TS error
Run Code Online (Sandbox Code Playgroud)
现在,当我使用类实例时,当我调用relayer.foo()或时,TypeScript 会发出抱怨relayer.bar()。为了使代码编译,我必须对其进行强制转换as any或类似的操作。
foo我有一个声明和bar其他方法的接口:
interface MyInterface {
foo: (a: string) => Promise<string>
bar: (b: string, c: number) => Promise<string>
// ... 20 other methods
}
Run Code Online (Sandbox Code Playgroud)
如何让 TypeScript 学习动态声明的方法foo和bar类方法?declare语法在这里有用吗?
第一步是创建一个类型或接口,当按 中的值索引时methodsList,结果将是一个函数:
// The cast to const changes the type from `string[]` to
// `['foo', 'bar']` (An array of literal string types)
const methodsList = [
'foo',
'bar'
] as const
type HasMethods = { [k in typeof methodsList[number]]: (...args: any[]) => any }
// Or
type MethodNames = typeof methodsList[number] // "foo" | "bar"
// k is either "foo" or "bar", and obj[k] is any function
type HasMethods = { [k in MethodNames]: (...args: any[]) => any }
Run Code Online (Sandbox Code Playgroud)
然后,在构造函数中,为了能够分配 的键methodsList,您可以添加一个类型断言this is HasMethods:
// General purpose assert function
// If before this, value had type `U`,
// afterwards the type will be `U & T`
declare function assertIs<T>(value: unknown): asserts value is T
class Relayer {
constructor() {
assertIs<HasMethods>(this)
for (const methodName of methodsList) {
// `methodName` has type `"foo" | "bar"`, since
// it's the value of an array with literal type,
// so can index `this` in a type-safe way
this[methodName] = (...args) => {
// ...
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在,在构建之后,您仍然必须转换类型:
const relayer = new Relayer() as Relayer & HasMethods
relayer.foo('asd')
relayer.bar('jkl', 123)
Run Code Online (Sandbox Code Playgroud)
您还可以在使用工厂函数构造时摆脱强制转换:
export class Relayer {
constructor() {
// As above
}
static construct(): Relayer & HasMethods {
return new Relayer() as Relayer & HasMethods
}
}
const relayer = Relayer.construct()
Run Code Online (Sandbox Code Playgroud)
另一种解决方法是创建一个新类和类型断言,从而new生成一个HasMethods对象:
class _Relayer {
constructor() {
assertIs<HasMethods>(this)
for (const methodName of methodsList) {
this[methodName] = (...args) => {
// ...
}
}
}
}
export const Relayer = _Relayer as _Relayer & { new (): _Relayer & HasMethods }
const relayer = new Relayer();
relayer.foo('asd')
relayer.bar('jkl', 123)
Run Code Online (Sandbox Code Playgroud)
或者,如果您只使用newand then 中的方法methodsList,您可以这样做:
export const Relayer = class Relayer {
constructor() {
assertIs<HasMethods>(this)
for (const methodName of methodsList) {
this[methodName] = (...args) => {
// ...
}
}
}
} as { new (): HasMethods };
Run Code Online (Sandbox Code Playgroud)
您还可以使用您的MyInterface界面而不是HasMethods,跳过第一步。这也为您的调用提供了类型安全。