【问题标题】:How to create a generic union type related to the other one?如何创建与另一个相关的通用联合类型?
【发布时间】:2021-11-15 17:49:03
【问题描述】:

我想创建一个联合类型,由与其他道具值相关的数字组成。

type Props = { numberOfPoints: number; activeItem: number }

如果numberOfPoints = 3 那么activeItem 是由1 | 2 | 3 根据numberOfPoints 值组成的联合类型。

【问题讨论】:

    标签: reactjs typescript


    【解决方案1】:

    要求: 您需要每晚

    使用 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>>
    
    

    Playground

    说明

    This PR 增加了递归类型的限制。

    ComputeRange - 在第一次调用时创建空数组并检查数组的长度是否等于N 泛型参数。如果不是,则使用相同的第一个参数和第二个参数的扩展版本(带有额外元素)递归调用自身。这种方法允许我们跟踪数组长度。 数组的每个元素在每次迭代中都是Result的长度。

    Destructure 帮助您获取数组中所有元素的并集。由于递归,您无法直接在Props 中执行ComputeRange&lt;PointsCount&gt;[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

    Playground


    如果你不想花大笔电费,可以为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>
    

    Playground

    IsFractionIsNegativeIsNotLiteral 将检查您的论点是否有效。请考虑添加更多测试。

    附:更多解释可以在我的article中找到

    【讨论】:

    • 您的游乐场链接已损坏;你能修好吗?我认为这样的事情很有趣,但您可能应该在这里提出警告,因为将数字与数组长度进行比较的递归类型往往会... 有趣 当这些数字为负数或有小数部分时(例如, -22.5)。
    • @jcalz 添加了更多信息。我希望看到你的支持:D
    • 我很抱歉耽搁了@captain-yossarian。非常感谢您的全面回答?️
    • @MohammadRezaGhasemi 完全没有问题。感谢您的反馈!
    猜你喜欢
    • 2012-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-17
    相关资源
    最近更新 更多