TypeScript: Understanding structural typing
Duck and structural typing
When we talk about duck typing or structural typing we are talking about the compatibility that different types may or may not have in a given programming language.
JavaScript is a duck typed language. What does it mean?
If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
Following that precept, it means that rather than try to determine whether an object can be used for a purpose according to its type, in duck typing, an object’s suitability is determined by the presence of certain properties.
TypeScript models this behavior and bases its types compatibility on structural typing, in contrast to nominal typing.
Structural typing makes TypeScript not as strict as nominally typed languages like C# or Java and this sometimes can lead to confusion or unexpected results.
Having a good understanding of structural typing can help you make sense of errors and non-errors and help you write more robust code.
Understanding structural typing
The basic rule for TypeScript’s structural type system is that type A
is compatible with type B
if B
has at least the same members as A
.
Say we have 2 different interfaces:
|
|
Structural typing allows to assign an object of type Alien
to Person
but not the other way around because Person
lacks of the property planet
.
|
|
|
|
This might look simple to deal with but it can sometimes lead to confusing results.
Say you want to create a function that prints the details of Person
.
You might think that something like the following would do the job
|
|
But surprisingly person[property]
will produce the following error:
Element implicitly has an ‘any’ type because expression of type ‘string’ can’t be used to index type ‘Person’.
The logic in the previous example assumes that the Person
passed to the function is a “sealed” (or “precise”) type and therefore every property of the object would exist.
But as we saw earlier that is not the case. Like it or not types in TypeScript are “open” and they might contain more properties than the ones declared.
According to structural typing I could pass an Alien
and the type checker wouldn’t complain about it. The problem is when we iterate over the properties of Person
and because it might have to deal with unknown properties like planet
, the type checker gives us that error.
If you are after a solution to this problem have a look to this article: How to iterate over object properties.
Benefits of structural typing
Structural typing is not always bad and in some circumstances it can be beneficial, for instance when you are writing tests.
Say you have a function that returns the users of your application and process the results.
|
|
In order to test getUsers
you could mock Repository
but a better approach would be leveraging structural typing and create a narrower interface:
|
|
You can still pass getUsers
a Repository
since it has a findUsers
method and because of structural typing it doesn’t need to implement MyRepository
.
But in your test you can now pass a simpler object:
|
|
Conclusion
TypeScript uses structural typing to model the duck typed nature of JavaScript. Types are not “sealed” and values assignable to your interfaces might have more properties than the ones listed in your type declaration.