为什么 TypeScript 有时只能通过字符串索引对象

Why can TypeScript only Sometimes Index an Object by a String

为什么当字符串是常量或简单的字符串变量时 TypeScript 可以通过字符串索引类型化对象,但如果字符串是从数组中提取出来的,它就无法通过字符串索引类型化对象

也就是考虑下面的代码

class Foo {
    public bar: string = 'hello';

    public test() {
        // this works
        console.log(this['bar'])

        // this also works
        const index = 'bar';
        console.log(this[index])

        // in both cases above I have successfully used
        // a string as an index for my type Foo

        // However, this doesn't work
        const props:string[] = ['bar']
        for(const [key,value] of props.entries()) {
            console.log(value); // prints 'bar' to terminal/console
            console.log(this[value])
        }

        // Nor does this
        for(let i=0;i<props.length;i++) {
            console.log(this[props[i]])
        }

        // when looping over an array of string and trying to use the
        // string to index the object, I get the following error
        // why.ts:20:25 - error TS7053: Element implicitly has an 'any'
        // type because expression of type 'string' can't be used to
        // index type 'Foo'.
    }
}

const foo = new Foo;
foo.test()


class Foo {
    public bar: string = 'hello';

    public test() {
        // this works
        console.log(this['bar'])

        // this also works
        const index = 'bar';
        console.log(this[index])

        // in both cases above I have successfully used
        // a string as an index for my type Foo

        // However, this doesn't work
        const props:string[] = ['bar']
        for(const [key,value] of props.entries()) {
            console.log(value); // prints 'bar' to terminal/console
            console.log(this[value])
        }

        // Nor does this
        for(let i=0;i<props.length;i++) {
            console.log(this[props[i]])
        }

        // when looping over an array of string and trying to use the
        // string to index the object, I get the following error
        // why.ts:20:25 - error TS7053: Element implicitly has an 'any'
        // type because expression of type 'string' can't be used to
        // index type 'Foo'.
    }
}

const foo = new Foo;
foo.test()

这两个都有效。

console.log(this['bar'])
//...
const index = 'bar';
console.log(this[index])    

TypeScript 能够通过字符串为我的对象编制索引。

但是,后面的例子中我在字符串数组上循环

const props:string[] = ['bar']
for(const [key,value] of props.entries()) {
    console.log(value); // prints 'bar' to terminal/console
    console.log(this[value])
}

for(let i=0;i<props.length;i++) {
    console.log(this[props[i]])
}            

不会 run/compile。我收到以下错误。

why.ts:42:17 - error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Foo'.
  No index signature with a parameter of type 'string' was found on type 'Foo'.

42     console.log(foo[value])

所以这个错误信息——expression of type 'string' can't be used to index type 'Foo' seems to 运行 counter我的前两个例子。

这是怎么回事?帮助一个可怜的动态语言程序员理解 TypeScript 试图告诉我什么。一个示例的加分点,它实际上允许我迭代一个字符串数组并将一个字符串用作对象索引。

答案很简单,如果typescript可以证明访问是安全的,那么索引是允许的。

当你写 this['bar'] 时,打字稿会看到字符串文字,它可以简单地检查 this 是否有 属性 bar

当你写const index = 'bar';时你可能认为index的类型是string但实际上不是,index的类型是string literal type 'bar',所以打字稿会知道 index 中唯一可能的值是 'bar'。由于 index 只能保留 bar,typescript 可以通过检查 this 是否具有 属性 bar

来检查访问 this[index] 是否有效

当您编写 const props:string[] 时,打字稿不会对 props 进行任何其他推断,它是 string 的数组。这意味着当您访问 this[prop] 时,打字稿需要确保 this 可以被任何 string 索引,因为它没有索引签名,所以它不是,因此访问会引发错误.如果您使用 as const 让 ts 推断数组的文字类型而不是 string 并删除显式注释,您将能够执行索引访问:

const props = ['bar'] as const
for(const [key,value] of props.entries()) {
    console.log(value); 
    console.log(this[value])//ok
}

for(let i=0;i<props.length;i++) {
    console.log(this[props[i]])
}

Playground Link

如果您确定 propthis 的键,您也可以使用类型断言:

const props = ['bar']
for(const [key,value] of props.entries()) {
    console.log(this[value as keyof this])
}

或者,如果你真的想变得很花哨,你可以使用自定义类型保护或自定义类型断言,但这在这里似乎有点过分了。