动态访问同一对象中两个不同值时出现TypeScript错误
TypeScript动态索引对象时的类型错误解决方案
错误信息
Element implicitly has an 'any' type because expression of type 'levelTypes | semanticTypes' can't be used to index type '{ level1: { highEmphasis: string; mediumEmphasis: string; outlineHighEmphasis: string; outlineMediumEmphasis: string; semantic: { info: string; onInfo: string; negative: string; onNegative: string; ... 12 more ...; AIGradientEndpoint: string; }; interactive: { ...; }; }; ... 15 more ...; aiIntense: { ...; }; } | { ....'. Property 'level1' does not exist on type '{ level1: { highEmphasis: string; mediumEmphasis: string; outlineHighEmphasis: string; outlineMediumEmphasis: string; semantic: { info: string; onInfo: string; negative: string; onNegative: string; ... 12 more ...; AIGradientEndpoint: string; }; interactive: { ...; }; }; ... 15 more ...; aiIntense: { ...; }; } | { ....'.ts(7053)
问题场景
构建组件库时,动态访问调色板对象的双层键触发上述错误,出错代码片段:
theme.fdsTheme.palette[getForeground(level)][level].interactive.highEmphasisSurface
完整组件代码:
import styled from "@emotion/styled"; type levelTypes = | "level1" | "level2" | "level3" | "level4" | "level5" | "level6" | "level7"; type semanticTypes = | "negative" | "negativeIntense" | "critical" | "criticalIntense" | "positive" | "positiveIntense" | "info" | "infoIntense" | "attention" | "attentionIntense" | "ai" | "aiIntense"; interface ButtonStyleProps { level: levelTypes | semanticTypes; } const isNavigation = (level: levelTypes | semanticTypes): boolean => { return level === "level6" || level === "level7"; }; const getForeground = (level: levelTypes | semanticTypes)=> { if (isNavigation(level)) { return "onNavigation"; } else { return "onSurface"; } }; const StyledButton = styled(BaseButton)<ButtonStyleProps>(({ theme, level }) => { return { [`&.${buttonClasses.root}`]: { // 其他样式... "&.fds-Button-primary": { backgroundColor: theme.fdsTheme.palette[getForeground(level)][level].interactive.highEmphasisSurface, color: theme.fdsTheme.palette[getForeground(level)][level].interactive.onHighEmphasis, border: `0px solid ${ theme.fdsTheme.palette[getForeground(level)][level].interactive.highEmphasisOutline }`, }, }, }; });
已尝试的方法
尝试定义Theme类型并使用类型断言,但问题未解决:
扩展Emotion的Theme类型
// 定义自定义Theme类型 import "@emotion/react"; import { Theme } from "../theme"; type DefaultTheme = typeof Theme; declare module "@emotion/react" { export interface Theme extends DefaultTheme {} }
使用类型断言修改组件代码
import { Theme } from "@emotion/react"; // 之前的代码 ... const StyledButton = styled(BaseButton)<ButtonStyleProps>(({ theme, level }) => { return { [`&.${buttonClasses.root}`]: { // 其他样式... "&.fds-Button-primary": { backgroundColor: theme.fdsTheme.palette[getForeground(level) as keyof Theme][level as keyof Theme].interactive.highEmphasisSurface, }, }, }; });
简化示例
为便于定位问题,提供简化后的测试代码,错误出现在theme[checkLevel(levelTwo)][levelTwo].color;行:
interface TestProps { levelOne: "color" | "typo"; levelTwo: "level1" | "level2" | "level3"; } function TestComponent({ levelOne, levelTwo }: TestProps) { const theme = { color: { level1: { color: "", }, level2: { color: "", }, }, typo: { level1: { color: "", }, level2: { color: "", }, level3: { color: "", }, }, }; const checkLevel = ( level: "level1" | "level2" | "level3", ): "color" | "typo" => { return level === "level1" || level === "level2" ? "color" : "typo"; }; const c = theme[checkLevel(levelTwo)][levelTwo].color; console.log(c); }
解决方案
原因分析
TypeScript无法推断getForeground(level)返回的键与level之间的关联关系,导致认为level可能不存在于theme.fdsTheme.palette[getForeground(level)]对应的对象中,从而抛出索引类型错误。
方案1:使用类型守卫缩小范围
通过类型守卫明确level与返回的前景键之间的对应关系,让TypeScript能准确推断类型:
针对原组件代码修改:
// 定义类型守卫 const isNavigationLevel = (level: levelTypes | semanticTypes): level is "level6" | "level7" => { return level === "level6" || level === "level7"; }; const StyledButton = styled(BaseButton)<ButtonStyleProps>(({ theme, level }) => { let bgColor: string; let textColor: string; let borderColor: string; if (isNavigationLevel(level)) { const palette = theme.fdsTheme.palette.onNavigation[level].interactive; bgColor = palette.highEmphasisSurface; textColor = palette.onHighEmphasis; borderColor = palette.highEmphasisOutline; } else { const palette = theme.fdsTheme.palette.onSurface[level].interactive; bgColor = palette.highEmphasisSurface; textColor = palette.onHighEmphasis; borderColor = palette.highEmphasisOutline; } return { [`&.${buttonClasses.root}`]: { "&.fds-Button-primary": { backgroundColor: bgColor, color: textColor, border: `0px solid ${borderColor}`, }, }, }; });
方案2:使用泛型明确键关联
通过泛型函数让TypeScript明确返回键与输入level的对应关系:
针对原组件代码修改:
// 定义泛型版本的getForeground type NavigationLevels = "level6" | "level7"; type NonNavigationLevels = Exclude<levelTypes | semanticTypes, NavigationLevels>; const getForeground = <T extends levelTypes | semanticTypes>(level: T): T extends NavigationLevels ? "onNavigation" : "onSurface" => { return isNavigation(level) ? "onNavigation" : "onSurface" as any; }; const StyledButton = styled(BaseButton)<ButtonStyleProps>(({ theme, level }) => { return { [`&.${buttonClasses.root}`]: { "&.fds-Button-primary": { backgroundColor: theme.fdsTheme.palette[getForeground(level)][level].interactive.highEmphasisSurface, color: theme.fdsTheme.palette[getForeground(level)][level].interactive.onHighEmphasis, border: `0px solid ${ theme.fdsTheme.palette[getForeground(level)][level].interactive.highEmphasisOutline }`, }, }, }; });
方案3:重构调色板类型定义
如果有权限修改Theme的类型定义,可以明确palette中每个键对应的子键类型:
// 定义调色板的类型 interface Palette { onSurface: Record<NonNavigationLevels, { interactive: { highEmphasisSurface: string; onHighEmphasis: string; highEmphasisOutline: string; } }>; onNavigation: Record<NavigationLevels, { interactive: { highEmphasisSurface: string; onHighEmphasis: string; highEmphasisOutline: string; } }>; } // 在Theme类型中使用这个Palette接口 interface Theme { fdsTheme: { palette: Palette; } }
这样TypeScript就能准确判断onSurface和onNavigation对应的子键范围,避免索引错误。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

