在打字稿中键入对象文字的安全合并 [英] Type safe merge of object literals in typescript

查看:79
本文介绍了在打字稿中键入对象文字的安全合并的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想合并两个打字稿对象(使用对象传播):

var one = { a: 1 }
var two = { a: 2, b: 3 }
var m = {...one, ...two} // problem as property `a` is overwritten

我想使用类型系统来确保第二个对象中的任何属性都不会覆盖第一个对象中的任何属性.我不确定为什么以下解决方案不起作用:

type UniqueObject<T extends {[K in keyof U]?: any}, U> =
    {[K in keyof U]: T[K] extends U[K] ? never : U[K]}

var one = { a: 1 }
var two1 = { a: 2, b: 3 }
var two1_: UniqueObject<typeof one, typeof two1> = two1 // errors correctly
var two2 = { a: undefined, b: 1 }
var two2_: UniqueObject<typeof one, typeof two2> = two2 // passes incorrectly

一年前的另一个版本我当时以为是undefined extends U[K]代替T[K] extends U[K]:

type UniqueObject<T extends {[K in keyof U]?: any}, U> =
    {[K in keyof U]: undefined extends T[K] ? U[K]: never}

这两个都不起作用.我怀疑这是因为undefined extends U[K]T[K] extends U[K]都是错误的,因为T中的属性K是可选的.不知道如何或是否有可能解决这个问题.

解决方案

您的版本或多或少都等效-只有条件类型中的true/false分支会被切换.

约束T extends {[K in keyof U]?: any}有点问题:在two中删除a时,会触发错误Type '{ a: number; }' has no properties in common with type '{ b?: any; },这实际上应该是成功的情况.

还请注意,生成的类型merge不包含两种类型的合并类型定义.我们可以更改声明:

type UniqueObject<T, U> =
    T & { [K in keyof U]: K extends keyof T ? never : U[K] }

现在,编译器使用重复的a属性正确地错误:

var one = { a: 1 }
var two = { a: 2, b: 3 }
//                                                        v a becomes never here
type Merge = UniqueObject<typeof one, typeof two> // { a: never; b: number; }
const res: Merge = { ...one, ...two } // errors now, cannot assign number to never

在下文中,我对类型进行了一些简化,并将所有内容打包在紧凑的辅助函数中,以控制类型+传播算子:

function mergeUnique<T extends object, U extends object & { [K in keyof U]: K extends keyof T ? never : U[K] }>(o1: T, o2: U) {
    return { ...o1, ...o2 }
}

const res21 = mergeUnique({ a: 1 }, { b: 3 })
const res22 = mergeUnique({ a: 1 }, { a: 2, b: 3 }) // error
const res23 = mergeUnique({ a: 1, c: 5 }, { b: 3 })
const res24 = mergeUnique({ a: 1}, { a: undefined }) // error

Another version from a year ago which I thought worked at the time had undefined extends U[K] in the place of T[K] extends U[K]:

type UniqueObject<T extends {[K in keyof U]?: any}, U> =
    {[K in keyof U]: undefined extends T[K] ? U[K]: never}

Neither of these two work. I suspect it is because the undefined extends U[K] or T[K] extends U[K] are both false as the property K in T is optional. Not sure how or if it's possible to get around this.

解决方案

Both your versions are more or less equivalent - only the true/false branches in the conditional type are switched up.

The constraint T extends {[K in keyof U]?: any} is a bit problematic: when you remove a in two, the error Type '{ a: number; }' has no properties in common with type '{ b?: any; } is triggered, which actually should be the success case.

Be also aware, that the resulting type merge doesn't contain the merged type definition from both types. We can change the declaration up:

type UniqueObject<T, U> =
    T & { [K in keyof U]: K extends keyof T ? never : U[K] }

Now, the compiler correctly errors with a duplicate a property:

var one = { a: 1 }
var two = { a: 2, b: 3 }
//                                                        v a becomes never here
type Merge = UniqueObject<typeof one, typeof two> // { a: never; b: number; }
const res: Merge = { ...one, ...two } // errors now, cannot assign number to never

In the following I have simplified the type a bit and packed everything in a compact helper function to control types + spread operator:

function mergeUnique<T extends object, U extends object & { [K in keyof U]: K extends keyof T ? never : U[K] }>(o1: T, o2: U) {
    return { ...o1, ...o2 }
}

const res21 = mergeUnique({ a: 1 }, { b: 3 })
const res22 = mergeUnique({ a: 1 }, { a: 2, b: 3 }) // error
const res23 = mergeUnique({ a: 1, c: 5 }, { b: 3 })
const res24 = mergeUnique({ a: 1}, { a: undefined }) // error

Code sample

这篇关于在打字稿中键入对象文字的安全合并的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