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

如何基于Entity与Collection接口实现实体指定路径值自增的辅助函数?

实现类型安全的Collection实体指定路径自增辅助函数

首先先把你给出的接口定义整理好,同时补充EntityId的基础类型定义(原接口里用到了但未明确):

/** 实体接口 */
export interface Entity extends Object { 
  readonly id: EntityId; 
}

/** 基于集合的状态接口 */
export interface Collection<T extends Entity> extends Object { 
  readonly entities: { [key: string]: T }; 
  readonly ids: EntityId[]; 
}

// 定义EntityId为字符串或数字类型,适配常见的ID场景
export type EntityId = string | number;

接下来,我们要实现的辅助函数需要满足两个核心要求:

  • 能沿着指定路径找到目标实体的数值字段并自增
  • 严格的类型安全:只允许传入指向number类型字段的路径,且路径必须属于实体的子属性

第一步:定义类型工具实现路径校验

首先需要写一个递归类型,用来自动提取实体中所有值为number类型的属性路径,这样TypeScript就能在编译期帮我们校验路径的合法性:

// 递归提取实体中所有值为number类型的属性路径
type DeepNumberPath<T> = T extends number 
  ? []
  : {
      [K in keyof T]: K extends string 
        ? [...DeepNumberPath<T[K]>, K] extends infer P 
          ? P extends any[] 
            ? P[number] extends never 
              ? never 
              : P
            : never
          : never
        : never
    }[keyof T];

// 辅助类型:反转路径数组,方便从实体根节点开始遍历到目标字段
type ReversePath<T> = T extends [...infer Rest, infer Last] ? [Last, ...ReversePath<Rest>] : [];

第二步:实现核心的increment函数

因为你的接口里都是readonly属性,所以我们遵循不可变原则——不修改原集合,而是返回一个新的更新后的集合:

export function increment<T extends Entity>(
  collection: Collection<T>,
  entityId: EntityId,
  path: DeepNumberPath<T>
): Collection<T> {
  const entityKey = String(entityId);
  const targetEntity = collection.entities[entityKey];

  // 如果指定ID的实体不存在,直接返回原集合
  if (!targetEntity) {
    return collection;
  }

  // 递归创建更新后的实体对象(保证不可变性)
  const updateNestedField = (obj: any, pathSegments: any[]): any => {
    if (pathSegments.length === 1) {
      return { ...obj, [pathSegments[0]]: obj[pathSegments[0]] + 1 };
    }
    const [currentSegment, ...remainingSegments] = pathSegments;
    return {
      ...obj,
      [currentSegment]: updateNestedField(obj[currentSegment], remainingSegments)
    };
  };

  // 反转路径,从根节点开始遍历更新
  const reversedPath = path.reverse() as ReversePath<DeepNumberPath<T>>;
  const updatedEntity = updateNestedField(targetEntity, reversedPath);

  // 返回全新的Collection实例
  return {
    ...collection,
    entities: {
      ...collection.entities,
      [entityKey]: updatedEntity
    }
  };
}

第三步:使用示例和类型安全验证

我们用一个具体的User实体来测试这个函数,看看类型安全的效果:

// 示例实体:继承自Entity,包含多层嵌套的数字字段和非数字字段
interface User extends Entity {
  stats: {
    posts: number;
    likes: number;
    profile: {
      views: number;
    };
  };
  name: string; // 非数字类型,路径会被TypeScript拦截
}

// 创建测试用的Collection
const userCollection: Collection<User> = {
  entities: {
    'user-1': {
      id: 'user-1',
      stats: {
        posts: 5,
        likes: 20,
        profile: { views: 100 }
      },
      name: 'Alice'
    }
  },
  ids: ['user-1']
};

// ✅ 合法调用:路径指向数字类型字段,编译通过
const updatedPosts = increment(userCollection, 'user-1', ['stats', 'posts']);
console.log(updatedPosts.entities['user-1'].stats.posts); // 输出6

// ✅ 合法调用:深层嵌套的数字字段路径
const updatedViews = increment(userCollection, 'user-1', ['stats', 'profile', 'views']);
console.log(updatedViews.entities['user-1'].stats.profile.views); // 输出101

// ❌ 类型错误:路径指向非数字类型(name是string),编译期直接报错
// increment(userCollection, 'user-1', ['name']);

这个实现完全满足你的需求:它会自动校验路径对应的字段是否为number类型,同时通过不可变更新的方式符合接口的readonly约束。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 12:05:57