- Published on
Interfaces in TypeScript
- Authors
- Name
- Curtis Warcup
Interfaces
We get very strong code reuse in TS when we use interfaces and classes together.
Definition: Creates a new type, describing the property names and values type of an object. Remember, an interface forces your class to have the same properties as the interface.
Long Type Interfaces
// create an object
const oldCivic = {
name: 'civic',
year: 2000,
broken: true,
}
//create function to take in this object and print out some properties of it.
const printVehicle = (vehicle: { name: string; year: number; broken: boolean }): void => {
console.log(`Name: ${vehicle.name}`)
console.log(`Year: ${vehicle.year}`)
console.log(`Broken? ${vehicle.broken}`)
}
printVehicle(oldCivic)
// Name: civic
// Year: 2000
// Broken? true
The problem is { name: string, year: number, broken: boolean }
is very long...which is not the best.
We can improve this file by using an interface.
Fixing long type annotations with Interfaces
creating an interface
- at the top of the page, create an interface.
- use keyword
interface
- capitalize the name of the interface (
Vehicle
) - use a generic name.
- then list out the properties below.
interface Vehicle {
name: string
year: number
broken: boolean
}
in order to be a Vehicle, you must have a name which is a string, year which is a number, and broken status that is a boolean.
Now you can delete the LONG annotation { name: string, year: number, broken: boolean }
and replace it with the interface Vehicle
.
interface Vehicle {
name: string
year: number
broken: boolean
}
const oldCivic = {
name: 'civic',
year: 2000,
broken: true,
}
const printVehicle = (vehicle: Vehicle): void => {
console.log(`Name: ${vehicle.name}`)
console.log(`Year: ${vehicle.year}`)
console.log(`Broken? ${vehicle.broken}`)
}
printVehicle(oldCivic)
Typescript took a look at the arg oldCivic
and confirms it contains all the appropriate names and values for each.
If you changed the value for broken, you would get an error. See example below:
const oldCivic = {
name: 'civic',
year: 2000,
broken: 1,
}
We get an error:
const oldCivic: {
name: string;
year: number;
broken: number;
}
Argument of type '{ name: string; year: number; broken: number; }' is not assignable to parameter of type 'Vehicle'.
Types of property 'broken' are incompatible.
Type 'number' is not assignable to type 'boolean'.
Interface Syntax
When we create an interface, must include the properties (name
) and their expected values (string
). But we are not limited to primitive values.
We can express any type we want to.
For example, what if we want to use an instance of a date?
interface Vehicle {
name: string
year: Date // chance the value here
broken: boolean
}
const oldCivic = {
name: 'civic',
year: new Date(), // update the value here
broken: true,
}
Can also use functions:
interface Vehicle {
name: string
year: Date
broken: boolean
summary(): string // add () and what you expect the function to return
}
const oldCivic = {
name: 'civic',
year: new Date(),
broken: true,
summary(): string {
return `Name is ${this.name}`
},
}
const printVehicle = (vehicle: Vehicle): void => {
console.log(vehicle.summary())
}
printVehicle(oldCivic) // Name is civic
Now, our function is only accessing one property of the vehicle. We are not accessing the properties such as year, broken. Only the summary()
function. So do we need to include them in the Vehicle
interface?
interface Vehicle {
name: string
year: Date
broken: boolean
summary(): string
}
NO. We can delete them.
interface Vehicle {
// this will still work
summary(): string
}
const oldCivic = {
name: 'civic',
year: new Date(),
broken: true,
summary(): string {
return `Name is ${this.name}`
},
}
const printVehicle = (vehicle: Vehicle): void => {
console.log(vehicle.summary())
}
printVehicle(oldCivic)
Now that our interface only has one property, does this name make sense? No. It should be more broad.
interface Reportable {
summary(): string
}
const oldCivic = {
name: 'civic',
year: new Date(),
broken: true,
summary(): string {
return `Name is ${this.name}`
},
}
const printVehicle = (vehicle: Reportable): void => {
console.log(vehicle.summary())
}
printVehicle(oldCivic)
we are saying that anything considered
Portable
can return a string with asummary()
. This is much more broad and reuseable.
Now the only thing that the function printVehicle
is doing is providing the name of the vehicle. Maybe we can make this more generic.
const printSummary = (item: Reportable): void => {
console.log(item.summary())
}
printSummary(oldCivic)
Code Reuse with Interfaces
By making the interface more generic, we can use this interface with a completely different object.
interface Reportable {
summary(): string
}
//new object
const drink = {
color: 'brown',
carbonated: true,
sugar: 40,
summary(): string {
return `My drink has ${this.sugar} grams of sugar`
},
}
const oldCivic = {
name: 'civic',
year: new Date(),
broken: true,
summary(): string {
return `Name is ${this.name}`
},
}
const printSummary = (item: Reportable): void => {
console.log(item.summary())
}
printSummary(oldCivic) // Name is civic
printSummary(drink) // My drink has 40 grams of sugar
So both our oldCivic
and drink
both have a summary()
function tied to them. These two object represent VERY different things. However, they both have a property called summary, that return a string, BOTH of them are type reportable.
Because of this, we can call
printSummary()
on both of them and have them return different things!.
We can use a single interface to describe the properties of very different objects.
This encourages us to write generic functions.
Summary
General Strategy for Reuseable Code in TS
- create functions that accept arguments that are types with interfaces
- Objects/classes can decide to 'implement' a given interface to work with a function