TypeScript-详尽检查可索引类型扩展的keyof

收藏

I'm having an issue wherein TypeScript is not exhaustively checking types when the keyof operator is used on types that extend indexable types.

So, the normal use-case of the keyof operator would be something like a Pick<T1, T2> type:

interface Person {
    name: string;
    age: number;
    alive: boolean;
}

function getPersonInfo<K extends keyof Person>(person: Person, prop: K): Person[K] {
    return person[prop];
}

getPersonInfo({ name: "John", age: 31, alive: true }, "age"); // compiles and returns 31
getPersonInfo({ name: "John", age: 31, alive: true }, "ssn"); // does NOT compile because "ssn" does not match type of "name" | "age" | "alive"

But when an indexable type is involved, we no longer get this safety of keyof (i.e. a compile-time error telling us that the provided argument is not a key of the type in question). Such as the following:

interface Collection {
    [item: string]: any;
}

interface MyCollection extends Collection {

    thing: string,
    shmoolean: boolean,
    lumber: number

}

function getInfo<C extends Collection, K extends keyof C = keyof C>(collection: C, key: K): C[K] {

    return collection[key];

}

let c: MyCollection = undefined as unknown as MyCollection;

getInfo<MyCollection>(c, "something"); // compiles even though "something" is not a property of MyCollection

Now, I think I understand why this is occurring, and I believe it is due to the fact that on interfaces that extend indexable types, the 'indexable signature' (if you will) is still present on the type, meaning that the type returned by keyof effectively gets widened to string.

So my question is there any way to prevent this from happening? I need to be able to tag classes with their parent indexable type in order to use them in my type system, so I can't just eliminate the indexable type, but I would also like to be able to exhaustively check the type of provided arguments to keyof types and to be given compile-time errors when a non-key is used.

回复
  • Your Collection interface allows any key. So your MyCollection can contains any key as well.

    If MyCollection would not extend Collection, then only the defined properties would be allowed.

    因此,您需要:

    1)不要使MyCollection扩展Collection

    2)重写您的getInfo泛型以允许类型不扩展Collection

    interface Collection {
        [item: string]: any;
    }
    
    
    interface MyCollection {
    
        thing: string,
        shmoolean: boolean,
        lumber: number
    
    }
    
    function getInfo<C = Collection, K extends keyof C = keyof C>(collection: C, key: K): C[K] {
    
        return collection[key];
    
    }
    
    let c: MyCollection = undefined as unknown as MyCollection;
    
    getInfo<MyCollection>(c, "something"); // Argument of type '"something"' is not assignable to parameter of type '"thing" | "shmoolean" | "lumber"'.