Home > Net >  How can I infer a template literal type when concatenating two known string types
How can I infer a template literal type when concatenating two known string types

Time:07-14

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.

See playground

  • Related