Based on the Stack Overflow Developer survey in 2018, TypeScript is more "loved" as a programming language than JavaScript. The reason TypeScript is so loved amongst JavaScript developers is because adding types to JavaScript allows you to spot errors before running your code. The errors provided by the TypeScript compiler will give a good indication of how an error can be fixed. Adding types to JavaScript also allows code editors to provide some more advanced features, such as code completion, project-wide refactoring, and automatic module importing.
Learning TypeScript might seem intimidating if you come to think of it as a completely new programming language. However, TypeScript is just an added layer to JavaScript and you by no means have to know every bit of syntax that comes along with TypeScript before you can start using it. TypeScript allows you to easily convert a JavaScript file by changing the file extension from .js to .ts and all the code will compile properly as TypeScript. You can configure TypeScript to be more restrictive if you want to enforce a larger percentage of type coverage in your TypeScript files, but that can be done once you get more familiar with the language.
This article aims to bring you up to speed with around 95% of the scenarios you will typically encounter in a standard TypeScript project. For that last 5%, well, Google is your friend and I've added links to useful TypeScript resources at the bottom of the article.
Setting Up TypeScript
Of course, to begin writing TypeScript that compiles correctly, a properly configured development environment is required.
1. Install the TypeScript compiler
To start off, the TypeScript compiler will need to be installed in order to convert TypeScript files into JavaScript files. To do this, TypeScript can either be installed globally (available anywhere in your file system) or locally (only available at the project level).
1# NPM Installation Method23npm install --global typescript # Global installation4npm install --save-dev typescript # Local installation56# Yarn Installation Method78yarn global add typescript # Global installation9yarn add --dev typescript # Local installation
2. Make sure your editor is setup to support TypeScript
You'll want to make sure your editor is properly configured to work with TypeScript. For example, you may need to install a plugin (such as atom-typescript if using the atom editor), in order to fully take advantage of TypeScript in your editor. If using VS Code, TypeScript support is built-in, so there are no extensions required 😎.
3. Create a tsconfig.json file
A tsconfig.json file is used to configure TypeScript project settings. The tsconfig.json file should be put in the project's root directory. The file allows you to configure the TypeScript compiler with different options.
You can have the tsconfig.json contain an empty JSON object if you just want to get TypeScript to work, but if you need the TypeScript compiler to behave differently (such as output transpiled JavaScript files in a specific output directory), you can read more about which settings can be configured.
Note: You can also run the tsc --init to generate a tsconfig.json file with some default options set for you and a bunch of other options commented out
4. Transpile TypeScript to JavaScript
In order to transpile your TypeScript code to JavaScript, the tsc command needs to be run in the terminal. Running tsc will have the TypeScript compiler search for the tsconfig.json file which will determine the project's root directory as well as which options to use when compiling the TypeScript and transpiling .ts files to .js files.
To quickly test that the setup works, you can create a test TypeScript file and then run tsc in the command line and see if a JavaScript file is generated beside the TypeScript file.
For example, this TypeScript file…
1const greeting = (person: string) => {2 console.log('Good day ' + person);3};45greeting('Daniel');
Should transpile to this JavaScript file…
1var greeting = function(person) {2 console.log('Good day ' + person);3};45greeting('Daniel');
If you'd like for the TypeScript compiler to watch for changes in your TypeScript files and automatically trigger the transpilation of .ts to .js files, you can run the tsc -p. command in your project's repository.
In VS Code, you can use ⌘⇧B to bring up a menu that can run the transpiler in either normal or watch modes (tsc:build or tsc:watch, respectively).
Understanding Static and Dynamic Types
JavaScript comes with 7 dynamic types:
- Undefined
- Null
- Boolean
- Number
- String
- Symbol
- Object
The above types are called dynamic since they are used at runtime.
TypeScript brings along static types to the JavaScript language, and those types are evaluated at compile time (without having to run the code). Static types are what predict the value of dynamic types and this can help warn you of possible errors without having to run the code.
Basic Static Types
Alright, let's dive into the syntax of TypeScript. What follows are the most commonly seen types in TypeScript.
Note: I've left out the never and object types since, in my experience, they are not very commonly used.
boolean
The simple true
and false
values you've come to know and love.
1let isAwesome: boolean = true;
string
Textual data surrounded in either single quotes ('
), double quotes ("
), or back ticks (```).
1let name: string = 'Chris';2let breed: string = 'Border Collie';
If using back ticks, the string is called a template literal and expressions can be interpolated within them.
1let punchline: string = 'Because it was free-range.';2let joke: string = `3 Q: Why did the chicken cross the road?4 A: ${punchline}5`;
number
Any floating point number is given the type of number
. The four types of number literals that are supported as part of TypeScript are decimal, binary, octal and hexadecimal.
1let decimalNumber: number = 42;2let binaryNumber: number = 0b101010; // => 423let octalNumber: number = 0o52; // => 424let hexadecimalNumber: number = 0x2a; // => 42
Note: If the binary, octal, and hexadecimal numbers confuse you, you're not alone.
array
Array types in TypeScript can be written in two ways. The first way requires that []
be postfixed to the type of elements that are found in the array.
1let myPetFamily: string[] = ['rocket', 'fluffly', 'harry'];
The alternative way to write Array
types is to use Array followed by the type of elements that are found in the array (within angle brackets).
1let myPetFamily: Array<string> = ['rocket', 'fluffly', 'harry'];
tuple
A tuple
is an array that contains a fixed number of elements with associated types.
1let myFavoriteTuple: [string, number, boolean];23myFavoriteTuple = ['chair', 20, true]; // ✅4myFavoriteTuple = [5, 20, true]; // ❌ - The first element should be a string, not a number
enum
An enum
is a way to associate names to a constant value, which can be either a number or a string. Enums are useful when you want to have a set of distinct values that have a descriptive name associated with it.
By default, enums are assigned numbers that start at 0
and increase by 1
for each member of the enum.
1enum Sizes {2 Small,3 Medium,4 Large,5}67Sizes.Small; // => 08Sizes.Medium; // => 19Sizes.Large; // => 2
The first value can be set to a value other than 0
.
1enum Sizes {2 Small = 1,3 Medium,4 Large,5}67Sizes.Small; // => 18Sizes.Medium; // => 29Sizes.Large; // => 3
Enums are by default assigned numbers, however, string values can also be assigned to an enum.
1enum ThemeColors {2 Primary = 'primary',3 Secondary = 'secondary',4 Dark = 'dark',5 DarkSecondary = 'darkSecondary',6}
any
If the type of a variable is not known and we don't want the type checker to complain at compilation time, then the type of any
can be used.
1let whoKnows: any = 4; // assigned a number23whoKnows = 'a beautiful string'; // can be reassigned to a string4whoKnows = false; // can be reassigned to a boolean
any
will likely frequently be used when starting out with TypeScript. However, it's best to try to reduce the usage of any since the usefulness of TypeScript decreases when the compiler isn't aware of the types associated with variables.
void
When there is no type associated with something, the void
type should be used. It is most commonly used when specifying the return value of a function that doesn't return anything.
1const darkestPlaceOnEarth = (): void => {2 console.log('Marianas Trench');3};
null and undefined
Both null
and undefined
correspond to the types of the null
and undefined
values you might see in JavaScript. These types aren't very useful when used on their own.
1let anUndefinedVariable: undefined = undefined;2let aNullVariable: null = null;
By default the null
and undefined
types are subtypes of all other types, meaning that a variable of type string can be assigned a value of null
or undefined
. This is often undesirable behavior and thus it's usually recommended to set the strictNullChecks
compiler option in a tsconfig.json
file to true
. Setting the strictNullChecks
option to true
makes it so that null
and undefined
need to be explicitly set as a type for a variable.
Type Inference
Fortunately, you don' have to specify types absolutely everywhere in your code because TypeScript has what is called Type Inference. Type inference is what the TypeScript compiler uses to automatically determine types.
Basic Type Inference
TypeScript can infer types during variable initialization, when default parameter values are set, and while determining function return values.
1// Variable initialization2let x = 10; // x is given the number type
In the above example, x
is assigned a number, TypeScript associates the x
variable with a type of number
.
1// Default function parameters2const tweetLength = (message = 'A default tweet') => {3 return message.length;4};
In the above example, the message
parameter is assigned a default value which is of type string
, so therefore the TypeScript compiler infers that message
is of type string
and therefore doesn't throw a compilation error when the length
property is being accessed.
1function add(a: number, b: number) {2 return a + b;3}45const result = add(2, 4);67result.toFixed(2); // ✅8result.length; // ❌ - length is not a property of number types
In the above example, since TypeScript is told that both parameters to the add
function have a type of number
, it can infer that the return type will also be a number
.
Best Common Type Inference
When a type is being inferred from multiple possible types, TypeScript uses a Best Common Type algorithm to pick a type that works with all the other candidates.
1let list = [10, 22, 4, null, 5];23list.push(6); // ✅4list.push(null); // ✅5list.push('nope'); // ❌ - type 'string' is neither of type 'number' or 'null'
In the above example, the array is composed of both number
and null
types, and therefore TypeScript expects only number
and null
values to be a part of the array.
Type Annotation
When the Type Inference system is not enough, you will need to declare types on variables and objects.
Basic Types
All the types introduced in the Basic Static Types
section can be declared using a :
followed by the name of the type.
1let aBoolean: boolean = true;2let aNumber: number = 10;3let aString: string = 'woohoo';
Arrays
As shown in the section talking about the array
type, arrays can be annotated one of two ways.
1// First method is using the square bracket notation2let messageArray: string[] = ['hello', 'my name is fred', 'bye'];34// Second method uses the Array keyword notation5let messageArray: Array<string> = ['hello', 'my name is fred', 'bye'];
Interfaces
One way to put together multiple type annotations is by using an interface.
1interface Animal {2 kind: string;3 weight: number;4}56let dog: Animal;78dog = {9 kind: 'mammal',10 weight: 10,11}; // ✅1213dog = {14 kind: true,15 weight: 10,16}; // ❌ - kind should be a string
Type Alias
To make things confusing, TypeScript also allows you to specify multiple type annotations using a type alias.
1type Animal = {2 kind: string;3 weight: number;4};56let dog: Animal;78dog = {9 kind: 'mammal',10 weight: 10,11}; // ✅1213dog = {14 kind: true,15 weight: 10,16}; // ❌ - kind should be a string
What seems to be the best practice in terms of using an interface or a type alias is that you should generally just pick either interface
or type
in your codebase and be consistent. However, if writing a 3rd party public API that can be used by others, use an interface
type.
If you want to get a more detailed comparison between the type alias and an interface, I would recommend this article by Martin Hochel.
Inline Annotations
Instead of creating a re-usable interface, it might be more appropriate to annotate a type inline instead.
1let dog: {2 kind: string;3 weight: number;4};56dog = {7 kind: 'mammal',8 weight: 10,9}; // ✅1011dog = {12 kind: true,13 weight: 10,14}; // ❌ - kind should be a string
Generics
There are situations where the specific type of a variable doesn't matter, but a relationship between the types of different variables should be enforced. For those cases, generic types should be used.
1const fillArray = <T>(len: number, elem: T) => {2 return new Array<T>(len).fill(elem);3};45const newArray = fillArray<string>(3, 'hi'); // => ['hi', 'hi', 'hi']67newArray.push('bye'); // ✅8newArray.push(true); // ❌ - only strings can be added to the array
The above example has a generic type T
that corresponds to the type of the second argument passed to the fillArray
function. The second argument passed to the fillArray
function is a string, and therefore the created array will have all of its elements set to have a type of string
.
It should be noted that it is by convention that single letters are used for generic types (e.g. T
or K
). However, there is nothing stopping your from using more descriptive names for your generic types. Here is the above example with a more descriptive name for the supplied generic type:
1const fillArray = <ArrayElementType>(len: number, elem: ArrayElementType) => {2 return new Array<ArrayElementType>(len).fill(elem);3};45const newArray = fillArray<string>(3, 'hi'); // => ['hi', 'hi', 'hi']67newArray.push('bye'); // ✅8newArray.push(true); // ❌ - only strings can be added to the array
Union Type
In scenarios where a type can be one of multiple types, a union type is used by separating the different type options with a |
.
1// The `name` parameter can be either a string or null2const sayHappyBirthdayOnFacebook = (name: string | null) => {3 if (name === null) {4 console.log('Happy birthday!');5 } else {6 console.log(`Happy birthday ${name}!`);7 }8};910sayHappyBirthdayOnFacebook(null); // => "Happy birthday!"11sayHappyBirthdayOnFacebook('Jeremy'); // => "Happy birthday Jeremy!"
Intersection Type
An intersection type uses the &
symbol to combine multiple types together. This is different than the union type, as a union type says "the resulting type is one of the listed types" whereas the intersection type says "the resulting type is the combination of all listed types".
1type Student = {2 id: string;3 age: number;4};56type Employee = {7 companyId: string;8};910let person: Student & Employee;1112person.age = 21; // ✅13person.companyId = 'SP302334'; // ✅14person.id = '10033402'; // ✅15person.name = 'Henry'; // ❌ - name does not exist in Student & Employee
Tuple Type
Tuples are annotated using a :
followed by a comma separated list of types within square brackets.
1let list: [string, string, number];23list = ['apple', 'banana', 8.75]; // ✅4list = ['apple', true, 8.75]; // ❌ - the second argument should be of type string5list = ['apple', 'banana', 10.33, 3]; // ❌ - the tuple specifies a length of 3, not 4
Optional Types
There may be instances where a function parameter or object property is optional. In those cases, a ?
is used to denote these optional values.
1// Optional function parameter2function callMom(message?: string) {3 if (!message) {4 console.log('Hi mom. Love you. Bye.');5 } else {6 console.log(message);7 }8}910// Interface describing an object containing an optional property11interface Person {12 name: string;13 age: number;14 favoriteColor?: string; // This property is optional15}
Useful Resources
For the parts of TypeScript that weren't covered in this article, I recommend the following resources.
TypeScript Handbook (Official TypeScript docs)
TypeScript Deep Dive (Online TypeScript Guide)
Understanding TypeScript's Type Annotation (Great introductory TypeScript article)