And the reason is, that we can actually pass in objects that have more than the keys of a person. So what happens if we pass in the object P1, which has a name property and an age property? Well, the for loop is going to take the first key, which is name, is going to uppercase it, everything will work fine, but then it's going to have the age key, and when it indexes with the age key it's going to get a number, which doesn't have the toUpperCase method, and this will fail at run-time. So, if object.keys were to return an array of whatever keys we passed in as a type, we might actually at run-time get more keys than we bargained for, and this might lead to run-time errors. And this is the reason object.keys has the type it has, because if it didn't, if it returned an array of key of whatever we passed in, we might actually get run-time errors that we weren't expecting, and run-time errors in a program that fully type checks.
Let's move on to unions of objects. What do these mean? Well, let's take an example. Let's say we have two object types, and we want to take their union. Well, this means that our new set, our union set, will have object values that might have a name property or might have an ID property, but we don't know exactly which one. So this means that it's not safe to access either of these properties because they might not be present. One of them will be, but we don't know which one. If these types would have had some property in common, for example, description, then that property would be safe to access because we can say for sure that it has to be present on all object values inside this set. And this is again a source of confusion for many newcomers to TypeScript because the union operation is named for what it does to the set of values, not necessary to what it does to the members because unions will allow access to an intersection of members. But again, the operation is named for what it does to the sets of values described by these types.
What about intersections of object types? Well, they suffer from a similar confusion. Namely, if we take the intersection of these two sets, what we will have in the intersection is object values that have both name and ID. And since they have both of these properties, it's safe to access either of them. So, intersection of object types will intersect the sets defined by the types and will result in a new type that has a union of members. But again, the operation is named for what it does to the sets, not for what it does to the members. So, again, unions and intersections, a lot of people have the feeling at first that these are badly named because, well, unions we have an intersection of members, while for intersections we have a union of members. But these, again, are not named for what they do to the members, they're named for what they do to the sets of values.
Let's see if we can use the same trick we used on unions of primitive types to filter out a discriminated union. And here we have a discriminated union that has two constituents, one of type square and one of type circle. And the question is how can we extract just the circle component. And we could of course use a conditional type, that is definitely the preferred way to do this, but intersections also can do the job. And mostly they give out the same results. So what type could we intersect this union to only preserve the type that has type circle? And the answer is that we can intersect with another object type, that has a property of type circle. And this will actually preserve just the component that we are interested in because it is, the intersection, does not contain any of the objects that could have type square. So let's see how this could work. Well, TypeScript will try to do the same thing it does for the union of primitives, namely to try to bring it to the canonical four. So first of all TypeScript will expand that shape into the union that it actually is.
Comments