0

In many cases, I have a function that accepts many strings:

export const acceptsManyStrings = (one: string, two: string, three: string) => {
    return one.slice(0,1) + two.slice(0,2) + three.slice(0,3);
}

because from a type perspective, the arguments are interchangeable, I may get the order wrong, pass two instead of one, or whatever.

Is there some way I could type each of the arguments somehow?

The only way I can think of is a dumb way, something like this:

export interface OneStr {
  one: string
}

export interface TwoStr {
  two: string
}

export interface ThreeStr {
  three: string
}

and then use it like so:

export const acceptsManyStrings = (one: OneStr, two: TwoStr, three: ThreeStr) => {
    return one.one.slice(0,1) + two.two.slice(0,2) + three.three.slice(0,3);
}

but that solution is pretty suboptimal for the obvious reasons. Any ideas?

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817

1 Answers1

4

If you want to avoid the posibility of wrong parameter order, I would recommend you use a single parameter and have it be an object containing all the curent arguments as fields. That way when calling it will be obvious which value is which parameter (this is a pretty common Javascript approach):

export const acceptsManyStrings = (o: { one: string, two: string, three: string }) => {
    return o.one.slice(0, 1) + o.two.slice(0, 2) + o.three.slice(0, 3);
}

acceptsManyStrings({ one: "", two:"", three:"" })

or using destructuring you could also do it like this:

export const acceptsManyStrings = ({ one, two, three }: { one: string, two: string, three: string }) => {
  return one.slice(0, 1) + two.slice(0, 2) + three.slice(0, 3);
};

You can create in Typescript types that are subtypes of string but are incompatible using branded types.

// The branded types
type One = string & { readonly isOne: unique symbol };
type Two = string & { readonly isTwo: unique symbol };
type Three = string & { readonly isThree: unique symbol };

// The create branded types functions 
export const createOne = (o: string) => o as One;
export const createTwo = (o: string) => o as Two;
export const createThree = (o: string) => o as Three;  

export const acceptsManyStrings = (one: One, two: Two, three: Three) => {
    return one.slice(0, 1) + two.slice(0, 2) + three.slice(0, 3);
}

acceptsManyStrings(createOne(""), createTwo(""), createThree(""))
acceptsManyStrings( createTwo(""), createOne(""), createThree(""))// error

But this is probably overkill in this situation

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • 1
    @AlexanderMills Yeah destructuring works as well I did not want to get into that necessarily for this answer. I changed your version a bit, I'm not really a fan o using an interface for a single function parameter, an inline type works as well, other than that it's fine :) – Titian Cernicova-Dragomir Aug 13 '18 at 06:52
  • honestly I think it was better with the OTT interface, but either way – Alexander Mills Aug 13 '18 at 22:58