TypeScript has rapidly grown in popularity among web developers in recent years. This typed superset of JavaScript adds optional static typing to the language, making it easier to build robust, scalable applications.
In this comprehensive guide, we'll demystify TypeScript and help you understand how to use it by walking through practical examples. Whether you're new to TypeScript or looking to go deeper into what it offers, read on to uncover the power of typed JavaScript.
What is TypeScript?
TypeScript is an open-source programming language developed by Microsoft that builds on JavaScript by adding static type definitions.
JavaScript is a dynamically typed language, meaning variables can change types at any time. This flexibility comes at the cost of reliability — runtime errors and bugs can emerge if types are used incorrectly.
TypeScript adds optional type annotations that are enforced during development and stripped out on compilation to plain JavaScript. This brings the benefits of static typing (e.g. improved code completion, debugging, and reliability) while maintaining compatibility with vanilla JavaScript.
The TypeScript compiler (tsc) converts .ts files into regular .js files that can run anywhere JavaScript runs: browsers, Node.js, Deno and more. TypeScript doesn't add any performance overhead compared to native JavaScript.
In a sense, TypeScript takes JavaScript and gives it compiler-enforced type checking and tooling. The core JavaScript language remains unchanged.
Why Use TypeScript?
Here are some of the key benefits of using TypeScript:
- Prevent Bugs: Type annotations can catch many common errors during development.
- Improve Tooling: Editors and IDEs provide better code completion, intellisense, refactoring and more with type information.
- Enable New Features: TypeScript supports emerging JS features years before browsers adopt them.
- Support Large Teams: Explicit types enable stronger collaboration and refactoring across large codebases.
- Gradual Adoption: TypeScript integrates perfectly with existing JavaScript code. You can introduce it incrementally.
- Future Proof Code: Type annotations document intentions and prevent drifting code over time.
While optional, taking advantage of TypeScript's static analysis pays dividends in long term productivity and maintenance for most non-trivial JavaScript applications.
Core TypeScript Concepts
Now that you understand the benefits of TypeScript, let's tour some of the core concepts you'll encounter:
Static Types
TypeScript's compiler uses static types from type annotations to analyze code for errors. These types provide more information to the compiler than it can infer through static analysis alone.
For example:
//ts
function add(x: number, y: number) {
return x + y;
}
add('5', 10); // Argument of type 'string' is not assignable to parameter of type 'number'
Here the compiler uses the type annotations `number` to catch a bug early — we're passing a string when the function expects numbers.
TypeScript has several built-in types like `string`, `number
`, `boolean
`, etc. You can also define custom types and interfaces to maximize code quality and consistency.
Structural vs Nominal Typing
TypeScript follows structural typing, unlike Java and others using nominal typing.
This means two types are considered compatible if their structure matches, regardless of their name. For example:
//ts
interface Point {
x: number;
y: number;
}
function print(point: Point) {
// ...
}
let obj = { x: 1, y: 2 };
print(obj); // OK, obj shape matches Point
```
The `obj
` doesn't explicitly implement the `Point
` interface, but matches its structure, so TypeScript allows passing it to `print
`.
Interface
An interface is a declaration of a type shape. It defines the structue and types of properties an object might have.
For example:
//ts
interface User {
name: string;
id: number;
}
function printUser(user: User) {
// ...
}
Any object passed to `printUser
` must match the `User` interface's shape. Interfaces are useful for defining contracts between different parts of your codebase.
Union Types
A union type describes a type that can be one of several types. For example:
//ts
type WindowStates = "open" | "closed" | "minimized";
Now `WindowStates
` accepts strings `"open
"`, `"closed
"` or `"minimized
"`. Union types are useful for modelling situations with limited known variants.
Generics
Generics provide variables to types. A common example is an array:
//ts
const names = ["Alice", "Bob"]; // string[]
const values = [1, 2]; // number[]
Instead of either being `any[]
` or requiring explicit types, arrays can declare their variable type:
//ts
function print<T>(arr: T[]) {
// ...
}
print<string>(names);
print<number>(values);
Here `print
` declares a type variable `T
` that's provided on each call. Generics adds reusability where explicit types would be verbose.
This covers the most useful core concepts in TypeScript. The language also supports classes, modules, namespaces, enums, mixins and more. But understanding static types, structual vs nominal types, interfaces, union types and generics will give you a solid foundation.
Now let's go through building a sample project to see TypeScript in action...
Project: Todo App
Let's build a simple command line todo app to demonstrate TypeScript's capabilities.
The app will let you add, complete, and remove todos. Here are the steps:
1. Initialization
First, we'll create a new npm project and install TypeScript:
mkdir typescript-todo
cd typescript-todo
npm init -y
npm install -D typescript
The `-D` flag saves TypeScript as a dev dependency since we only need it at compile time, not runtime.
Next, initialize a `tsconfig.json` file:
npx tsc --init
This creates a basic config for the TypeScript compiler with defaults.
Finally, add two commands to `package.json` under scripts:
//json
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
2. Define Types
Let's start by defining types for a todo item:
// models/todo.ts
export interface Todo {
id: number;
text: string;
complete: boolean;
}
The `Todo` interface declares the shape of a todo item. We can import this anywhere to reuse the type information.
Next we'll define types for our state:
// types/state.ts
import { Todo } from "../models/todo";
export interface AppState {
todos: Todo[];
}
The application state is a single `todos
` array containing `Todo` objects.
3. Application Logic
With the core types in place, let's implement the key application logic:
// state.ts
import { AppState } from "./types/state";
export function addTodo(state: AppState, text: string): AppState {
return {
...state,
todos: [...state.todos, {
id: state.todos.length + 1,
text,
complete: false
}]
};
}
export function completeTodo(state: AppState, id: number) {
return {
...state,
todos: state.todos.map(todo => {
if (todo.id === id) {
return {...todo, complete: true};
} else {
return todo;
}
})
};
}
export function removeTodo(state: AppState, id: number) {
return {
...state,
todos: state.todos.filter(todo => todo.id !== id)
};
}
This implements pure functions that modify the app state. TypeScript can validate their use of the `AppState
` and `Todo` types we defined earlier.
For example, it will catch if we pass a string instead of a number id, or attempt to access a non-existent property like `todo.priority
`.
4. CLI UI
Now let's tie it together by implementing a basic command line UI:
// index.ts
import { AppState, addTodo, completeTodo, removeTodo } from "./state";
function displayTodos(appState: AppState) {
if (appState.todos.length === 0) {
console.log("All caught up!");
return;
}
appState.todos.forEach(todo => {
console.log(
`${todo.id}. [${todo.complete ? "X" : " "}] ${todo.text}`
);
});
}
function main() {
let appState: AppState = { todos: [] };
while (true) {
const input = prompt("Enter command: ");
const [command, arg] = input.split(" ");
if (command === "quit") {
break;
} else if (command === "add") {
appState = addTodo(appState, arg);
} else if (command === "complete") {
if (!arg) continue;
const id = parseInt(arg);
appState = completeTodo(appState, id);
} else if (command === "remove") {
if (!arg) continue;
const id = parseInt(arg);
appState = removeTodo(appState, id);
}
displayTodos(appState);
}
}
main();
This brings everything together into an interactive CLI todo app!
The key things to note are:
- Type annotations on `appState
` let us call state functions correctly
- `parseInt
` casts user input to numbers for type safety
- The `AppState
` type automatically updates as we add methods like `completeTodo
`
5. Build and Run
Finally, we can try it out:
npm run build
npm start
This will compile our TypeScript code to JS and run the `main` function.
Some example usage:
Enter command: add Buy milk
1. [ ] Buy milk
Enter command: add Schedule meeting
2. [ ] Schedule meeting
Enter command: complete 2
1. [ ] Buy milk
2. [X] Schedule meeting
Enter command: quit
And that's it! While simple, this shows how TypeScript can make building JavaScript applications more robust and enjoyable.
Key Takeaways
Some key things to remember about using TypeScript:
- Optional Typing: TypeScript doesn't force you to add types. You can be as explicit or implicit as you want.
- Gradual Adoption: TypeScript integrates perfectly with JavaScript. You can introduce it incrementally.
- Compiler Configuration: The `tsconfig.json` file controls how TypeScript compiles to JavaScript.
- Editor Integration: Editor plugins provide auto-complete, error checking and other support for TypeScript.
- Core Types: Understand built-in primitives like `string
`, `number
`, `boolean
`, etc and how to create object types like interfaces and union types.
- Type Inference: The compiler can infer types for variables, return values, etc when not explicitly provided.
TypeScript brings optional typing to JavaScript, enabling you to scale apps and prevent entire classes of runtime errors. The core language enhancements are relatively simple but deliver immense productivity and maintenance benefits.
Whether you're just getting started or are looking to use TypeScript more extensively, I hope this guide gave you a helpful introduction! Static types have become a game-changer for large scale JavaScript applications.
Let me know if you have any other topics you'd like to see covered. Happy coding!