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

基于嵌套键数组的TypeScript返回类型实现求助

解决方案

1. 基础类型定义

先明确实体类型,将未通过include获取的属性标记为可选:

type Brand = {
  id: string;
  name: string;
};

type Product = {
  id: string;
  title: string;
  price: number;
};

type CartItem = {
  id: string;
  quantity: number;
  brand?: Brand; // 未include时可选
  product?: Product; // 未include时可选
};

type Cart = {
  id: string;
  total: number;
  items?: CartItem[]; // 未include时可选
};

2. 核心类型工具

实现递归类型工具,解析include路径并将对应嵌套属性转为必选:

// 单一路径转换:将对象指定路径的属性转为必选
type DeepRequiredByPath<T, Path extends string> = 
  Path extends `${infer Key}.${infer Rest}`
    ? Key extends keyof T
      ? {
          [K in keyof T]: K extends Key
            ? T[K] extends Array<infer U>
              ? Array<DeepRequiredByPath<U, Rest>> // 处理数组嵌套场景
              : DeepRequiredByPath<NonNullable<T[K]>, Rest> // 处理对象嵌套场景
            : T[K]
        } & Required<Pick<T, Key>> // 标记当前层级属性为必选
      : T
    : Path extends keyof T
      ? Required<Pick<T, Path>> & Omit<T, Path> // 顶级属性直接转为必选
      : T;

// 多路径合并:依次应用所有路径的转换逻辑
type DeepRequiredByPaths<T, Paths extends string[]> = 
  Paths extends [infer First, ...infer Rest]
    ? First extends string
      ? Rest extends string[]
        ? DeepRequiredByPaths<DeepRequiredByPath<T, First>, Rest>
        : T
      : T
    : T;

3. 约束include参数(可选但推荐)

添加类型约束,确保用户只能传入实体的有效嵌套路径,避免无效输入:

// 生成实体的所有有效嵌套路径
type ValidPaths<T, Prefix extends string = ''> = {
  [K in keyof T]: T[K] extends Array<infer U>
    ? `${Prefix}${K}` | ValidPaths<U, `${Prefix}${K}.`>
    : T[K] extends object
      ? `${Prefix}${K}` | ValidPaths<T[K], `${Prefix}${K}.`>
      : `${Prefix}${K}`
}[keyof T];

4. 实现retrieve函数

利用上述类型工具,让函数根据include参数自动推断返回类型:

async function retrieve<Include extends ValidPaths<Cart>[] = []>(
  include?: Include
): Promise<DeepRequiredByPaths<Cart, Include>> {
  // 实际API请求逻辑示例
  const response = await fetch('/api/cart', {
    method: 'GET',
    headers: { 'Content-Type': 'application/json' },
    body: include ? JSON.stringify({ include }) : undefined
  });
  return response.json();
}

5. 类型校验测试

以下场景会自动触发TypeScript类型校验:

(async () => {
  // 无include:items为可选属性
  const cart1 = await retrieve();
  cart1.items; // 类型:CartItem[] | undefined

  // include ["items"]:items转为必选,但其子属性仍为可选
  const cart2 = await retrieve(["items"]);
  cart2.items; // 类型:CartItem[](必选)
  cart2.items[0].brand; // 类型:Brand | undefined

  // include ["items.brand", "items.product"]:子属性也转为必选
  const cart3 = await retrieve(["items.brand", "items.product"]);
  cart3.items[0].brand.name; // 类型安全,无报错
  cart3.items[0].product.price; // 类型安全,无报错

  // 传入无效路径会直接报错:类型'"invalid.path"'不能赋值给ValidPaths<Cart>类型
  // const cart4 = await retrieve(["invalid.path"]);
})();

内容的提问来源于stack exchange,提问作者Axel DS.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 00:48:39