打字稿:如何将字符串类型映射到具有许多不同子类型的泛型

Typescript: How to map string type to generic with many different subtypes

我正在尝试在本地存储之上创建一个类型化接口,我可以仅使用字符串键以类型化方式从本地存储set/get。我想要将字符串类型映射到泛型,这样我就可以自定义 serialization/deserialization.

我 运行 遇到的问题是,当我使用映射对象从字符串类型转换为通用对象时,Typescript 似乎无法正确推断类型。如果有人对解决此问题的最佳方法有一些好的建议,那就太棒了! ❤️

下面有一个例子可以更好地解释我的情况:

type LocalStorageKey = "bool_test" | "num_test";

type LocalStorageKeyInfo<T> = {
  key: LocalStorageKey;
  getDefault(): T;
  serialize(val: T): string;
  deserialize(lsVal: string | null, defaultVal: T): T;
};

const BooleanSerializer = (val: boolean) => String(val);
const BooleanDeserializer = (lsVal: string | null, defaultVal: boolean) => {
  if (lsVal === null) {
    return defaultVal;
  } else {
    return lsVal === "true";
  }
};

const NumberSerializer = (val: number) => String(val);
const NumberDeserializer = (lsVal: string | null, defaultVal: number) => {
  if (lsVal === null) {
    return defaultVal;
  } else {
    return Number(lsVal);
  }
};

export const BoolTestKeyInfo: LocalStorageKeyInfo<boolean> = {
  key: "bool_test",
  getDefault: () => false,
  serialize: BooleanSerializer,
  deserialize: BooleanDeserializer,
};

export const NumTestKeyInfo: LocalStorageKeyInfo<number> = {
  key: "num_test",
  getDefault: () => 0,
  serialize: NumberSerializer,
  deserialize: NumberDeserializer,
};

const keyInfoMapping = {
  bool_test: BoolTestKeyInfo,
  num_test: NumTestKeyInfo,
};

export function getKeyVal(key: LocalStorageKey) {
  const keyInfo = keyInfoMapping[key];
  try {
    return keyInfo.deserialize(
      window.localStorage.getItem(keyInfo.key),
      // This line is the issue because deserialize's second arg is infered as never
      keyInfo.getDefault()
    );
  } catch (e) {
    return keyInfo.getDefault();
  }
}

// I want x to be a boolean here
const x = getKeyVal("bool_test");

您可以通过向函数添加泛型类型参数以捕获传入的 key 的实际类型,将 x 变为 boolean。但是您仍然会在实现中需要一些类型断言,因为 ts 不能很好地遵循变量之间的相关性。

export function getKeyVal<K extends LocalStorageKey>(key: K) :ReturnType<typeof keyInfoMapping[K]['getDefault']>
export function getKeyVal(key: LocalStorageKey) {
  const keyInfo = keyInfoMapping[key];
  try {
    return keyInfo.deserialize(
      window.localStorage.getItem(keyInfo.key),
      // This line is the issue because deserialize's second arg is infered as never
      keyInfo.getDefault() as never
    );
  } catch (e) {
    return keyInfo.getDefault();
  }
}

Playground Link