How to abstract the intersection of types in Types

2020-07-26 07:23发布

问题:

I'm new to TypeScript and I'm trying to write a generic function which returns the intersection of two types.

I don't I'm quite understanding record types and their intersection very well so I'm probably doing something wrong but here goes...

Let's say we have some types like these:

type A = { foo: string }
var a: A = { foo: 'foo' }

type B = { [key: string]: string }
var b: B = a

interface C { foo: string }
var c: C = a

type D = { bar: number }
var d: D = { bar: 123 }

type E = { [key: string]: number }
var e: E = d

interface F { bar: number }
var f: F = d

type G = A & D
var g: G = { ...a, ...d }
g = { ...c, ...f }

type H = A & D
var h: H = { ...a, ...d }
h = { ...c, ...f }

type I = B & E
var i: I = { ...b, ...e } // Error!... >_<
i = { ...a, ...d } // Error!
i = { ...c, ...f } // Error!

type I2 = { [key: string]: string & number }
var i2: I2 = {} // Can only be empty
i = i2

type J = C & F
var j: J = { ...c, ...f }
j = { ...a, ...d }

type K = { [key: string]: string | number }
var k: K = { ...a, ...d }
k = { ...b, ...e }
k = { ...c, ...f }

Now the problem I was actually trying to solve was this:

const func1 = <T, U>(t: T, u: U, f: (t: T, u: U) => T & U): T & U => {
    return f(t, u)
}

h = func1(a, d, (t, u) => ({ ...t, ...u }))
i = func1(b, e, (t, u) => ({ ...t, ...u })) // Error
j = func1(c, f, (t, u) => ({ ...t, ...u }))

And the workaround that I came up with:

type RecordIntersection<T, U> = { [C in (keyof T | keyof U)]: T[keyof T] | U[keyof U] }

const func2 = <T, U>(t: T, u: U, f: (t: T, u: U) => RecordIntersection<T, U>): RecordIntersection<T, U> => {
    return f(t, u)
}

h = func2(a, d, (t, u) => ({ ...t, ...u })) // Error
k = func2(b, e, (t, u) => ({ ...t, ...u }))
j = func2(c, f, (t, u) => ({ ...t, ...u })) // Error

This works for my particular use case but I only get type K back for everything.

Playing around some more and I can get this to work:

type Intersection<T, U> = keyof T extends keyof U ? RecordIntersection<T, U> : T & U

const func3 = <T, U>(t: T, u: U, f: (t: T, u: U) => Intersection<T, U>): Intersection<T, U> => {
    return f(t, u)
}

h = func3(a, d, (t, u) => ({ ...t, ...u }))
k = func3(b, e, (t, u) => ({ ...t, ...u }))
j = func3(c, f, (t, u) => ({ ...t, ...u }))

I can't seem to do much with a RecordIntersection<T, U> as I can't work out how to get either a T or U back out. See How to use conditional types to filter unions in Typescript.

Is there a better way to do this?