是否可以在一个 TypeScript 注释中组合多个类型的成员?

看起来我正在努力做的事情是不可能的,但我真的希望它是可能的。

实际上,我有两个接口,我想注释一个函数参数作为它们的组合。

interface ClientRequest {
userId:     number
sessionKey: string
}


interface Coords {
lat:  number
long: number
}

然后,在函数中,我想这样做:

function(data: ClientRequest&Coords) { ... }

这样我的“ data”对象就可以包含来自这两种类型的所有成员。

我在 规格预览的“组合类型的成员”下面看到了一些引用,但似乎这个还没有进入。

如果不可能,我的解决方案可能是这样的:

interface ClientRequest<T> {
userId:     number
sessionKey: string
data?:       T
}


function(data: ClientRequest<Coords>) { ... }

在这种情况下也可以,虽然不像我想象的那么有活力。我真的希望能够在注释本身中组合多个(2 +)类型:

function(data: TypeA&TypeB&TypeC) { ... }

我猜传统的解决方案是定义一个类型来扩展这些类型,尽管这看起来没有那么灵活。如果我想添加一个类型,我必须

  • (a)返回声明并重写,或
  • (b)创建一个全新的接口。我不确定是否同意额外的开销。

有没有 TypeScript 专家愿意给我指明正确的方向?

125773 次浏览

ANSWER UPDATED ON 2018-10-30

TypeScript now has type intersections. So you can now simply do:

interface ClientRequest {
userId:     number
sessionKey: string
}


interface Coords {
lat:  number
long: number
}


function log(data: ClientRequest & Coords) {
console.log(
data.userId,
data.sessionKey,
data.lat,
data.long
);
}

ORIGINAL ANSWER

The specific answer to your question is: no, there is not a single inline annotation to signify combined or extended types.

The best practice for the problem you are trying to solve would be to create third type that would extend the other two.

interface IClientRequestAndCoords extends IClientRequest, ICoords {}


function(data: IClientRequestAndCoords)

The interface answer is a reasonably graceful method of combining the two structures, but you mention that you want to know if it is possible to combine the type as part of an annotation.

A note on interfaces

I have supplied some descriptions of a few features related to your question, but first I would say that if you are put off of the interface solution because you think you'll have to create an ICoords interface (as in your question it looks more like a class) - rest easy - because an interface can extend a class too:

// Interface extending an interface and a class
interface IClientRequestAndCoords extends IClientRequest, Coords {}

The interface will even merge properties as long as they have the same name and type. (For example if they both declared a property x: string.

Here are notes on the other annotation features you allude to.

Union Types

The specification you may have read is the union type, which looks like this:

var x: IClientRequest | Coords;

But this only ensures that x is either one or the other, not a combination of the two. Your syntax of a merged type IClientRequest & Coords isn't on the roadmap as far as I know.

function go(data: IClientRequest | Coords) {
var a = data[0]; // IClientRequest
var b = data[1]; // Coords
}


// Allowed (even though it doesn't supply Coords data
go(myClientRequest);


// Allowed (even though it doesn't supply IClientRequest data
go (myCoords);

This also isn't part of the current release, but is coming later.

Tuple Types

Another possible part of the specification you may have seen is tuple types:

var x: [IClientRequest, Coords];

But this would change the shape of the data from being a structure to being like an array where element 0 is an IClientRequest and element 1 is an Coords.

function go(data: [IClientRequest, Coords]) {
var a = data[0]; // ClientRequest
var b = data[1]; // Coords
}


go([myClientRequest, myCoords]);

Uber-Annotation

And finally, if you really don't want to create a merged interface, you could just use an uber-annotation:

function go(data: { userId:number; sessionKey: string; x: number; y: number; } ) {


}

I saw something referenced in a spec preview, under "Combining Types' Members", but it seems like this hasn't made it in yet.

I think you would be more interested in intersection types (not union). The difference is that the object passed in must support all of the properties and not one of.

Github issue : https://github.com/Microsoft/TypeScript/issues/1256#issuecomment-64533287

It's very possible if you use ES6 Object.assign. Assuming you have existing objects of those types.

First let's define the types

interface ClientRequest {
userId:     number
sessionKey: string
}


interface Coords {
lat:  number
long: number
}

Now the combination of both:

type Combined = ClientRequest & Coords;

Assuming you have two existing objects which you would like to pass to the function:

const foo: ClientRequest = {
userId: 10,
sessionKey: "gjk23h872ty3h82g2ghfp928ge"
}


const bar: Coords = {
lat: -23,
long: 52
}

You can combine them like this:

const myData: Combined = Object.assign({}, foo, bar);

Or simply create a new one like this:

const myData: Combined = {
userId: 10,
sessionKey: "gjk23h872ty3h82g2ghfp928ge",
lat: -23,
long: 52,
}

Previous method

Not type-safe.

The <Type> {...} syntax casts the object to the type specified in the angle brackets (Type), which bypasses Typescript's checker. See Type Assertion.

const myData = Object.assign({},
<ClientRequest> {
userId: 10,
sessionKey: "gjk23h872ty3h82g2ghfp928ge"
},
<Coords> {
lat: -23,
long: 52
}
);

Finally, call the function:

function myFunc(data: Combined) { ... }
myFunc(myData);

See this other question for even more ways of accomplishing this:

How can I merge properties of two JavaScript objects dynamically?

I just needed to do this and the following solution worked for me:

type MyFunction = () => void


interface MyFunctionWithProps extends MyFunction {
prop1: string,
prop2: number
}


// compiles fine
const MyMashup: MyFunctionWithProps = Object.assign(
() => {},
{
prop1: 'hello',
prop2: 1234
}
)

You can do something like this, now, with conditional types, if types P1 and P2 extends of object:

type P1UnionP2 = { [k in (keyof P1 | keyof P2)]: k extends keyof P1 ? P1[k] : k extends keyof P2 ? P2[k] : never }

The best approach is, if that's applies in your case, this:

interface P1UnionP2 extends P1, P2 { }

This is a reusable utility type based on this answer, for combining a union of Record types

type UnionToType<U extends Record<string, unknown>> = { [K in (U extends unknown ? keyof U : never)]: U extends unknown ? K extends keyof U ? U[K] : never : never}


type A = {
a: string
}


type B = {
b: number
}


type C = {
c: boolean
}


type D = {
c: A
}


type Combined = UnionToType<A | B | C | D>


/*
type Combined = {
a: string;
b: number;
c: boolean | A;
}
*/


const combined1: Combined = {
a: 'test',
b: 2,
c: {
a: ''
}
}


const combined2: Combined = {
a: 'test',
b: 2,
c: false
}

Playground example