Reputation: 363
I want to create a union type consist of numbers related to the other prop value.
type Props = { numberOfPoints: number; activeItem: number }
If numberOfPoints = 3
so activeItem
be a union type consist of 1 | 2 | 3
according to numberOfPoints
value.
Upvotes: 1
Views: 110
Reputation: 33041
Requirements: You need to use TypeScript nightly
Similar question/answer you can find here
The code:
type MAXIMUM_ALLOWED_BOUNDARY = 999
type ComputeRange<
N extends number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result
: ComputeRange<N, [...Result, [...Result, 0]['length']]>
)
type Props<PointsCount extends number> = {
numberOfPoints: PointsCount; activeItem: ComputeRange<PointsCount>
}
type Destructure<T extends Props<any>> =
T extends { numberOfPoints: infer Count; activeItem: infer Union }
? Union extends number[]
? { numberOfPoints: Count; activeItem: Union[number] }
: never
: never
type Result = Destructure<Props<3>>
Explanation
This PR increases a limit of recursive types.
ComputeRange
- creates empty array on first call and checks whether the length of an array equals N
generic argument. If not, calls recursively itself with same first argument and extended version of second argument (with extra element). This approach allows us to keep track of array length.
Each element of array is the length of Result
during each iteration.
Destructure
helps you to get the union of all elements in array. You are unable to do ComputeRange<PointsCount>[number]
in Props
directly because of the recursion.
Caveats (thanks @jcalz for pointing it)
You still are allowed to use negative numbers, typeof Infinity and fractionals.
// activeItem: any;
type _ = Props<2.3>
// activeItem: []
type __ = Props<number>
// activeItem: any;
type ___ = Props<0.000001>
// activeItem: [];
type ____ = Props<typeof Infinity>
The better way is to restrict Props
argument:
type AllowedRange = ComputeRange<MAXIMUM_ALLOWED_BOUNDARY>[number]
type Props<PointsCount extends AllowedRange> = {
numberOfPoints: PointsCount; activeItem: ComputeRange<PointsCount>
}
See full code:
type MAXIMUM_ALLOWED_BOUNDARY = 999
type ComputeRange<
N extends number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result
: ComputeRange<N, [...Result, [...Result, 0]['length']]>
)
type AllowedRange = ComputeRange<MAXIMUM_ALLOWED_BOUNDARY>[number]
type Props<PointsCount extends AllowedRange> = {
numberOfPoints: PointsCount; activeItem: ComputeRange<PointsCount>
}
type Destructure<T extends Props<any>> =
T extends { numberOfPoints: infer Count; activeItem: infer Union }
? Union extends number[]
? { numberOfPoints: Count; activeItem: Union[number] }
: never
: never
/**
* Ok
*/
type Ok0 = Destructure<Props<42>> // ok
type Ok1 = Destructure<Props<600>> // ok
/**
* Fails
*/
// activeItem: any;
type _ = Props<2.3>
// activeItem: []
type __ = Props<number>
// activeItem: any;
type ___ = Props<0.000001>
// activeItem: [];
type ____ = Props<typeof Infinity>
Please keep in mind, these things with recursion could boil a glass of water on your CPU :D
If you don't want to pay a huge amount of money for electricity, you can add some validators for number
argument:
type ComputeRange<
N extends number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result
: ComputeRange<N, [...Result, [...Result, 0]['length']]>
)
type IsFraction<T extends number> = `${T}` extends `${number}.${number}` ? true : false
type IsNegative<T extends number> = `${T}` extends `-${number}` ? true : false
type IsNotLiteral<T> = T extends number ? number extends T ? true : false : false
type Either<Validators extends boolean[]> = Validators[number] extends false ? true : false
type IsValid<PointsCount extends number> = Either<[
IsFraction<PointsCount>,
IsNegative<PointsCount>,
IsNotLiteral<PointsCount>
]
>
{
type Test = IsValid<2.3> // false
type Test1 = IsValid<-1> // false
type Test2 = IsValid<number> // false
}
type Props<
PointsCount extends number> =
IsValid<PointsCount> extends false ? never : {
numberOfPoints: PointsCount; activeItem: ComputeRange<PointsCount>
}
type Destructure<T> =
T extends { numberOfPoints: infer Count; activeItem: infer Union }
? Union extends number[]
? { numberOfPoints: Count; activeItem: Union[number] }
: never
: never
/**
* Returns union
*/
type Ok0 = Destructure<Props<2>> // expected result
/**
* Never
*/
type _ = Props<2.3>
type __ = Props<number>
type ___ = Props<0.000001>
type ____ = Props<typeof Infinity>
IsFraction
, IsNegative
, IsNotLiteral
will check whether your argument is valid or not. Please consider adding more tests.
P.S. More explanation you can find in my article
Upvotes: 2