TypeScript 2: Union, Narrowing & Interface
esther
Dive a bit deeper! 🧐
Union Type
let answer: any; // any type
let typedAnswer: string | number; // union type
As a result of supporting a union of multiple types, TypeScript allows you to access properties that are common among the member types without any error.
let typedAnswer: string | number[] = 'Codecademy';
console.log(typedAnswer.length); // Prints 10
console.log(typedAnswer.match('my')); // Prints ["my"]
typedAnswer = [3, 5, 1];
console.log(typedAnswer.length); // Prints 3
// The .match method will not work for an array type
console.log(typedAnswer.match(5)); // Error: Proper
Union of Array Types
const getLength = (arr: string[] | number[]) => {
return arr.length;
}
let strArr: string[] = ["foo", "bar", "baz"];
let numArr: number[] = [1, 2, 3, 4, 5];
console.log(getLength(strArr)); // Output: 3
console.log(getLength(numArr)); // Output: 5
Union Type Narrowing
Use the typeof
operator with the variable name and compare it with the type you expect for the variable.
const choices: [string, string] = ['NO', 'YES'];
const processAnswer = (answer: number | boolean) => {
if (typeof answer === 'number') {
console.log(choices[answer]);
} else if (typeof answer === 'boolean') {
if (answer) {
console.log(choices[1]);
} else {
console.log(choices[0]);
}
}
}
processAnswer(true); // Prints "YES"
processAnswer(0); // Prints "NO"
Function Return Union Type
const calculateArea = (shape: 'circle' | 'rectangle', dimensions: number[]): number | null => {
if (shape === 'circle' && dimensions.length === 1) {
const radius = dimensions[0];
return Math.PI * radius * radius; // return type is number
} else if (shape === 'rectangle' && dimensions.length === 2) {
const length = dimensions[0];
const width = dimensions[1];
return length * width; // return type is number
} else {
return null; // return type is null
}
};
const circleArea: number | null = calculateArea('circle', [5]);
console.log(circleArea); // Prints "78.53981633974483"
const rectangleArea: number | null = calculateArea('rectangle', [4, 5]);
console.log(rectangleArea); // Prints "20"
const invalidArea: number | null = calculateArea('circle', [3, 4]);
console.log(invalidArea); // Prints "null"
Union of Literal Types
type RPS = 'rock' | 'paper' | 'scissors' ;
const play = (choice: RPS): void => {
console.log('You: ', choice);
let result: string = '';
switch (choice) {
case 'rock':
result = 'paper';
break;
case 'paper':
result = 'scissors';
break;
case 'scissors':
result = 'rock';
break;
}
console.log('Me: ', result);
}
const number = Math.floor(Math.random()*3);
let choices: [RPS, RPS, RPS] = ['rock', 'paper', 'scissors'];
play(choices[number]);
Type Narrowing
Using typeof
to guard the variable type, including number
, string
and boolean
.
const choices: [string, string] = ['NO', 'YES'];
const processAnswer = (answer: number | boolean) => {
if (typeof answer === 'number') {
console.log(choices[answer]);
} else if (typeof answer === 'boolean') {
if (answer) {
console.log(choices[1]);
} else {
console.log(choices[0]);
}
}
}
processAnswer(true); // Prints "YES"
processAnswer(0); // Prints "NO"
Using if
statement to help
function formatAge(age: number | string) {
if (typeof age === 'number') {
return age.toFixed(); // age must be a number
}
return age; // age must not be a number
}
console.log(formatAge(3.5)); // Prints "4"
console.log(formatAge('3.5')); // Prints "3.5"
Interface Type
Interface can only define objects.
interface Publication {
isbn: string;
author: string;
publisher: string;
}
The difference between Type alias and Interface:
// Type alias
type ISBN = number | string;
type PublicationT = {
isbn: ISBN;
author: string;
publisher: string;
}
// Interface can only define an object type
interface PublicationI {
isbn: ISBN;
author: string;
publisher: string;
}
Interface for Classes
interface Shape {
area: number;
computeArea: () => number;
}
// Rectangle class implements the Shape interface
class Rectangle implements Shape {
width: number;
height: number;
area: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
this.area = this.computeArea();
}
computeArea = (): number => {
return this.width * this.height;
}
}
let target = new Rectangle(4, 5);
console.log(target.area); // Prints "20"
Nested Interface
interface Address {
street: string;
city: string;
state: string;
zip: string;
}
interface Person {
name: string;
age: number;
address: Address;
}
class Customer implements Person {
name: string;
age: number;
address: Address;
constructor(name: string, age: number, address: Address) {
this.name = name;
this.age = age;
this.address = address;
}
}
const myAddress: Address = {
street: '123 Main St',
city: 'Anytown',
state: 'CA',
zip: '12345'
};
const customer = new Customer('John Doe', 30, myAddress);
console.log(customer.name); // Output: 'John Doe'
console.log(customer.address.street); // Output: '123 Main St'
Interface Inheritance
interface Animal {
name: string;
move(distance: number): void;
}
interface Bird extends Animal {
fly(): void;
}
class Eagle implements Bird {
name: string;
constructor(name: string) {
this.name = name;
}
move(distance: number) {
console.log(`${this.name} flew ${distance} meters.`);
}
fly() {
console.log(`${this.name} is flying.`);
}
}
const eagle = new Eagle('Bald Eagle');
eagle.move(1000); // Prints "Bald Eagle flew 1000 meters."
eagle.fly(); // Prints "Bald Eagle is flying."
Interface Index Signature
In TypeScript, you can define an object property whose name is not known in advance or whose name can be a number instead of a string.
interface FruitPrices {
[fruit: string]: number;
}
const fruitPrices: FruitPrices = { apple: 1.99, banana: 0.99, orange: 2.49 };
interface Sales {
[fruit: string]: number;
}
const sales: Sales = { apple: 10, banana: 20, orange: 15 };
// Calculate the total revenue from fruit sales
let revenue = 0;
for (let fruit in sales) {
if (fruit in fruitPrices) {
revenue += sales[fruit] * fruitPrices[fruit];
}
}
console.log(Total revenue: $${revenue.toFixed(2)});
// Prints "Total revenue: $44.55"
Interface Optional Properties
nterface Profile {
name: string;
age: number;
hobbies?: string[];
}
const teacher: Profile = {name: 'Tom Sawyer', age: 18};