TypeScript中使用变量动态访问对象属性报类型错误如何解决
TypeScript 做静态类型检查时,字符串拼接表达式的推导类型默认是string,编译器无法在编译阶段确认拼接结果一定是Budget接口的合法属性名,因此抛出 Type 'string' is not assignable to type 'keyof Budget' 的类型错误。
直接给budget_month赋值字符串字面量'budget_july'时,TS可以直接识别到该字面量属于Budget的合法键范围,因此不会触发报错。
另外原始代码存在隐藏运行时风险:toLocaleString('default', { month: 'long' })会跟随运行环境的默认语言返回对应语言的月份名,如果环境默认语言不是英文,会返回类似七月的非英文字符串,拼接出来的属性名和接口定义完全不匹配,哪怕绕过TS检查,运行时也会写入错误的属性。
方案1:类型断言(最简洁,适合确定逻辑无错的场景)
直接通过类型断言告诉TS,你确认拼接结果是Budget的合法键,即可绕过类型检查。注意这里最好把toLocaleString的locale固定为en-US,避免环境语言差异导致的运行时错误:
const budgets: Budget[] = [{}]; // 原代码数组初始为空,直接取[0]会报错,需要先初始化第一个元素 const datestr = '2022-07-01'; const monthStr = new Date(datestr).toLocaleString('en-US', { month: 'long' }).toLowerCase(); const budget_month = `budget_${monthStr}` as keyof Budget; budgets[0][budget_month] = 5000;
注意:该方案需要自行保证拼接逻辑的正确性,TS不会做额外校验。
方案2:固定月份映射表(类型安全最高,无运行时风险)
完全放弃依赖toLocaleString的返回值,手动建立JS Date月份索引(0=1月,11=12月)到Budget属性名的固定映射,从根源避免环境差异和拼写错误,TS也可以完整做类型校验:
// 月份索引对应Date.prototype.getMonth()的返回值 const MONTH_BUDGET_MAP: Record<number, keyof Budget> = { 0: 'budget_january', 1: 'budget_february', 2: 'budget_march', 3: 'budget_april', 4: 'budget_may', 5: 'budget_june', 6: 'budget_july', 7: 'budget_august', 8: 'budget_september', 9: 'budget_october', 10: 'budget_november', 11: 'budget_december' }; const budgets: Budget[] = [{}]; const datestr = '2022-07-01'; const dateIns = new Date(datestr); const budget_month = MONTH_BUDGET_MAP[dateIns.getMonth()]; budgets[0][budget_month] = 5000;
这个方案没有任何隐式依赖,不管运行环境是什么语言、什么时区,只要传入的日期合法,就能拿到正确的属性名,是生产环境优先推荐的写法。
方案3:运行时校验(兼顾灵活性和安全性)
如果不想维护固定映射表,可以先把Budget的所有合法键存为集合,拼接完属性名后先做合法性校验,再执行赋值,既保留了动态拼接的灵活性,也能避免非法属性写入:
const VALID_BUDGET_KEYS = new Set([ 'budget_july', 'budget_august', 'budget_september', 'budget_october', 'budget_november', 'budget_december', 'budget_january', 'budget_february', 'budget_march', 'budget_april', 'budget_may', 'budget_june' ] as const); const budgets: Budget[] = [{}]; const datestr = '2022-07-01'; const monthStr = new Date(datestr).toLocaleString('en-US', { month: 'long' }).toLowerCase(); const budgetKey = `budget_${monthStr}`; if (VALID_BUDGET_KEYS.has(budgetKey as keyof Budget)) { budgets[0][budgetKey as keyof Budget] = 5000; } else { throw new Error(`生成的预算属性名非法:${budgetKey}`); }
内容的提问来源于stack exchange,提问作者thethakuri

