You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何TypeScript使用动态属性名访问嵌套对象时丢失类型推断?

TypeScript动态访问嵌套属性时类型推断丢失的问题与解决

问题场景

我需要基于输入参数访问theme对象的深层嵌套属性,同时给开发者提供完整的自动补全体验。但在函数体内通过动态属性名访问时,TypeScript丢失了类型推断,导致访问breakpoints.base报错:

const theme = {
  typography: {
    text: {
      small: {
        base: "",
        mobile: "",
        desktop: "",
      },
      medium: {
        base: "",
        mobile: "",
        desktop: "",
      },
    },
    heading: {
      h1: {
        base: "",
        mobile: "",
        desktop: "",
      },
      h2: {
        base: "",
        mobile: "",
        desktop: "",
      },
    },
  },
} as const;

type Theme = typeof theme;

function createVariant<T extends keyof Theme["typography"], S extends keyof Theme["typography"][T]>(
  variantName: T,
  variantValue: S,
) {
  const breakpoints = theme.typography[variantName][variantValue];
  const base = breakpoints.base; // 此处报错:类型“Theme["typography"][T][S]”上不存在属性“base”
}

const result = createVariant("heading", "h2");

如果改用显式类型注解ThemeAnnotation,函数内部能识别嵌套属性,但调用端会丢失variantValue的自动补全:

type ThemeAnnotation = {
  typography: {
    text: {
      [index: string]: Record<"base" | "mobile" | "desktop", string>;
    },
    heading: {
      [index:string]: Record<"base" | "mobile" | "desktop", string>;
    }
  }
}

const theme: ThemeAnnotation = { /* ... */ } 

问题原因

  1. 原代码中theme用as const断言后,所有嵌套属性的类型都会被推断为字面量类型(比如"h1"、"h2"而非string),但在泛型函数内部,TypeScript无法跟踪variantName(类型T)和variantValue(类型S)的关联关系——它只知道S是Theme["typography"][T]的键,但无法确认所有T和S的组合对应的子对象都包含base属性,因此无法推断breakpoints的具体结构。
  2. 显式注解ThemeAnnotation时,因为使用了字符串索引签名[index: string],TypeScript会丢失具体的键名信息,导致调用端无法自动补全variantValue的可选值。

解决方案

使用TypeScript 4.9+新增的satisfies关键字,既能确保theme符合预设的结构类型,又能保留原字面量类型的自动补全能力:

// 定义所有断点配置的公共类型
type BreakpointConfig = {
  base: string;
  mobile: string;
  desktop: string;
};

// 用satisfies约束theme的结构,同时保留字面量类型
const theme = {
  typography: {
    text: {
      small: { base: "", mobile: "", desktop: "" },
      medium: { base: "", mobile: "", desktop: "" },
    },
    heading: {
      h1: { base: "", mobile: "", desktop: "" },
      h2: { base: "", mobile: "", desktop: "" },
    },
  },
} as const satisfies {
  typography: {
    text: Record<string, BreakpointConfig>;
    heading: Record<string, BreakpointConfig>;
  };
};

type Theme = typeof theme;

function createVariant<T extends keyof Theme["typography"], S extends keyof Theme["typography"][T]>(
  variantName: T,
  variantValue: S,
) {
  const breakpoints = theme.typography[variantName][variantValue];
  const base = breakpoints.base; // 不再报错,类型推断正常
}

// 调用端自动补全正常
const result = createVariant("heading", "h2");

原理说明

satisfies的作用是验证对象是否符合指定类型,但不会改变对象的原始推断类型。这样:

  • 函数内部能通过BreakpointConfig的约束,确认breakpoints包含base等属性;
  • 调用端能保留theme的字面量类型,因此variantName和variantValue都能提供完整的自动补全。

替代方案(无satisfies时)

如果使用TypeScript 4.9以下版本,可以通过类型断言结合公共类型解决:

type BreakpointConfig = {
  base: string;
  mobile: string;
  desktop: string;
};

const theme = { /* 同原代码 */ } as const;
type Theme = typeof theme;

function createVariant<T extends keyof Theme["typography"], S extends keyof Theme["typography"][T]>(
  variantName: T,
  variantValue: S,
) {
  // 断言为公共类型,确保TypeScript识别到base属性
  const breakpoints = theme.typography[variantName][variantValue] as BreakpointConfig;
  const base = breakpoints.base;
}

内容的提问来源于stack exchange,提问作者wolvesback019

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 11:44:53