In my particular scenario, I am aiming for the array to consist of my object and two numeric values.
If this is truly your requirement, then your current method is absolutely correct, with the condition that the types in the array must follow that specific order.
(FieldSource | number )[]
is distinct from [FieldSource, number, number]
), take this into account:
interface MyInterface {
prop: number
}
type MyTuple = [MyInterface, number, number]
type MyArr = (MyInterface | number)[]
const myTup: MyTuple = [{ prop: 2 }, 2, 2] // <-- can only have two numbers
const myArr: MyArr = [{ prop: 2 }, 2, 2, 2, 2] // <-- can have any amount of numbers
const myArrAsTup: MyTuple = [{ prop: 2 }, 2, 2, 2, 2] // <-- not assignable to type
(FieldSource | number )[]
, which is the same as
(FieldSource | number | number)[]
, specifies that the array can include any of those values in any order and quantity.
[FieldSource, number, number]
is referred to as a Tuple type, it enforces the types allowed in an array, their positions, and the array's length.
type MyTuple = [MyInterface, number, number]
const tupleOne: MyTuple = [2, 2, {prop: 2} ] // <-- not assignable
const tupleTwo: MyTuple = [2, {prop: 2}, 2] // <-- not assignable
This also makes it easier for typescript to determine the types of items retrieved from the array using indexing or destructuring:
const [interfaceInstance] = myTup // <-- type is MyInterface
const aNumber = myTup[1] // <-- type is number
If you are unable to ensure the type order (or prefer not to), you can utilize a union of multiple tuples:
// less verbose, weaker inference
type TupleMember = (MyInterface | number)
type MyTuple = [TupleMember, TupleMember, TupleMember]
// more verbose, still able to infer types of tuple members
// as long as those tuples are defined somewhere in your codebase
// and not generated from an external source, like an API call
type MyTuple =
[MyInterface, number, number] |
[number, MyInterface, number] |
[number, number, MyInterface]