Interfaces
An interface
declaration defines the key and value types of an object.
The main purpose of an interface is to tell the TypeScript compiler about how an object is structured. All interface declarations are removed by the compiler when it generates JavaScript code.
key: value
key2: value2
}
You can also use type
to define the structure of an object - both examples are shown below.
Structural Compatibility
TypeScript uses structural typing to determine whether two types are compatible. This means that compatibility is based on the shape of the types — the properties they have and how they're typed — rather than their explicit declarations.
A Point3D
object can be used wherever a Point2D
is expected, because it has at least the required properties x
and y
. We also don't need to explicitly set the type of pt
to Point3D
- as long as the type inferred by TypeScript for pt
matches Point3D
, it is allowed to be used as a Point3D
argument.
This flexibility allows for safer, more adaptable programs. However, it also means that TypeScript does not enforce nominal typing (i.e., type compatibility based on name), which can be surprising for developers coming from other statically typed languages.
Type Composition
The ampersand operator (&
) can be used to construct an intersection type which contains all the members of two or more types.
Notice that the &
actually joins the two types. More precisely, A & B
creates a new type C
such that every object that matches either A
or B
will fit the description of C
. This is often useful for constructing a safe interface for operating with multiple other interfaces.
This also means that two overlapping keys that map to different value types will produce a resulting value of never
.
The type of never
indicates to any operations that depend on values of type C
that the foo
property should not be accessed. Consider the exact same example as above, but with the union type operator (|
) instead.
If you played with the example above, you might have noticed that the union type will only contain keys that are in both object types.
Type composition
Let's look at a more practical example to understand the implications of all this. Suppose we have two functions for sending emails, and we want to merge them into one.
We have a roughly equivalent choice here of using Employee | Customer
or Employee & Customer
as the first parameter of our new composed function if we
are only interested in the name
and email
field.
The difference is that by choosing a union type (|
) instead, we being more restrictive with the information that the function has available to operate on. There are certainly cases where &
is more appropriate, but in this case, we are telling the sendEmail
function that a Recipient
may have a badge
and birthday
property that can also be accessed.
Also notice what happens if we update the Customer
type to include a required key. We can no longer send emails with Employee
objects, because the intersection type also contains a required key that Employee
objects do not have.
Indexed Types
The value type for a specific key can be extracted with an indexed access type.
The indexing type is itself a type, so we can use unions keyof
, or other types.
You will even get an error if you try to access a property which doesn't exist.