Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/typescript/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Typescript 使用泛型对象类型联合进行类型缩小_Typescript - Fatal编程技术网

Typescript 使用泛型对象类型联合进行类型缩小

Typescript 使用泛型对象类型联合进行类型缩小,typescript,Typescript,以下类型在事件系统中用于对象更改 我在基于某些条件缩小对象类型方面遇到了问题。例如,当prop属性为null时,我们可以确定deleted将为false 失败案例如下所示: 声明常量更改:更改 如果(change.prop=='a'){ change.prop//OK change.newValue//应为“number” } if(change.prop==null){ change.prop//应为“null” change.deleted//应为“false” change.newValu

以下类型在事件系统中用于对象更改

我在基于某些条件缩小对象类型方面遇到了问题。例如,当
prop
属性为null时,我们可以确定
deleted
将为false

失败案例如下所示:

声明常量更改:更改
如果(change.prop=='a'){
change.prop//OK
change.newValue//应为“number”
}
if(change.prop==null){
change.prop//应为“null”
change.deleted//应为“false”
change.newValue//应为“{a:number;b:string}”
}
改型=
|换根
|(T扩展对象?嵌套更改:从不)
类型RootChange=IChange&{
道具:空
旧值:T
新值:T
已删除:false
}
类型嵌套更改=
|(i改变,{
道具:P
旧值:T[P]
新值:T[P]
已删除:false
})
|(i改变,{
道具:P
旧值:T[P]
newValue:未定义
删除:真
})
界面易变{
/**正在更改的属性。如果为null,则此更改针对根值*/
道具:任何|空
/**以前的值*/
旧值:未知
/**下一个值*/
newValue:未知
/**是否已删除该属性*/
删除:布尔值
}

问题在于
NestedChange
。您为
{a:number,b:string}
定义类型的方式相当于:

type NestedChange<{a: number, b: string}> =
    | (IChange & {
        prop: "a" | "b"
        newValue: number | string
        deleted: false
    })
    | (IChange & {
        prop: "a" | "b"
        newValue: undefined
        deleted: true
    })
可以使用联合类型的分布行为(read)创建这样的联合。这意味着,如果我们有一个裸类型参数,它包含
T
的键的并集,我们可以迭代这些键并对每个键应用类型转换,得到一个包含应用于每个键的转换的并集

为了引入新的类型参数并在其上分布,我们使用了两种条件类型:
keyof T extensed inferp?P扩展任何?…:从不:从不
。在这两种类型中,条件都不重要,我们使用第一个条件(
keyof T extends infer P
)引入新的类型参数
P
,并使用第二个条件(
P extends any
)触发分配行为

declare const change: Change<{ a: number; b: string }>

if (change.prop === 'a') {
    change.prop // OK
    change.newValue // is number | undefined
    if (change.deleted) {
        change.newValue // undefined
    } else {
        change.newValue // number
    }
}

if (change.prop == null) {
    change.prop // is "null"
    change.deleted // is "false"
    change.newValue // is "{a: number; b: string}"
}

type Change<T = any> =
    | RootChange<T>
    | (T extends object ? NestedChange<T> : never)

type RootChange<T> = IChange & {
    prop: null
    newValue: T
    deleted: false
}

type NestedChange<T extends object> = keyof T extends infer P ?
    P extends any ?
    (IChange & {
        prop: P
        newValue: T[P]
        deleted: false
    })
    | (IChange & {
        prop: P
        newValue: undefined
        deleted: true
    })
    : never : never;
interface IChange {
    /** The property being changed. When null, this change is for the root value. */
    prop: keyof any | null
    /** The previous value */
    oldValue: unknown
    /** The next value */
    newValue: unknown
    /** Whether the property has been deleted */
    deleted: boolean
}
声明常量更改:更改
如果(change.prop=='a'){
change.prop//OK
change.newValue//是数字|未定义
如果(更改。删除){
change.newValue//未定义
}否则{
change.newValue//number
}
}
if(change.prop==null){
change.prop//为“null”
change.deleted//为“false”
change.newValue//是“{a:number;b:string}”
}
改型=
|换根
|(T扩展对象?嵌套更改:从不)
类型RootChange=IChange&{
道具:空
新值:T
已删除:false
}
类型NestedChange=keyof T扩展推断P?
有吗?
(i改变,{
道具:P
新值:T[P]
已删除:false
})
|(i改变,{
道具:P
newValue:未定义
删除:真
})
:从不:从不;
界面易变{
/**正在更改的属性。如果为null,则此更改针对根值*/
道具:任何|空
/**以前的值*/
旧值:未知
/**下一个值*/
newValue:未知
/**是否已删除该属性*/
删除:布尔值
}

Wow,我以前从未见过
keyof T扩展推断P
。非常有趣。@aleclarson希望它能帮上忙,如果我能帮上忙,请告诉我:)@aleclarson该解决方案在操场上有效,但需要严格的空检查,因为您使用
prop:null
进行根目录更改。我总是忘记在操场上启用它!应该是默认值。谢谢:P
 { prop: "a"; newValue: number; deleted: false; } | 
 { prop: "a"; newValue: undefined; deleted: true; } | 
 { prop: "b"; newValue: string; deleted: false; } | 
 { prop: "b"; newValue: undefined; deleted: true; }
declare const change: Change<{ a: number; b: string }>

if (change.prop === 'a') {
    change.prop // OK
    change.newValue // is number | undefined
    if (change.deleted) {
        change.newValue // undefined
    } else {
        change.newValue // number
    }
}

if (change.prop == null) {
    change.prop // is "null"
    change.deleted // is "false"
    change.newValue // is "{a: number; b: string}"
}

type Change<T = any> =
    | RootChange<T>
    | (T extends object ? NestedChange<T> : never)

type RootChange<T> = IChange & {
    prop: null
    newValue: T
    deleted: false
}

type NestedChange<T extends object> = keyof T extends infer P ?
    P extends any ?
    (IChange & {
        prop: P
        newValue: T[P]
        deleted: false
    })
    | (IChange & {
        prop: P
        newValue: undefined
        deleted: true
    })
    : never : never;
interface IChange {
    /** The property being changed. When null, this change is for the root value. */
    prop: keyof any | null
    /** The previous value */
    oldValue: unknown
    /** The next value */
    newValue: unknown
    /** Whether the property has been deleted */
    deleted: boolean
}