But Flow recommends you use closed objects by default. They think that, especially when working with spread operators, it’s better to err on the side of caution.
However, I think TypeScript's approach makes more sense. Open objects more closely reflect how JavaScript actually works. Given how dynamic JavaScript is, any type system claiming to represent it has to be relatively cautious about how ‘safe’ it can truly be.
So the TypeScript team aimed for a compromise. A dogmatic approach to open objects would be counterproductive. You would be able to add any properties to any object:
type Album = {
title: string;
artist: string;
};
const album: Album = {
title: "The Dark Side of the Moon",
artist: "Pink Floyd",
// In this imaginary world, excess properties would be allowed!
year: 1973,
anythingElse: "Anarchy!!!",
};
So they decided to use two different behaviors based on an object's freshness. A fresh object is one created directly at the assignment site. This can be when declaring a variable:
// Fresh object!
const album: Album = {
title: "The Dark Side of the Moon",
artist: "Pink Floyd",
};
Or when passing an object to a function:
const processAlbum = (album: Album) => {};
// Fresh object!
processAlbum({
title: "The Dark Side of the Moon",
artist: "Pink Floyd",
});
When using fresh objects, TypeScript will warn you about any excess properties passed in. This helps you catch common errors, like passing the wrong object to a function.
A stale object is one assigned to a variable, or returned from a function.
// Stale object!
const staleAlbum = {
title: "The Dark Side of the Moon",
artist: "Pink Floyd",
};
const album: Album = staleAlbum;
Stale objects are not checked for excess properties, and are treated like open objects.
In my opinion, this approach is the best of both worlds. It's more lenient than Flow's closed object types, while giving the warnings you expect when declaring fresh objects.