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

TypeScript Proxy封装缓存对象赋值报类型错误问题

问题背景

我的应用内置了一套缓存机制,该缓存被Proxy对象包裹,用于实现缓存过期校验逻辑。

以下是Lists.d.ts文件中部分列表类型定义供参考,问题并非出在这部分代码中:

interface MetaData {
  Title: string | null;
  FileSystemObjectType: number;
  Id: number;
  // ....
}

// The CreationProps types just define the fields in the object
type DeviceCreationProps = {
  Model: string;
  Manufacturer: string;
  // ...
};

type CommentData = MetaData & CommentCreationProps;
type DeviceData = MetaData & DeviceCreationProps;
type UserData = MetaData & UserCreationProps;

type ListDataMap = {
  Comments: CommentData;
  Devices: DeviceData;
  Users: UserData;
};

type ListName = keyof ListDataMap;

ListItemCache.ts文件中的代码如下:

type CacheExpirationMapValue = {
  // [list name] : expiration date object
  [List in ListName]?: Date;
};
const CacheExpirationMap: CacheExpirationMapValue = {};

type RawListItemCacheValue = {
  // [name of list]: array of list items
  [List in ListName]?: ListDataMap[List][];
};
const RawListItemCache: RawListItemCacheValue = {};

const maxAge = 1000 * 60 * 5;

// This is a proxied object that allows us to handle the expirations when fetching cached objects
const ListItemCache = new Proxy(RawListItemCache, {
  set<List extends ListName>(obj, prop: List, value: ListDataMap[List][]) {
    const expires = new Date(Date.now() + maxAge);

    CacheExpirationMap[prop] = expires;

    obj[prop] = value;

    return true;
  },

  get(obj, prop) {
    const item = obj[prop];
    const now = new Date();

    if (item && !!CacheExpirationMap[prop] && CacheExpirationMap[prop] > now) {
      return item;
    }

    return [];
  },
});

export default ListItemCache;

该文件内代码无任何类型报错,但在其他文件中尝试为缓存赋值时,例如编写如下代码:

export default function usePopulateCache<List extends ListName>(list: List) {
  const { sp } = useContext(SPContext);
  useEffect(() => {
    sp.web.lists
      .getByTitle(list)
      .items<ListDataMap[List][]>()
      .then((items) => {
        ListItemCache[list] = items;
      });
  }, []);
}

赋值语句处出现类型报错,报错截图如下:
类型报错截图

无法定位报错原因:用于索引两侧类型的List泛型完全一致,按照类型定义,RawListItemCacheValue[List]本应与ListDataMap[List][]类型完全匹配。

报错原因

报错核心是TypeScript对Proxy对象的默认类型推导没有保留自定义的泛型关联规则:

  1. 在set捕获器上单独声明的<List extends ListName>泛型,仅在Proxy内部的handler逻辑中生效,TS不会把这个泛型规则推导到最终导出的ListItemCache变量类型上
  2. 外部调用ListItemCache[list] = items时,TS识别到的ListItemCache是默认Proxy实例类型,它的索引赋值参数类型是所有可能value类型的联合,当用泛型List extends ListName做索引时,TS无法证明传入的items类型和对应key要求的value类型严格匹配,就会抛出类型不兼容错误。
修复方案

显式给ListItemCache标注类型,跳过TS对Proxy实例的默认推导,直接使用提前定义好的RawListItemCacheValue作为对外暴露的类型即可:

// 显式标注变量类型,外部访问时直接按照RawListItemCacheValue做类型校验
const ListItemCache: RawListItemCacheValue = new Proxy(RawListItemCache, {
  set(obj, prop: ListName, value) {
    const expires = new Date(Date.now() + maxAge);
    CacheExpirationMap[prop] = expires;
    // 此处类型强转是因为TS在handler内部无法追踪动态索引的类型关联,逻辑上是类型安全的
    (obj as any)[prop] = value;
    return true;
  },

  get(obj, prop: ListName) {
    const item = (obj as any)[prop];
    const now = new Date();
    if (item && !!CacheExpirationMap[prop] && CacheExpirationMap[prop] > now) {
      return item;
    }
    return [];
  },
});

修改后外部赋值语句的类型报错会直接消失,内部缓存过期校验逻辑不受任何影响。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 04:27:26