我想声明一些这样的接口:
interface Foo<K extends string> {
[k in K]: string // TS error: A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.
}
用法:
let b: Foo<'bar'> = { bar: '123' }
let c: Foo<'a' | 'b'> = { a: '123', b: '321' }
有可能吗?
更新:
我不能只使用Record
实用程序泛型或类型别名,因为我实际上想使用polymorphic this type。
interface Foo<K extends string> {
[k in K] (): this //
}
class Bar implements Foo<'sorted' | 'reversed'> { /* omitted */ }
let foo: Bar
foo.sorted() // returns Bar
foo.reversed().sorted().sorted().reversed() // still Bar
答案 0 :(得分:2)
接口不能包含映射类型,只有类型别名可以:
type Foo<K extends string> = {
[k in K]: string
}
let b: Foo<'bar'> = { bar: '123' }
let c: Foo<'a' | 'b'> = { a: '123', b: '321' }
请注意,此类型与Record
非常相似,您可能只想使用它。这等效于显式映射类型:
type Foo<K extends string> = Record<K, string>
答案 1 :(得分:0)
@Titian Cernicova-Dragomir的回答对于原始问题是正确的,而我的实际潜在问题要比问题本身复杂得多。我将解决方案发布在这里,以防其他人有类似需求到达这里。
实际问题是:
假设我有一个类TClass
和一个函数addMethod
,该函数向TClass.prototype
添加了一些方法。换句话说,addMethod
充当装饰器,但是我省略了@
装饰器语法,因为它至少在目前不能更改要装饰的类的签名(请参见跟踪的Github issue此功能)。
如果addMethod
仅被调用一次,则通过编写递归类型别名可以轻松解决该问题:
type Ctor<T, Args extends any[] = any[]> = new (...args: Args) => T
type CtorParameters<T extends Ctor<any>> = T extends Ctor<any, infer P> ? P : never
type Augmented<T, K extends string, Args extends any[]> = T & { [k in K]: (...args: Args) => Augmented<T, K, Args> }
function addMethod<
T extends Ctor<any>,
K extends string,
Args extends any[]
> (ctor: T, k: K, method: (this: InstanceType<T>, ...args: Args) => void): Ctor<Augmented<InstanceType<T>, K, Args>, CtorParameters<T>> {
ctor.prototype[k] = function (...args: any[]) {
method.apply(this, args)
return this
}
return ctor
}
class Foo {
constructor(public foo: number) {}
}
const Bar = addMethod(Foo, 'inc', function (delta: number) {
this.foo += delta
})
const bar = new Bar(1)
bar.inc(2)
console.log(bar.foo) // 3
bar.inc(3).inc(4)
console.log(bar.foo) // 10
但是,如果随后addMethod
被多次调用,则结果构造函数的实例将受到某种程度的损害。添加的方法只有按特定顺序调用才能完全链接。
const Baz = addMethod(Bar, 'mult', function (factor: number) {
this.foo *= factor
})
const baz = new Baz(2)
baz.mult(3).inc(4)
console.log(baz.foo) // 10, works
baz.inc(3).mult(4) // Property 'mult' does not exist on type 'Augmented<Foo, "inc", [number]>'.
这就是为什么我发布原始问题。我意识到,使用递归类型别名来表示扩充类型是行不通的,因为先前添加的方法的返回类型不包括后来添加的方法。然后,我认为将返回类型声明为this
会有所帮助。但是this
仅在class
或interface
内部受支持。因此是问题。
但是,我也意识到addMethod
不需要多次调用,只要我可以在一次调用中提供方法即可。
type Augmented<T, Methods extends Record<string, (...args: any[]) => void>> =
T & { [k in keyof Methods]: (...args: Parameters<Methods[k]>) => Augmented<T, Methods> }
function addMethod<
T extends Ctor<any>,
Methods extends Record<string, (...args: any[]) => void>
> (ctor: T, methods: Methods & ThisType<InstanceType<T>>): Ctor<Augmented<InstanceType<T>, Methods>> {
const keys = Object.keys(methods) as (keyof Methods)[]
keys.forEach(k => {
ctor.prototype[k] = function (...args: any[]) {
methods[k].apply(this, args)
return this
}
})
return ctor
}
const Baz = addMethod(Foo, {
inc: function (delta: number) {
this.foo += delta
},
mult: function (factor: number) {
this.foo *= factor
}
})
const baz = new Baz(2)
baz.mult(3).inc(4)
console.log(baz.foo) // 10, works
baz.foo = 2
baz.inc(3).mult(4)
console.log(baz.foo) // 20, works