【问题标题】:Interface treated differently than equivalent object接口的处理方式与等效对象不同
【发布时间】:2019-11-06 02:09:31
【问题描述】:

在下面的 sn-p 中,最后两条看似等效的行显然不是。我想编写我的函数定义,既不出错,又返回(string | number)[]。有什么想法吗?

function keyableValues<T extends Record<any, keyof any>>(o: T): T[keyof T][] {
    const values: T[keyof T][] = [];
    for (const key of Object.getOwnPropertyNames(o)) {
        values.push(o[key as keyof T]);
    }
    return values;
};

interface O {
    a: string;
    b: number;
}
let asInterface: O = { a: 'hi', b: 2 };
const notAsInterface = { a: 'hi', b: 2 };

keyableValues(asInterface);    // <- typing error, returns (string | number | symbol)[]
keyableValues(notAsInterface); // <- no error, returns (string | number)[]

倒数第二行的错误是:

“O”类型的参数不能分配给“记录”类型的参数。

类型“O”中缺少索引签名。(2345)

Here it is in the typescript playground.

编辑 请注意,这是一个简化的示例。保持对可分配给keyof any 的值的限制非常重要。我真正的用例是将集合中的值映射到新对象的键的函数:

function mapAsKeys<T extends Record<any, keyof any>, V>(
  object: T,
  iteratee: ObjectIteratee<T, V>,
): Record<T[keyof T], V>;

【问题讨论】:

标签: typescript


【解决方案1】:

无论好坏,接口类型都没有隐式索引签名,而类型别名却可以。有关详细信息,请参阅相关的 GitHub 问题microsoft/TypeScript#15300

我的建议是改用自引用泛型约束,T extends Record&lt;keyof T, ...&gt;,对于任何对象类型,无论是接口还是类型别名,都应该是这样。像这样:

function keyableValues<T extends Record<keyof T, keyof any>>(o: T): T[keyof T][] {
    const values: T[keyof T][] = [];
    for (const key of Object.getOwnPropertyNames(o)) {
        values.push(o[key as keyof T]);
    }
    return values;
};

这应该可以解决问题:

keyableValues(asInterface);    // <- no error, returns (string | number)[]
keyableValues(notAsInterface); // <- no error, returns (string | number)[]

好的,希望对您有所帮助。祝你好运!

Link to code

【讨论】:

  • 太棒了!这正是我所需要的。非常不直观......我永远不会认为这是合法的,更不用说有用了。谢谢!
【解决方案2】:

您不需要为此使用Recordkeyof 就够了。

function keyableValues<T>(o: T): T[keyof T][] {
    const values: T[keyof T][] = [];
    for (const key of Object.getOwnPropertyNames(o)) {
        values.push(o[key as keyof T]);
    }
    return values;
};

interface O {
    a: string;
    b: number;
}
let asInterface: O = { a: 'hi', b: 2 };

let result = keyableValues(asInterface);

那么result的类型推断为(string | number)[]

【讨论】:

  • 感谢您的关注!此处的不同之处在于您已取消对可分配给keyof any 的值的限制。在我努力制作一个简化的例子时,我根本没有明确这一需求。对于那个很抱歉!我会更新问题。
  • 啊,在这种情况下会出现问题,因为您要为数组元素推断的类型 - (string | number) - 不能用作对象中键的类型,即使 stringnumber 每个都是单独的。尝试写type X = { [k: string|number]: any },你会看到它给出了一个错误:“索引签名参数类型不能是联合类型。考虑使用映射对象类型。”
  • 我没有听从你的建议,还没有考虑使用映射对象类型。你能详细说明吗?它是否解释了为什么不实现接口的那个可以正常工作?
  • 这不是我的建议;当您尝试使用 string|number 作为键类型时,这只是 Typescript 的错误消息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-12-26
  • 2016-06-23
  • 1970-01-01
  • 1970-01-01
  • 2015-08-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多