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

标签 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;
}

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

最佳答案

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

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

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;
}

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

第三,索引签名意味着您特别要求的任何属性的存在:
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

总的来说,当你在一个类型上写一个索引签名时,你是在说:
  • 可以用任意键读/写这个对象
  • 当我通过任意键从该对象读取特定属性时,它总是存在
  • 出于类型兼容性的目的,此对象并没有字面上存在的所有属性名称
  • 关于typescript - TypeScript 索引签名实际上是什么意思?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58458308/

    相关文章:

    typescript - 空对象的索引签名和记录之间的区别?

    typescript - 我们如何在 Nextjs 动态导入中输入forwardRef?

    typescript - 如何使用 DebugSession.customRequest 运行任意 gdb 命令并获取结果

    javascript - 为什么在 canActivate() 之前调用 ngOnInit()?

    typescript - 如何将已知的接口(interface)属性与自定义索引签名相结合?

    实现类中的 typescript 索引签名和方法不起作用

    angular - Angular2 typescript 中的第三方 JS

    string - typescript - 逗号运算符的左侧未使用且没有副作用 - 如何在 hookrouter 的路由中使用常量代替字符串?

    node.js - 如何在 TypeScript 中通过索引访问通用对象的属性?