ProgrammingTypescript

Overview of TypeScript Types

A brief look at all the major types available in TypeScript (3.1+)

In this article, I’m going to be covering TypeScript types you will want to be using on a day to day basis. Please note, I’m not really going to touch on custom types or interfaces. Since the launch of TypeScript, they have added quite a few new features this article will look at all of the major types up to version 3.1.

What are data types? Though various programming languages use them differently, a data type is a classification that describes the type of variable stored in memory. Be it a number, string or even null. These are used to help both the programmer and the program understand what operations are intended for the variable. For instance, you wouldn’t expect to mathematically add two string together, nor would you expect to divide a true or false.

Boolean Type

To start with, lets look at the simplest datatype.

let myVar1: boolean = true; // ok
let myVar2: boolean = false; // ok
let myVar3: boolean = 1; // error
let myvar4: boolean = 'hi'; // error
...

Number Type

JavaScript handles double, floating point, integer, long, short all the same. This also includes hexadecimal numbers, binary and octal.

let age: number = 37; // ok
let lat: number = 12.34567 // ok
let hexadecimal: number = 0xff22aa; // ok
let binary: number = 0b1010; // ok
let octal: number = 0o744; // ok
age.toFixed(); // ok
age.toString(); // ok
age.length; // error
let name1: string = 'John'; // ok
let name2: string = "Smith"; // ok
let name3: string = `Ffion`; // ok
name1.length; // ok
name1.toString(); // ok
name1.toFixed(); // error

String Type

Concatenation without coercion is possible in a few different ways providing both variables are of type string.

let firstName: string = 'John';
let lastName: string = 'Smith';
let name: string = firstName + ' ' + lastName; // John Smith
let name2: string = firstName + " " + lastName; // John Smith
let name3: string = `${firstName} ${lastName}`; // John Smith

Array Type

In general practice, any native type, like ones found here, would be typed into an array using square brackets below.

let array1: string[]; // array of strings - ['john', 'smith']
let array2: number[]; // array of numbers - [1, 2, 3]
let array3: boolean[]; // array of booleans - [false, true, true, false]
let array4: [number[]] // nested array of numbers - [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

If you have any custom types or interfaces, then you’d usually precede the type declaration with a greater and less than symbol. See the following.

type Person = {
	name: string, age: number
}
interface IAddress {
	line1: string, zipcode: number
}
let people: Array<Person> = [];
let addresses: Array<IAddress> = [];

Tuple Type

A tuple is a way to describe each type of element in an array, in a specific order. Passing the wrong types into the array at any position would result in an error.

// decalre tuple
let myArray: [number, string, number];
myArray = [1, 'hello', 12.456] // ok
myArray = [1, 2, 3] // errors on second element
myArray = [1, 'hi']; // errors because the third element is missing

It is also possible to use optional types and union types in a tuple.

let myArray: [number, string | number, number?];
myArray = [1, 'hello', 12.456] // ok
myArray = [1, 'hi']; // ok
myArray = [1, 123]; // ok

Enum Type

Enum, short for enumeration is a neater way of mapping a series of values to integers. This can be done either automatically, or defined at instantiation.

enum TrafficLight {
    red,
	amber,
	green
}
// -> red = 0, amber = 1, green = 2
enum TrafficLight {
	red = 1,
	amber = 2,
	green = 3
}
// -> red = 1, amber = 2, green = 3

Manually populating an enum is also possible, as well as partially populating one.

Note – the enum will fill the indexes in an auto increment starting at the lowest assigned value, skipping any you have already used.

enum trafficLight {
	red = 51,
	amber = 7,
	green
}
// -> red = 51, amber = 7, green = 8

Any Type

Caution, should be avoided where ever possible!

Using type any tells TypeScript to not perform any type assertions what so ever. Which kind of defeats the purpose of TypeScript. There are some rare cases, where you may not actually know the type or structure of an object, but it is generally considered bad practice.

let randomVar: any = '123';
randomVar.toString(); // may exist at runtime, but compiler doesn't check
randomVar.toFixed(); // may exist at runtime, but compiler doesn't check
randomVar.filter(...); // may exist at runtime, but compiler doesn't check

Void Type

Void is a fairly simple one. It’s a variable type that can only ever be null or undefined. This is used almost exclusively when setting a return type of a function.

