У меня есть модуль с объектом, содержащим кеш элементов разных типов, каждый из которых хранится в свойстве объекта кеша:
type Pet = { name: string, petFood: string }
type Human = { firstName: string, lastName: string }
type Cache = { pets: Pet[], humans: Human[] }
const theCache: Cache = {pets: [], humans: []}
У меня есть функция для извлечения данных из кеша.Он принимает ключ, представляющий свойство кэша (т. Е. pets
или humans
), и универсальный тип, указывающий тип данных, которые, как я ожидаю, должно возвращать это свойство (т. Е. Pet
или Human
).Я могу использовать условные типы для обеспечения безопасности типов здесь:
// Alias for all properties of TObj which return a TResult
export type PropertiesOfType<TObj, TResult> = {
[K in keyof TObj]: TObj[K] extends TResult ? K : never
}[keyof TObj]
function readCache<T, K extends PropertiesOfType<Cache, T[]>, C extends { [key in K]: T[] }>(
key: K, cache: C
): T[] {
return cache[key]
}
const pets: Pet[] = readCache("pets", theCache) // Compiles - this is correct
const wrongType: Human[] = readCache("pets", theCache) // Doesn't compile - this is correct
Внутри модуля все работает нормально, так как я могу передать theCache
в функцию readCache
.Тем не менее, theCache
не экспортируется, так как я хочу сохранить его в секрете для модуля.Вместо этого я хочу экспортировать функцию, которую другие модули могут вызывать, чтобы получить доступ только для чтения к данным в кэше, например, что-то вроде этого:
export function getCachedItems<T, K extends PropertiesOfType<Cache, T[]>>(key: K): T[] {
return readCache(key, theCache) // Doesn't compile
}
Проблема в том, что приведенный выше код не компилируется:
TS2345: Argument of type 'Cache' is not assignable to
parameter of type '{ [key in K]: T[]; }'
Есть ли способ получить код для компиляции (без необходимости небезопасного приведения)?
ОБНОВЛЕНИЕ : Тициан Черникова-Драгомир сделалдва отличных предложения:
- Возврат
Cache[K]
.Это хорошо работает для мест, где я хочу вызвать метод и узнать тип, с которым я работаю.Но в случаях, когда у меня есть другая универсальная функция, которая хочет вызвать универсальный getCachedItems
, и знаю, что он возвращает массив T
, это не сработает. - Return
Cache[K][number][]
,Это решает вышеуказанную проблему (и отвечает на оригинальный вопрос).Но это работает только тогда, когда кэш содержит массивы.Что делать, если я хочу, чтобы объект кеша немного отличался (больше похож на словарь или карту в других языках): каждая запись в кеше должна сама быть другим объектом, со свойством для каждого элемента, который она кеширует, где имя свойстваэто некоторый идентификатор этого объекта, , как предложено при проектировании магазина Redux .
Другими словами, у меня будет это:
export type Map<T> = { [key: string]: T }
export type Cache = { pets: Map<Pet>, humans: Map<Human> }
const theCache: Cache = {pets: {}, humans: {}}
function readCache<T, K extends PropertiesOfType<Cache, Map<T>>, C extends { [key in K]: Map<T> }>(
key: K, cache: C
): Map<T> {
return cache[key]
}
const pets: Map<Pet> = readCache("pets", theCache) // Compiles - this is correct
const wrongType: Map<Human> = readCache("pets", theCache) // Doesn't compile - this is correct
export function getCachedItems<T, K extends PropertiesOfType<Cache, Map<T>>>(key: K): Map<T> {
return readCache(key, theCache) // Doesn't compile
}
Так что явсе еще получаю оригинальную проблему таким образом.Если я попытаюсь предложить возвращение C[K]
, это снова сработает, если я знаю тип при вызове, но не в универсальной функции:
function readCache<T, K extends keyof C, C extends { [key in K]: Map<T> }>(
key: K, cache: C
): C[K] {
return cache[key]
}
const pets: Map<Pet> = readCache("pets", theCache) // Compiles - this is correct
const wrongType: Map<Human> = readCache("pets", theCache) // Doesn't compile - this is correct
export function getCachedItems<T, K extends keyof Cache>(key: K): Cache[K] {
return readCache(key, theCache)
}
const pets2: Map<Pet> = getCachedItems("pets") // Compiles - this is correct
const wrongType2: Map<Human> = getCachedItems("pets") // Doesn't compile - this is correct
function callerInAnotherModule<T, K extends keyof Cache>(key: K) {
const expected : Map<T> = getCachedItems(key) // Doesn't compile
}