I have two types: Action
and ActionEmitter
. The latter is inferred from the Action
type and appends the string literal 'Clicked'
.
I am trying to create a function that takes an Action
as an arg and returns an ActionEmitter
.
I can make this work with a type guard, but seeing as I already know I have an Action
I would prefer to be able to infer that a valid ActionEmitter
is created when I concatenate the string 'Clicked' to my action argument.
Here is my code:
type Action = 'next' | 'previous';
type ActionEmitter = Action extends `${infer u}` ? `${u}Clicked` : never;
function isActionEmitter(s: string | ActionEmitter): s is ActionEmitter {
switch (s) {
case 'previousClicked':
case 'nextClicked':
return true;
default:
return false;
}
}
// this works but feels unnecessary
function createActionEmitterGood(action: Action): ActionEmitter | null {
const emitter = action 'Clicked';
return (isActionEmitter(emitter) && emitter) || null;
}
// fails... is this even possible without casting?
function createActionEmitterBad(action: Action): ActionEmitter | null {
const clicked = 'Clicked' as const;
// Type 'string' is not assignable to type '"nextClicked" | "previousClicked"'
return action clicked;
}
Is what I'm trying to do in the second function - createActionEmitterBad
- possible without a type guard or casting to ActionEmitter
?
CodePudding user response:
You need to use string interpolation.
return `${action}${clicked}`;
Typescript doesn't treat the
operator for joining strings with the same type safety. Probably because
can do all kinds of bizarre type coercion at runtime. So the template literal form of string interpolation is usually preferred anyway.