private doNothing(): void {
	// I'm not going to return anything
}
let result = doNothing();
// notice that result has a type of void (undefined or null)

Null & Undefined Types

Types null and undefined are sub-types of all other types. Meaning you can either create an ’empty’ variable, or clear one up for garbage collection.

let myAge: number = null; //ok
let myName: string = undefined; //ok
...

However, the strictNullChecks flag in the tsconfig file actually prevents you from doing this. The reason is, lets say you created a variable of type string, but assigned it null. When you pass the variable into a function, if the parameter type is string, then it won’t complain, despite the fact it’s actually a null / undefined variable.

let myName: string = null;
function printName(name: string) {
	console.log(`hello, ${name}`);
}
printName(myName); // will compile and run, but will console -> hello, null OR hello, undefined

Never Type

Never is an amazing feature added in TypeScript version 2.0. It has two main functions. To throw an error when there us unreachable code, and type guards that are never true. Looking at the first case:

private static generateToken(payload: Object, remember: boolean): string | Error {
	let token: string;
	try {
		token = jwt.sign(payload, secret, {
			expiresIn: (remember) ? '14d' : '1d'
		});
		// can return a string
		return token;
	} catch (error) {
		// can also return an Error
		return new Error(error);
	}
	// any code here is unreachable
	console.log('hello'); // would cause an error, as we have said this function can not have unreachable code.
}

Case 2, where we can check against variables with impossible types.

private static doThisThing(value: number | string) {
	if(typeof value === 'string') {
		// in this type assertion, value will have type string
	} else if(typeof value === 'number') {
		// in this type assertion, value will have type never
	} else {
		// here, value MUST have a never value, as it can only be a number or a string as declared above, but we have already asserted for both of those
	}
}

Note – function declarations cannot infer a never type

// will return type void
function doThis() {
	throw new Error('nope');
}
// will return type never
const doThat = function() {
	throw new Error('nope');
}

Object Type

The type of object in TypeScript can be a little annoying, I find. Especially when using third party API’s, or plugins. You might accept a parameter as type object, but will be unable to access any of it’s properties without giving the object any property types. For example:

let myObj: object = {}; // ok
myObj.name = 'John' // Error: Property name does not exist on type object
let position:object = Pixi.getGlobalPosition(); // call a third party plugin that returns an object
position.x // Error: Property x does not exist on type object
position.y // Error: Property y does not exist on type object

To get around this we can use a variety of methods. Depending on the scope of your object, you may want to use interfaces. If not interfaces, then possibly a type. And if it’s only ever locally scoped to one function, you can define the object itself.

let position:{x: number, y: number} = Pixi.getGlobalPosition(); // call a third party plugin that returns an object
position.x // ok
position.y // ok
// create a new type, that is locked to the current scope
type position = {
	x: number,
	y: number
}
let position:position = Pixi.getGlobalPosition(); // call a third party plugin that returns an object
position.x // ok
position.y // ok
decalre interface IPosition {
	x: number;
	y: number;
}
class Main {
	public static updatePosition() {
		let position:IPosition = Pixi.getGlobalPosition();
		position.x // ok
		position.y // ok
	}
}

A few notes on using interfaces. Exporting them in a .d.ts file, you can include the reference path at the top of the file and have globally accessible types; helping keeping neat code and having lots of objects mapped, especially when they are quite large.

Unknown Type

Released in version 3.0, unknown is a new top type that sort of replaces the type any. In a nutshell it will allow to restrict operations on any variable without first checking it’s type; known as control flow or, narrowing.

type name = unknown | string; // has type of unknown
let myName = 'nick'; // still has type of unknown
myName.length; // Error: Object is of type 'unknown'
if(typeof myName === 'string') {
	myName.length // ok, as we've now narrowed it's type
}

This is also true for function parameters.

function doMath(x: unknown) {
	x + x; // error
	x * 2; // error
	...
}
function getSomeProperty(y: unknown) {
	y.name; // error
	y[1]; // error
	y(); // error
	new y(); // error
}

I will cover unknown in greater detail in another post as it can get a little complicated and convoluted.

That’s it for an overview of TypeScript posts. Feel free to comment and let me know what you thought!

Related Articles

Leave a Reply

Back to top button