TypeScript is a statically-typed language developed by Microsoft. It compiles to plain JavaScript and supports all ECMAScript features. To get started with TypeScript, install it via npm:
npm install -g typescript
Compile a TypeScript file with:
tsc filename.ts
ts-node
allows running TypeScript files directly without compiling:
npm install ts-node --save-dev
Run TypeScript files directly:
ts-node filename.ts
TypeScript includes several basic types:
let isDone: boolean = false;
let count: number = 42;
let name: string = "TypeScript";
let list: number[] = [1, 2, 3];
let tuple: [string, number] = ["hello", 42];
let anyValue: any = 5; // Avoid using 'any' when possible
let unknownValue: unknown; // Similar to 'any', but type-safe
TypeScript supports the same variable declarations as JavaScript:
let x = 10; // number
const pi = 3.14; // const
Functions can have parameter types and return types:
function add(a: number, b: number): number {
return a + b;
}
// Optional and default parameters
function greet(name: string, greeting?: string): string {
return greeting ? `${greeting}, ${name}!` : `Hello, ${name}!`;
}
Interfaces define object structures:
interface Person {
name: string;
age: number;
}
function printPerson(person: Person): void {
console.log(`Name: ${person.name}, Age: ${person.age}`);
}
TypeScript supports classes with constructor and inheritance:
class Animal {
constructor(public name: string) {}
makeSound() {
console.log("Some generic sound");
}
}
class Dog extends Animal {
makeSound() {
console.log("Woof!");
}
}
TypeScript can infer types based on context:
let message = "TypeScript"; // Type inferred as string
let numbers = [1, 2, 3]; // Type inferred as number[]
Generics allow flexible, reusable functions and classes:
function identity<T>(arg: T): T {
return arg;
}
class Box<T> {
constructor(private value: T) {}
getValue(): T {
return this.value;
}
}
Enums allow defining named constants:
enum Color {
Red,
Green,
Blue,
}
let myColor: Color = Color.Green;
TypeScript offers advanced types like union, intersection, and mapped types:
type StringOrNumber = string | number;
interface Person {
name: string;
age: number;
}
type PersonKeys = keyof Person; // "name" | "age"
Type assertions enable you to override TypeScript's inference:
let message: unknown = "Hello, TypeScript!";
let strLength: number = (message as string).length;
Type guards help narrow down types within conditional blocks:
function doSomething(value: string | number) {
if (typeof value === "string") {
console.log("It's a string!");
} else {
console.log("It's a number!");
}
}
TypeScript supports ECMAScript modules:
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
// app.ts
import { add } from "./math";
console.log(add(2, 3)); // Output: 5
TypeScript supports decorators, which are used to modify classes, methods, or properties:
function log(target: any, key: string) {
console.log(`Logged: ${key}`);
}
class MyClass {
@log
myMethod() {
// Method logic here
}
}
Type aliases provide a way to create custom names for types:
type Point = { x: number; y: number };
function distance(p1: Point, p2: Point): number {
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
return Math.sqrt(dx * dx + dy * dy);
}
String literal types allow you to define a set of possible string values:
type Direction = "up" | "down" | "left" | "right";
function move(direction: Direction): void {
// Move logic here
}
The non-null assertion operator (!
) tells TypeScript to consider a value as non-null, even if it's not specified in the type:
let name: string | null = getNameFromExternalSource();
let length: number = name!.length; // Asserting 'name' is not null
The keyof
operator and lookup types allow you to work with object keys more dynamically:
interface Person {
name: string;
age: number;
}
type PersonKeys = keyof Person; // "name" | "age"
function getProperty(obj: Person, key: PersonKeys): string {
return obj[key]; // Compiler knows this will be a string
}
Tuples are arrays with fixed-length and typed elements:
let coordinates: [number, number] = [10, 20];
let pair: [string, number] = ["answer", 42];
Namespaces help organize code and prevent naming collisions:
namespace MyNamespace {
export const PI = 3.14;
export function calculateCircumference(diameter: number): number {
return diameter * PI;
}
}
const diameter = 10;
console.log(`Circumference: ${MyNamespace.calculateCircumference(diameter)}`);
Triple-slash directives provide additional information to the TypeScript compiler:
/// <reference path="path/to/another-file.ts" />
namespace AnotherNamespace {
// Namespace content
}
TypeScript seamlessly integrates with modern asynchronous programming using async
and await
:
async function fetchData(url: string): Promise<string> {
const response = await fetch(url);
const data = await response.text();
return data;
}
Intersection types allow combining multiple types into one:
interface User {
id: number;
name: string;
}
interface Admin {
isAdmin: boolean;
}
type AdminUser = User & Admin;
Type predicates
allow you to define custom type guards:
function isString(value: unknown): value is string {
return typeof value === "string";
}
function processValue(value: unknown) {
if (isString(value)) {
console.log(value.toUpperCase());
} else {
console.log("Value is not a string.");
}
}
Mixins enable composition of classes from reusable components:
class Timestamped {
timestamp = new Date();
}
class Person {
constructor(public name: string) {}
}
type TimestampedPerson = Person & Timestamped;
Abstract classes provide a blueprint for other classes to inherit from:
abstract class Shape {
abstract area(): number;
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
area(): number {
return Math.PI * this.radius ** 2;
}
}
TypeScript's strict null checks (strictNullChecks
) help avoid null and undefined errors:
function divide(a: number, b: number): number {
if (b !== 0) {
return a / b;
} else {
throw new Error("Cannot divide by zero.");
}
}
The tsconfig.json
file allows configuring TypeScript compilation options:
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"strict": true,
"outDir": "dist"
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
ESLint can be integrated with TypeScript to enforce code quality:
npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
Create an .eslintrc.js
file:
module.exports = {
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
rules: {
// Your custom rules here
},
};
TypeScript's discriminated unions and type guards make handling different types in conditional blocks more intuitive:
interface Square {
kind: "square";
size: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Circle;
function area(shape: Shape): number {
switch (shape.kind) {
case "square":
return shape.size ** 2;
case "circle":
return Math.PI * shape.radius ** 2;
default:
throw new Error("Unknown shape!");
}
}
TypeScript allows using type-only imports and exports to improve compilation performance:
// types.ts
export type Point = { x: number; y: number };
export type Vector = { dx: number; dy: number };
// main.ts
import type { Point } from "./types";
const p: Point = { x: 0, y: 0 };
TypeScript provides options to customize type inference for better control:
// In tsconfig.json
{
"compilerOptions": {
"strictNullChecks": true,
"noImplicitAny": true,
"strictPropertyInitialization": false
}
}
TypeScript includes utility types to manipulate existing types:
type PartialPoint = Partial<Point>; // { x?: number; y?: number; }
type ReadonlyPoint = Readonly<Point>; // { readonly x: number; readonly y: number; }
type Merged = Point & { z: number }; // { x: number; y: number; z: number; }
Prefer using unknown
over any
to ensure type safety:
function parseJSON(jsonString: string): unknown {
return JSON.parse(jsonString);
}
const data: unknown = parseJSON("{ \"name\": \"John\", \"age\": 30 }");
// Use type assertion or type guards to access properties safely
Conditional types enable complex type transformations based on conditions:
type NonEmptyArray<T> = T extends Array<infer U> ? [U, ...U[]] : never;
type Numbers = NonEmptyArray<number>; // number[]
Use /// <reference lib="..." />
to include specific lib types:
/// <reference lib="esnext" />
const arr: Array<number> = [1, 2, 3];
TypeScript supports import.meta
for accessing meta information in modules:
console.log(import.meta.url); // URL of the current module
Triple-slash directives can be used to include type declarations:
/// <reference types="some-module" />
import { SomeModule } from "some-module";
For advanced scenarios, TypeScript's Compiler API allows writing custom transformers:
import * as ts from "typescript";
const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
return (node) => {
// Transformation logic here
return node;
};
};
const result = ts.transpileModule(code, {
transformers: { before: [transformer] },
});