【发布时间】:2021-11-15 17:49:03
【问题描述】:
我想创建一个联合类型,由与其他道具值相关的数字组成。
type Props = { numberOfPoints: number; activeItem: number }
如果numberOfPoints = 3 那么activeItem 是由1 | 2 | 3 根据numberOfPoints 值组成的联合类型。
【问题讨论】:
标签: reactjs typescript
我想创建一个联合类型,由与其他道具值相关的数字组成。
type Props = { numberOfPoints: number; activeItem: number }
如果numberOfPoints = 3 那么activeItem 是由1 | 2 | 3 根据numberOfPoints 值组成的联合类型。
【问题讨论】:
标签: reactjs typescript
要求: 您需要每晚
使用 TypeScript您可以找到类似的问题/答案here
代码:
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>>
说明
This PR 增加了递归类型的限制。
ComputeRange - 在第一次调用时创建空数组并检查数组的长度是否等于N 泛型参数。如果不是,则使用相同的第一个参数和第二个参数的扩展版本(带有额外元素)递归调用自身。这种方法允许我们跟踪数组长度。
数组的每个元素在每次迭代中都是Result的长度。
Destructure 帮助您获取数组中所有元素的并集。由于递归,您无法直接在Props 中执行ComputeRange<PointsCount>[number]。
注意事项(感谢@jcalz 指出)
您仍然可以使用负数、无穷大类型和小数。
// activeItem: any;
type _ = Props<2.3>
// activeItem: []
type __ = Props<number>
// activeItem: any;
type ___ = Props<0.000001>
// activeItem: [];
type ____ = Props<typeof Infinity>
更好的方法是限制Props参数:
type AllowedRange = ComputeRange<MAXIMUM_ALLOWED_BOUNDARY>[number]
type Props<PointsCount extends AllowedRange> = {
numberOfPoints: PointsCount; activeItem: ComputeRange<PointsCount>
}
查看完整代码:
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>
请记住,这些带有递归的东西可能会在你的 CPU 上烧开一杯水:D
如果你不想花大笔电费,可以为number参数添加一些验证器:
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 将检查您的论点是否有效。请考虑添加更多测试。
附:更多解释可以在我的article中找到
【讨论】:
-2 或 2.5)。