【问题标题】:What does a TypeScript index signature actually mean?TypeScript 索引签名实际上意味着什么?
【发布时间】:2020-02-15 21:22:59
【问题描述】:

我写 TypeScript 已经有一段时间了,对索引签名的含义感到困惑。

例如,这段代码是合法的:

function fn(obj: { [x: string]: number }) {
    let n: number = obj.something;
}

但是这个基本上做同样事情的代码不是:

function fn(obj: { [x: string]: number }) {
    let p: { something: number } = obj;
}

这是一个错误吗?这是什么意思?

【问题讨论】:

    标签: typescript index-signature


    【解决方案1】:

    你感到困惑是对的。索引签名意味着几件事,它们的含义略有不同,具体取决于您询问的地点和方式。

    首先,索引签名意味着类型中所有声明的属性都必须具有兼容的类型

    interface NotLegal {
        // Error, 'string' isn't assignable to 'number'
        x: string;
        [key: string]: number;
    }
    

    这是索引签名的定义方面——它们描述具有不同属性键的对象,但所有这些键的类型一致。此规则可防止在通过间接访问属性时观察到不正确的类型:

    function fn(obj: NotLegal) {
        // 'n' would have a 'string' value
        const n: number = obj[String.fromCharCode(120)];
    }
    

    第二,索引签名允许写入具有兼容类型的任何索引

    interface NameMap {
        [name: string]: number;
    }
    function setAge(ageLookup: NameMap, name: string, age: number) {
        ageLookup[name] = age;
    }
    

    这是索引签名的一个关键用例:您有一组键,并且想要存储与该键关联的值。

    第三,索引签名暗示存在特别要求的任何属性:

    interface NameMap {
        [name: string]: number;
    }
    function getMyAge(ageLookup: NameMap) {
        // Inferred return type is 'number'
        return ageLookup["RyanC"];
    }
    

    因为 x["p"]x.p 在 JavaScript 中具有相同的行为,TypeScript 将它们等同对待:

    // Equivalent
    function getMyAge(ageLookup: NameMap) {
        return ageLookup.RyanC;
    }
    

    这与 TypeScript 查看数组的方式一致,即假定数组访问是在边界内的。它对于索引签名也符合人体工程学,因为通常情况下,您有一组已知的可用密钥,并且不需要进行任何额外的检查:

    interface NameMap {
        [name: string]: number;
    }
    function getAges(ageLookup: NameMap) {
        const ages = [];
        for (const k of Object.keys(ageLookup)) {
            ages.push(ageLookup[k]);
        }
        return ages;
    }
    

    然而,索引签名暗示在将具有索引签名的类型与已声明的类型相关联时将出现任何任意的、非特定的属性属性:

    interface Point {
        x: number;
        y: number;
    }
    interface NameMap {
        [name: string]: number;
    }
    const m: NameMap = {};
    // Not OK, which is good, because p.x is undefined
    const p: Point = m;
    

    这种分配在实践中不太可能是正确的!

    这是{ [k: string]: any }any 本身之间的一个显着特征 - 您可以在具有索引签名的对象上读取和写入任何类型的属性,但它不能用于替代任何类型,例如 @ 987654332@可以。

    这些行为中的每一个都是非常合理的,但作为一个整体,一些不一致是可以观察到的。

    例如,这两个函数在运行时行为方面是相同的,但 TypeScript 只认为其中一个被错误调用:

    interface Point {
        x: number;
        y: number;
    }
    interface NameMap {
        [name: string]: number;
    }
    
    function A(x: NameMap) {
        console.log(x.y);
    }
    
    function B(x: Point) {
        console.log(x.y);
    }
    const m: NameMap = { };
    A(m); // OK
    B(m); // Error
    

    总的来说,当你在一个类型上写一个索引签名时,你是在说:

    • 可以用任意键读/写这个对象
    • 当我通过任意键从该对象中读取特定属性时,它始终存在
    • 出于类型兼容性的目的,该对象没有字面上的每个属性名称

    【讨论】:

      猜你喜欢
      • 2021-09-24
      • 2012-01-26
      • 2020-11-30
      • 1970-01-01
      • 2017-06-03
      • 1970-01-01
      • 2019-06-02
      • 1970-01-01
      • 2023-01-03
      相关资源
      最近更新 更多