Я думаю, что это как раз тот случай, когда TypeScript расширяется от ["foo","bar"]
до string[]
, потому что он не понимает, что вам нужен тип, чтобы оставаться кортежем строковых литералов ["foo", "bar"]
(или, по крайней мере, массива строковых литералов Array<"foo"|"bar">
).В вашей функции bar()
ограничение P
на keyof
указывает на то, что компилятор не будет расширять строковые литералы до строки, но такой подсказки не существует для P
в Indexed.app()
.
Либо вам нужно придумать способ изменить подпись Indexed.app()
, чтобы намекнуть, что P
следует выводить узким способом, где это возможно, без фактического ограничения (поскольку вы не знаете, что P
будет, как вы сказали), или вам нужно найти способ намекнуть / указать, что P
должно быть узким, когда вы звоните Indexed.app()
.
Изменение подписи app()
для этого в настоящее время требует некоторых странных уловок, и до тех пор, пока не изменит , оно выглядит так:
type Narrowable =
| string
| number
| boolean
| symbol
| object
| undefined
| void
| null
| {};
class Indexed {
constructor(public a: number = 1) {}
public app<
N extends Narrowable,
P extends N | [] | { [k: string]: N | P | [] }
>(f: (obj: this, arg: P) => this, arg: P) {
return f(this, arg);
}
}
const a = new Indexed().app(bar, ["a"]); // okay
const b = new Indexed().app(bar, ["wtf"]); // error "wtf" not assignable to "a"|"app"
Подсказка на сайте вызова менее уродлива, если вызывающая сторона не забудет это сделать:
class Indexed {
constructor(public a: number = 1) {}
public app<P>(f: (obj: this, arg: P) => this, arg: P) {
return f(this, arg);
}
}
const a = new Indexed().app(bar, ["a" as "a"]); // okay
const b = new Indexed().app(bar, ["wtf" as "wtf"]); // error "wtf" not assignable to "a"|"app"
Или вы можете забыть подсказку и просто указать параметр типа самостоятельно:
const c = new Indexed().app<["a"]>(bar, ["a"]); // okay
const d = new Indexed().app<["wtf"]>(bar, ["wtf"]); // error "wtf" not assignable to "a"|"app"
Хорошо, надеюсь, одна из тех поможет.Удачи!
Ссылка на код