TypeScript索引签名实际上是什么意思?

时间:2019-10-18 21:17:06

标签: typescript index-signature

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

例如,此代码是合法的:

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

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

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

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

1 个答案:

答案 0 :(得分:9)

您很困惑。索引签名的含义不尽相同,具体取决于您询问的地点和方式。

首先,索引签名暗示该类型中所有声明的属性必须具有兼容的类型

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本身之间的一个区别功能-您可以在具有索引签名的对象上读写任何类型的属性,但是不能代替任何属性键入any可以的任何形式。

这些行为中的每一个都是非常合理的,但是从整体上看,有些矛盾是可以观察到的。

例如,这两个函数在运行时行为方面是相同的,但是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

总体而言,当您在类型上编写索引签名时,您会说:

  • 可以使用任意键读写该对象
  • 当我通过任意键从该对象读取特定属性时,该属性始终存在
  • 出于类型兼容性的目的,此对象实际上并没有每个属性名称