0

I have the following typings declaration:

public static Loop<Type>(arr:Type[], callback:(obj:Type) => void):void;

This describes the signature for a function implemented with native JavaScript. It loops through each object in an array and passes it to the callback function, e.g.:

Loop(["hello", "world"], function(value)
{
    console.log(value);
});

I get working intellisense when used like in the example above, but I also want it working when the array can be of different types, e.g.:

let arr: string[] | number[] = [];
Loop(arr, function(value)
{
    console.log(value);
});

But the example above does not work - "value" is described with the 'any' type, rather than "string | number".

However, if the array is declared like let arr: (string|number)[], it works as expected. But I'm not interested in having mixed arrays. It's either a string array or number array.

Can I declare the signature for Loop to work with e.g. string[] | number[] ?

-- Thanks in advance

2 Answers 2

3

In this case I think you'll find it easier to make your Loop function generic in the array type, not the element type. Like this:

  public static Loop<A extends readonly any[]>(arr: A, callback: (obj: A[number]) => void): void {
    arr.forEach(v => (callback(v)));
  }

Here, A is the array type, and A[number] is the element type (because if you have an array of type A and index into it with a key of type number you should get one of its elements).

Let's make sure it works:

let arr = Math.random() < 0.5 ? ["a", "b", "c"] : [1, 2, 3];
Loop(arr, function (value) {
  console.log(typeof value === "number" ? value + 1 : value.toUpperCase());
});
// A B C or 2 3 4 

Yes, arr is accepted, and then value is contextually inferred to be the union string | number.

Playground link to code

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks jcalz. I will give it a try and see whether I like yours or Linda's solution the best. I appreciate your input - thanks! :-)
1

The issues with a type like string[] | number[] come up a lot and I have a go-to solution that I like to use. This is a bit of a hack, but when we say that your type is (string[] | number[]) and also (string | number)[], then you no longer have issues calling functions like array.map or your Loop method. The import part is that the array must fit both of those definitions, so it doesn't allow for mixed arrays. You can put this into a utility type:

type ArrayOfEither<A, B> = (A[] | B[]) & (A | B)[]

You don't need to make any changes to your Loop method, but you do need to make a change to how you declare your variable arr.

let arr: ArrayOfEither<string, number> = [];
arr = ["1", 2, 3] // error: Type '(string | number)[]' is not assignable to type 'ArrayOfEither<string, number>'.
arr = [1, 2, 3] // ok
Loop(arr, function(value) {
    console.log(value);  // value is `string | number`
});

Typescript Playground Link

1 Comment

Thank you very much. I will give your suggestion a try, and see whether I like this approach or jcalz' approach better. Thank you - much appreciated :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.