我得到:
重载签名与函数实现不兼容。ts(2394)
开启:
/** Iterate through an Array. */
export default function eachr<Value>(
subject: Array<Value>,
callback: IteratorCallback<typeof subject, number, Value>
): typeof subject
整个摘要中的
:export interface IteratorCallback<Subject, Key, Value> {
(this: Subject, value: Value, key: Key, subject: Subject): void | boolean
}
/** Iterate through an Array. */
export default function eachr<Value>(
subject: Array<Value>,
callback: IteratorCallback<typeof subject, number, Value>
): typeof subject
/** Iterate through an Object. */
export default function eachr<RecordKey extends keyof any, Value>(
subject: Record<RecordKey, Value>,
callback: IteratorCallback<typeof subject, RecordKey, Value>
): typeof subject
/** Iterate through the subject. */
export default function eachr<RecordKey extends keyof any, Value>(
input: Array<Value> | Record<RecordKey, Value>,
callback: IteratorCallback<typeof input, RecordKey | number, Value>
): typeof input {
if (Array.isArray(input)) {
// Array
const subject = input as Array<Value>
for (let key = 0; key < subject.length; ++key) {
const value = subject[key]
if (callback.call(subject, value, key, subject) === false) {
break
}
}
} else {
// Object
const subject = input as Record<RecordKey, Value>
for (const key in subject) {
if (subject.hasOwnProperty(key)) {
const value = subject[key]
if (callback.call(subject, value, key, subject) === false) {
break
}
}
}
}
// Return
return input
}
我可以将其更改为:
/** Iterate through an Array. */
export default function eachr<Subject extends Array<Value>, Value>(
subject: Subject & Array<Value>,
callback: IteratorCallback<typeof subject, number, Value>
): typeof subject
但是,我不明白为什么要修复它。问题出在哪里,为什么这种改变使问题消失了?
令我更加惊讶的是,如果我将相同的更改应用于纯对象迭代器函数,它将导致失败:
/** Iterate through an Object. */
export default function eachrObject<
Subject extends Record<RecordKey, Value>,
RecordKey extends keyof any,
Value
>(
subject: Subject & Record<RecordKey, Value>,
callback: IteratorCallback<typeof subject, RecordKey, Value>
): typeof subject {
for (const key in subject) {
if (subject.hasOwnProperty(key)) {
const value = subject[key]
// above fails with: Element implicitly has an 'any' type because type 'Record<RecordKey, Value>' has no index signature.ts(7017)
// below fails with: Argument of type 'string' is not assignable to parameter of type 'RecordKey'.ts(2345)
if (callback.call(subject, value, key, subject) === false) {
break
}
}
}
return subject
}
这可行:
/** Iterate through an Object. */
export default function eachrObject<RecordKey extends keyof any, Value>(
subject: Record<RecordKey, Value>,
callback: IteratorCallback<typeof subject, RecordKey, Value>
): typeof subject {
for (const key in subject) {
if (subject.hasOwnProperty(key)) {
const value = subject[key]
if (callback.call(subject, value, key, subject) === false) {
break
}
}
}
return subject
}
然而,这两种形式都可以用于Array迭代器:
/** Iterate through an Array. */
export default function eachrArray<Subject extends Array<Value>, Value>(
subject: Subject & Array<Value>,
callback: IteratorCallback<typeof subject, number, Value>
): typeof subject {
for (let key = 0; key < subject.length; ++key) {
const value = subject[key]
if (callback.call(subject, value, key, subject) === false) {
break
}
}
return subject
}
/** Iterate through an Array. */
export default function eachrArray<Value>(
subject: Array<Value>,
callback: IteratorCallback<typeof subject, number, Value>
): typeof subject {
for (let key = 0; key < subject.length; ++key) {
const value = subject[key]
if (callback.call(subject, value, key, subject) === false) {
break
}
}
return subject
}
那么,为什么要对Subject extends Array<Value>
进行更改才能使区域迭代器的兼容性过载,而Subject extends Record<RecordKey, Value>
却破坏了对象迭代器?
很抱歉,这里的代码量少,这是我可以归结为的最小用例,其中包含了所有考虑因素。
答案 0 :(得分:5)
老实说,这有很多困难要解决,我想我无法确切回答您为什么需要工作。我认为,您的重载签名都应该失败。让我们看一个超简单的重载/实现示例:
function foo(x: string): void; // narrower, okay
function foo(x: string | number | boolean): void; // wider, error
function foo(x: string | number): void {} // impl
请注意第二个重载签名如何给出与实现签名不兼容的错误。这是因为与实现的x
相比,重载的x
是更宽的类型。重载需要更窄的类型。
还要注意,一般而言(自--strictFunctionTypes
was introduced in TypeScript 2.6起)函数类型的参数类型是 contravariant 。导致以下行为:
type StringAccepter = (x: string) => void;
const helloAccepter: StringAccepter = (x: "hello") => {}; // error
const stringOrNumberAccepter: StringAccepter = (x: string | number) => {}; // okay
helloAccepter
不是有效的StringAccepter
,因为"hello"
比string
窄,而stringOrNumberAccepter
是有效的{{ 1}},因为StringAccepter
比string | number
宽。因此,功能参数变宽会使它们的功能变窄,反之亦然:
string
因此,我希望您的两个重载都将失败,因为实现签名的function bar(cb: (x: "hello")=>void): void; // error, cb is too wide because x is too narrow
function bar(cb: (x: string | number)=>void): void; // okay, cb is narrower because x is wider
function bar(cb: StringAccepter): void {} // impl
类型(callback
)实际上比调用签名的IteratorCallback<typeof input, RecordKey | number, Value>
类型都窄。
在这一点上,与其尝试尝试涉及额外的callback
类型参数的可能解决方案,不了解为什么有些事情起作用而有些事情不起作用(这使我的大脑受伤了……也许是因为编译器错误?也许不是?谁知道),我将使用我建议的解决方案...使实现签名真正足够宽,以支持两个调用签名:
Subject
区别在于实现签名上的/** Iterate through an Array. */
export function eachr<Value>(
subject: Array<Value>,
callback: IteratorCallback<typeof subject, number, Value>
): typeof subject
/** Iterate through an Object. */
export function eachr<RecordKey extends keyof any, Value>(
subject: Record<RecordKey, Value>,
callback: IteratorCallback<typeof subject, RecordKey, Value>
): typeof subject
/** Iterate through the subject. */
export function eachr<RecordKey extends keyof any, Value>(
input: Array<Value> | Record<RecordKey, Value>,
// here is the change
callback: IteratorCallback<Array<Value>, number, Value> |
IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
): typeof input {
if (Array.isArray(input)) {
// Array
const subject = input as Array<Value>
// a new assertion:
const cb = callback as IteratorCallback<Array<Value>, number, Value>;
for (let key = 0; key < subject.length; ++key) {
const value = subject[key]
if (cb.call(subject, value, key, subject) === false) {
break
}
}
} else {
// Object
const subject = input as Record<RecordKey, Value>
// a new assertion:
const cb = callback as IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>;
for (const key in subject) {
if (subject.hasOwnProperty(key)) {
const value = subject[key]
if (cb.call(subject, value, key, subject) === false) {
break
}
}
}
}
// Return
return input
}
参数是每个调用签名上callback
参数类似类型的真实结合。另外,实现本身需要对callback
到callback
进行缩小的断言,就像您已经对cb
到input
做断言一样。
现在,编译器应该很高兴。希望能有所帮助;祝你好运!
答案 1 :(得分:2)
问题
这与联合类型的工作方式有关。问题源于最后一次(累积)过载:
callback: IteratorCallback<typeof input, RecordKey | number, Value>
由于input
的类型为Array<Value> | Record<RecordKey, Value>
,因此以这种方式构造的callback
的定义允许存在4种可能的组合:
IteratorCallback<Array<Value>, RecordKey, Value>
IteratorCallback<Array<Value>, number, Value>
IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
IteratorCallback<Record<RecordKey, Value>, number, Value>
但根据您先前的重载定义,其中只有2个有效
解决方案
可以通过说callback
将是以下两种类型之一来解决此问题:
callback: IteratorCallback<Array<Value>, number, Value> | IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
这可以解决重载签名与函数实现不兼容错误。
但是,还发现了另一个问题:TypeScript不会在提供的input
的类型和随之而来的callback
之间建立连接。由于最后一个重载仍使用联合类型-两个用于input
,两个用于callback
-TypeScript认为可能发生4种情况。似乎最流行的解决方法是使用类型断言。