I'm creating a generic function logVisitorInfo<T>
, with two fields. One is named type
, defined as ("registeredUser"
or "anonymous"
). The second is named info
, contains the information of the visitor (ip
, username
).
The generic function logVisitorInfo
will type info
to change based on the value of type
.
But I am having a problem inside logVisitorInfo
function:
type visitorType = {
registeredUser: {
ip: string;
username: string;
};
anonymous: {
ip: string;
}
}
const logVisitorInfo = <T extends keyof visitorType>({ type, info }: {type: T, info: visitorType[T] }) => {
const username = info.username; //error as expected, 'username' not exist on '{ ip: string; }'
if(type === 'registeredUser') { //type is narrowed down to 'registeredUser', username should exists by now?
const username = info.username; //but here still getting the same error
}
}
It seems that type === 'registeredUser'
doesn't do what I'm expecting. How can I avoid that error?
CodePudding user response:
TypeScript won't narrow info
based on the type of type
, which makes sense if you consider this application:
logVisitorInfo<'registeredUser' | 'anonymous'>({type: 'registeredUser', info: {ip: ''}})
Both type
and info
are correctly typed here, but info
doesn't contain username
.
To get the behavior you want, you can create a discriminated union type like this:
type Info = {[K in keyof VisitorType]: {type: K, info: VisitorType[K]}}[keyof VisitorType]
// type Info =
// | { type: 'registeredUser'; info: { ip: string; username: string } }
// | { type: 'anonymous'; info: { ip: string } }
Using Info
, you can write logVisitorInfo
without generics:
const logVisitorInfo = ({ type, info }:Info) => {
const username = info.username; //error as expected, 'username' not exist on '{ ip: string; }'
if(type === 'registeredUser') {
const username = info.username;
}
}
logVisitorInfo({type: 'anonymous', info: {ip: ''}}) // Ok
logVisitorInfo({type: 'registeredUser', info: {ip: ''}}) // Error