I have template names enum and for each enum value function fetchTemplate
must return it's own response type.
enum KeyEnum {
VK = 'VK',
VIBER = 'VIBER',
}
type VkResponse = { vk: string };
type ViberResponse = { viber: number };
type ReturnTypes = {
[KeyEnum.VK]: VkResponse;
[KeyEnum.VIBER]: ViberResponse;
};
I have created function fetchTemplate
with this signature:
const fetchTemplate = <T extends KeyEnum>(key: T): ReturnTypes[T]
And with this body :
const fetchTemplate = <T extends KeyEnum>(key: T): ReturnTypes[T] => {
if (key === KeyEnum.VK) {
return { vk: 'someString' }; <<- Here i got TS2322 error (description below)
}
if (key === KeyEnum.VIBER) {
return { viber: 1 } as ReturnTypes[T]; <<- No problems, but i dont want use "as"
}
};
# Unnecessary type definition in the left, just for testing
const vkRes: VkResponse = fetchTemplate(KeyEnum.VK);
const viberRes: ViberResponse = fetchTemplate(KeyEnum.VIBER);
But in the function body i can't just return {vk: 'someString'}
or {viber:1}
, because i got TS2322:
TS2322:
Type { vk: string; } is not assignable to type ReturnTypes[T]
Type { vk: string; } is not assignable to type VkResponse & ViberResponse
Property viber is missing in type { vk: string; } but required in type ViberResponse
Why TypeScript can't do type narrowing in this case? Why i need to use "as ReturnTypes[T]", how to avoid it? How i can correctly narrow key
type from T
to specific .VK
or .VIBER
and explicitly define certain (not union) return type in IFs blocks?
TypeScript v5.2.2
Node v20.8.0
This one also not working:
switch (key) {
case KeyEnum.VIBER:
return { viber: 1 }; <<- The same TS2322 error
}
Currently control-flow based narrowing (like
switch
/case
orif
/else
blocks) does not play nicely with generics. The problem is that while checkingkey === KeyEnum.VK
will narrow the type ofkey
, it will not affect the generic type parameter (T
in your example). This is considered a missing feature, as requested in microsoft/TypeScript#33014. Until and unless that is implemented, you'll need to work around it.For now if you want to use a generic function and return an indexed access type like
ReturnTypes[T]
, you'll need to actually perform an indexing operation: that is, get an object of typeReturnTypes
, and read a property at the key of typeT
. Conceptually for your example that looks like:One possible problem with this is that it requires you to pre-calculate every possible return value. If you call
fetchTemplate(KeyEnum.VK)
, the object will still evaluate{viber: 1}
and then throw it away. For a simple case like this that's probably no big deal, but if your code has side-effects or is expensive to compute, then you could refactor to use getters, so that only the relevant code gets run:Here if you call
fetchTemplate(KeyEnum.VK)
, only the getter forKeyEnum.VK
is never run.Hopefully someday microsoft/TypeScript#33014 will be implemented, since this refactoring, while functional, is potentially confusing.
Playground link to code