gpt4 book ai didi

typescript - 为什么兼容的重载签名需要 `Subject & Array`?

转载 作者:搜寻专家 更新时间:2023-10-30 20:39:22 25 4
gpt4 key购买 nike

我得到:

Overload signature is not compatible with function implementation.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
}

然而这两种形式都适用于数组迭代器:

/** 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>破坏对象迭代器?

抱歉,这里的代码量太大了,这是我可以归结为的最小用例,其中包含了所有的考虑因素。

最佳答案

老实说,这需要费力地解决很多问题,而且我认为我无法准确回答您为什么让这些东西发挥作用。在我看来,您的过载签名都应该失败。让我们看一个 super 简单的重载/实现示例:

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 起)函数类型在它们的参数类型中是如何逆变的。这会导致以下行为:

type StringAccepter = (x: string) => void;
const helloAccepter: StringAccepter = (x: "hello") => {}; // error
const stringOrNumberAccepter: StringAccepter = (x: string | number) => {}; // okay

helloAccepter不是有效的 StringAccepter因为"hello"string 窄, 而 stringOrNumberAccepter 一个有效的StringAccepter因为string | numberstring 宽.因此,函数参数变宽会使它们的函数变窄,反之亦然:

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 所做的断言的方式大致相同至 subject .

现在编译器应该很高兴了。希望有所帮助;祝你好运!

关于typescript - 为什么兼容的重载签名需要 `Subject & Array<Value>`?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54008406/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com