TypeScript中使用括号表示法通过变量访问对象属性的问题
这个问题我碰到过好几次了,本质是TypeScript对Object.keys的类型推断和自定义接口之间的类型安全冲突。咱们先拆解下原因:
Object.keys()返回的类型是string[],而你的IUser接口的键是明确的字面量类型("username"、"namespace"、"password")。TypeScript出于严谨性,不会默认认为string类型的变量一定是IUser的合法键,所以就抛出了“无法用string类型索引IUser”的错误。
而直接写tmpUser["username"]或者用提前定义的someKey(如果someKey是字面量类型而非宽泛的string)能正常运行,是因为这些值被TS明确推断为IUser的合法键类型,不存在类型歧义。
至于tmpUser.hasOwnProperty(property)没用,是因为它只是运行时的存在性检查,没法帮TypeScript在编译阶段缩小property的类型范围,所以编译错误依然存在。
下面给你几个实用的解决办法:
方法1:类型断言Object.keys的结果
最直接的方式是把Object.keys返回的数组断言为IUser的键数组,告诉TS这些字符串都是接口的合法键:
interface IUser { username: string; namespace: string; password: string; } const tmpUser: IUser = { username: "test", namespace: "test", password: "test" }; // 断言为keyof IUser的数组 (Object.keys(tmpUser) as Array<keyof IUser>).forEach(property => { console.log(tmpUser[property]); // 编译通过,正常运行 });
方法2:自定义类型守卫
如果想更严谨地做类型检查(避免断言可能带来的风险),可以写一个类型守卫函数,在运行时验证键的合法性,同时让TS在编译阶段识别合法键:
interface IUser { username: string; namespace: string; password: string; } // 类型守卫:判断key是否是IUser的键 function isUserKey(key: string): key is keyof IUser { return ["username", "namespace", "password"].includes(key); } const tmpUser: IUser = { username: "test", namespace: "test", password: "test" }; Object.keys(tmpUser).forEach(property => { if (isUserKey(property)) { // 进入这个分支后,TS知道property是IUser的合法键 console.log(tmpUser[property]); } else { // 可以处理非法键的情况(如果有需要) console.warn(`Unknown property: ${property}`); } });
方法3:使用Object.hasOwn(TS 4.9+)
如果你使用的是TypeScript 4.9及以上版本,可以结合Object.hasOwn(替代hasOwnProperty,更安全)和类型缩小来解决:
interface IUser { username: string; namespace: string; password: string; } const tmpUser: IUser = { username: "test", namespace: "test", password: "test" }; Object.keys(tmpUser).forEach(property => { if (Object.hasOwn(tmpUser, property)) { // 这里需要额外断言,因为Object.hasOwn没法直接缩小类型 console.log(tmpUser[property as keyof IUser]); } });
不过这个方法还是需要配合断言,所以前两种方法会更实用。
内容的提问来源于stack exchange,提问作者Jax-p

