高阶函数参数类型的错误类型推断
Wrong type inference of higher-order function argument type
假设我想编写接受某种类型 T 和另一个值的对象的函数,该类型 P 应该以某种方式受 T 限制,例如 P 应该是 T 的键数组。
我可以轻松写出来:
function bar<T, P extends keyof T>(obj: T, p: P[]) {
// use p to index obj somehow
return obj;
}
bar({ a: 1, b: 'foo' }, ['a']); // Ok
bar({ a: 1, b: 'foo' }, ['a', 'b']); // Ok
bar({ a: 1, b: 'foo' }, ['a', 'b', 'c']); // Error: 'c' is not valid key
想象一下,然后我想将该函数用作高阶方法的参数,它应该与第二个参数 arg
一起接受它,然后用 this
和 arg
调用它:
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']); // Error, `Type 'string' is not assignable to type '"a" | "app"'.`
const a = new Indexed().app(bar, ['wtf']); // The same
如果我直接使用 bar
,一切都会按预期工作:
bar(new Indexed(), ['a']); // Ok
bar(new Indexed(), ['wtf']); // Err, as expected
问题是:如何编写 app
使其 accept/reject 参数与 bar
相同?
请注意,通常我不知道 bar
先验的限制,因此我无法使用与 bar
.
中相同的界限来限制 P
我认为这只是 TypeScript 将 ["foo","bar"]
扩展为 string[]
的情况,因为它没有意识到您需要该类型来保留字符串文字 ["foo", "bar"]
的元组(或至少一个字符串文字数组 Array<"foo"|"bar">
)。在您的 bar()
函数中,P
被限制为 keyof
任何提示编译器不要将字符串文字扩展为字符串,但 P
在 [=21= 中不存在这样的提示].
要么你需要想出一种方法来修改 Indexed.app()
签名,以便暗示 P
应该尽可能以狭窄的方式推断而不实际限制它(因为你不知道 P
会是什么,就像你说的那样),或者你需要想出一种方法来 hint/specify 当你 调用 [=48 时 P
应该变窄=] Indexed.app()
。
修改 app()
的签名来做到这一点目前需要一些奇怪的技巧,直到并且除非这个 changes,它看起来像这样:
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"
好的,希望其中之一对您有所帮助。祝你好运!
假设我想编写接受某种类型 T 和另一个值的对象的函数,该类型 P 应该以某种方式受 T 限制,例如 P 应该是 T 的键数组。
我可以轻松写出来:
function bar<T, P extends keyof T>(obj: T, p: P[]) {
// use p to index obj somehow
return obj;
}
bar({ a: 1, b: 'foo' }, ['a']); // Ok
bar({ a: 1, b: 'foo' }, ['a', 'b']); // Ok
bar({ a: 1, b: 'foo' }, ['a', 'b', 'c']); // Error: 'c' is not valid key
想象一下,然后我想将该函数用作高阶方法的参数,它应该与第二个参数 arg
一起接受它,然后用 this
和 arg
调用它:
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']); // Error, `Type 'string' is not assignable to type '"a" | "app"'.`
const a = new Indexed().app(bar, ['wtf']); // The same
如果我直接使用 bar
,一切都会按预期工作:
bar(new Indexed(), ['a']); // Ok
bar(new Indexed(), ['wtf']); // Err, as expected
问题是:如何编写 app
使其 accept/reject 参数与 bar
相同?
请注意,通常我不知道 bar
先验的限制,因此我无法使用与 bar
.
P
我认为这只是 TypeScript 将 ["foo","bar"]
扩展为 string[]
的情况,因为它没有意识到您需要该类型来保留字符串文字 ["foo", "bar"]
的元组(或至少一个字符串文字数组 Array<"foo"|"bar">
)。在您的 bar()
函数中,P
被限制为 keyof
任何提示编译器不要将字符串文字扩展为字符串,但 P
在 [=21= 中不存在这样的提示].
要么你需要想出一种方法来修改 Indexed.app()
签名,以便暗示 P
应该尽可能以狭窄的方式推断而不实际限制它(因为你不知道 P
会是什么,就像你说的那样),或者你需要想出一种方法来 hint/specify 当你 调用 [=48 时 P
应该变窄=] Indexed.app()
。
修改 app()
的签名来做到这一点目前需要一些奇怪的技巧,直到并且除非这个 changes,它看起来像这样:
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"
好的,希望其中之一对您有所帮助。祝你好运!