代码之家  ›  专栏  ›  技术社区  ›  edA-qa mort-ora-y

一个类型安全的TypeScript,它有一个DISCIMION联盟(比如C++的动态模型)

  •  0
  • edA-qa mort-ora-y  · 技术社区  · 4 年前

    enum my_type {
        drag = 'drag',
        std = 'std',
    }
    
    interface base {
        type: my_type
    }
    
    interface drag {
        type: my_type.drag
    }
    interface std {
        type: my_type.std
    }
    
    type all = drag | std
    
    function test_drag(obj: all) {
        let obj_drag: drag|null = obj.type == my_type.drag ? obj : null
        if (!obj_drag) {
            console.log("It's not a drag")
            return
        }
        console.log("It's a drag")
    }
    
    test_drag({type: my_type.drag}) // yes
    test_drag({type: my_type.std}) // no
    test_drag({type: 'drag'} as any) // yes
    

    我对更干净的工作方式感兴趣 let obj_drag: drag|null = obj.type == my_type.drag ? obj : null 一个接受类型参数的泛型函数会很好,但我不知道如何正确地获取语法。也就是说,我想要一个如下所示的函数:

    function dynamic_cast<DiscriminatingType>(obj): ObjectType
    

    let obj_drag = dynamic_cast<my_type.drag>(obj)
    let obj_std = dynamic_cast<my_type.std>(obj)
    

    这种类型的功能是可能的,还是与之相近的功能?我主要想要可读的东西,最好不必同时指定判别式和结果类型。


    @jcalz提供了这种通用方法。

    function dynamicCastO<T, K extends keyof T, V extends T[K] & (string | number | boolean)>(
        obj: T, k: K, v: V) {
        return obj[k] === v ? obj as Extract<T, Record<K, V>> : null;
    }
    

    所以我想知道我是否可以省去通过考试的必要性 type . 在我下面的尝试中,返回类型不是所需的类型。

    function dynamicCast<K, T extends { type: K }>(
        obj: T, v: K) {
        return obj.type === v ? obj : null;
    }
    
    0 回复  |  直到 4 年前
        1
  •  2
  •   jcalz    4 年前

    一种可能的编写函数的方法,可以区分 discriminated union 这是:

    const discriminateUnion = <K extends PropertyKey>(
        discriminantKey: K
    ) => <T extends Record<K, string | number | boolean>, V extends T[K]>(
        obj: T, discriminantValue: V
    ) => obj[discriminantKey] === discriminantValue ? obj as Extract<T, Record<K, V>> : null;
    
    const dynamicCast = discriminateUnion("type");
    

    在这里 discriminateUnion 判别式 歧视联盟的关键。你的情况是 "type" . 然后,它返回另一个函数,该函数接受已判别的联合类型的对象,以及要检查的判别值。如果对象的鉴别器属性与该值匹配,则返回缩小到联合的相关成员的对象。否则它就会回来 null

    鉴别属性需要通过 === string | number | boolean . 如果你有其他的单位判别式,比如 undefined 你可以加进去。返回类型, Extract<T, Record<K, V>> 使用内置 utility types 拉出 T K 是有价值的 V . 这里可能有边缘情况;例如,如果您的判别式是可选的,您可能需要这样做 Extract<T, Partial<Record<K, V>>>

    我们试试看。我将更改类型,使其符合标准的TypeScript命名约定(大写和驼峰大小写之类的东西),并为您的工会成员添加一点结构,以表明这种区别有实际的结果:

    enum MyType {
        DRAG = 'drag',
        STD = 'std',
    }
    
    interface Base {
        type: MyType
    }
    
    interface Drag {
        type: MyType.DRAG
        dragProp: string;
    }
    interface Std {
        type: MyType.STD
        stdProp: string;
    }
    
    type All = Drag | Std
    

    然后实施 testDrag() 将使用 dynamicCast 这样地:

    function testDrag(obj: All) {
        let objDrag = dynamicCast(obj, MyType.DRAG);
        if (!objDrag) {
            console.log("It's not a drag")
            return
        }
        console.log("It's a drag, dragProp is " + objDrag.dragProp.toUpperCase())
    }
    

    testDrag({ type: MyType.DRAG, dragProp: "hello" }) // It's a drag, dragProp is HELLO
    testDrag({ type: MyType.STD, stdProp: "goodbye" }) // It's not a drag
    

    Playground link to code