In this chapter, we explore what intersections of object types can be used for in TypeScript.
In this chapter, object type means:
Record
)
The intersection of two object types has the properties of both:
type Obj1 = { prop1: boolean };
type Obj2 = { prop2: number };
const obj: Obj1 & Obj2 = {
prop1: true,
prop2: 123,
};
With interfaces, we can use extends
to add properties:
interface Person {
name: string;
}
interface Employe extends Person {
company: string;
}
With object types, we can use an intersection:
type Person = {
name: string,
};
type Employee =
& Person
& {
company: string,
}
;
One caveat is that only extends
supports overriding. For more information, see $type.
NonNullable
(T & {}
)
/**
* Exclude null and undefined from T
*/
type NonNullable<T> = T & {};
type _ = [
Assert<Equal<
NonNullable<undefined | string>,
string
>>,
Assert<Equal<
NonNullable<null | string>,
string
>>,
Assert<Equal<
NonNullable<string>, // (A)
string
>>,
];
The result of NonNullable<T>
is a type that is the intersection of T
and all non-nullish values.
It’s interesting that string & {}
is string
(line A).
The following code shows how the inferred type of obj
changes when we use the built-in type guard in
(line A and line B):
function func(obj: object) {
if ('prop1' in obj) { // (A)
assertType<
object & Record<'prop1', unknown>
>(obj);
if ('prop2' in obj) { // (B)
assertType<
object & Record<'prop1', unknown> & Record<'prop2', unknown>
>(obj);
}
}
}
In the next example, we combine the type Obj
of a parameter with the type WithKey
– by adding the property .key
of WithKey
to the parameter:
type WithKey = {
key: string,
};
function addKey<Obj extends object>(obj: Obj, key: string)
: Obj & WithKey
{
const objWithKey = obj as (Obj & WithKey);
objWithKey.key = key;
return objWithKey;
}
addKey()
is used like this:
const paris = {
city: 'Paris',
};
const parisWithKey = addKey(paris, 'paris');
assertType<
{
city: string,
key: string,
}
>(parisWithKey);