问题在于
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
})
因此属性类型和属性名称之间没有关系,
prop:"a"
可以与
string
就编译器而言。
你想要一个更像这样的联盟:
{ prop: "a"; newValue: number; deleted: false; } |
{ prop: "a"; newValue: undefined; deleted: true; } |
{ prop: "b"; newValue: string; deleted: false; } |
{ prop: "b"; newValue: undefined; deleted: true; }
可以使用联合类型的分布行为创建这样的联合(请参阅
here
)。这意味着如果我们有一个裸类型参数,该参数包含
T
要引入新类型参数并在其上分布,我们使用两种条件类型:
keyof T extends infer P ? P extends any ? ... : never: never
. 在这两种类型中,条件都不重要,我们使用第一个条件(
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
}