What is TypeScript?
TypeScript is a superset of JavaScript that adds static typing capabilities. While JavaScript is a dynamically-typed language, TypeScript brings compile-time type checking similar to languages like Java or C#.
The key distinction: dynamically-typed languages allow variables to hold any type of value without declaration, whereas statically-typed languages require explicit type specifications when declaring variables.
TypeScript offers two major advantages:
- Error detection: Type mismatches are caught during development rather than at runtime
- Intelligent suggestions: IDE autocomplete provides relevant method recommendations
Installation
npm install -g typescript
Verify installation with:
tsc -v
Creating a TypeScript Vue Project
npm create vite
Type Annotations
Type annotations define constraints for variables:
let userAge: number = 25
The :number annotation enforces that userAge can only hold numeric values. Attempting to assign a string or other type results in a compilation error.
TypeScript Type System
Basic Types
TypeScript supports all JavaScript primitive types:
| Category | Types |
|---|---|
| Primitives | number, string, boolean, null, undefined |
| Complex | arrays, functions, objects |
Array Declaration:
let numbers: number[] = []
let names: Array<string> = ['alice', 'bob']
Union Types
Combine multiple types for a single variable:
let data: (number | string)[] = [1, 2, 'hello']
With union types, only methods common to all constituent types are accessible. For example, toString() works because both number and string share this method.
Type Aliases
Create reusable type definitions:
type MixedArray = (number | string)[]
let firstData: MixedArray = [1, 2, 'test']
let secondData: MixedArray = ['a', 'b', 3]
Function Types
Specify parameter and return types:
function calculate(a: number, b: number): number {
return a * b
}
Function Type Alias:
type MathOperation = (x: number, y: number) => number
const multiply: MathOperation = (x, y) => {
return x * y
}
Type aliases are primarily used with arrow functions and function expressions rather than function declarations.
Void Type
Use void for functions that don't return a value:
function printMessage(message: string): void {
console.log(message)
}
While JavaScript functions without explicit returns produce undefined, TypeScript prefers void for such cases.
Optional Parameters
Mark parameters with ? when they're not required:
const greet = (name?: string, id?: number): void => {
console.log(`Hello ${name}, ID: ${id}`)
}
greet('alice')
Important: Required parameters must precede optional ones.
Object Types
Define object structures inline or with type aliases:
let user: {
username: string,
userId: number,
greet: (msg: string) => void
} = {
username: 'charlie',
userId: 42,
greet(msg) {
console.log(msg)
}
}
Using Type Alias:
type User = {
username: string,
userId: number,
greet: (msg: string) => void
}
let admin: User = {
username: 'admin',
userId: 1,
greet(msg) {
console.log(msg)
}
}
Interfaces
Interfaces define object contracts:
interface Person {
name: string
age: number
sayHello: () => void
}
const person: Person = {
name: 'david',
age: 30,
sayHello() {
console.log('Hi')
}
}
Interface Syntax:
interface InterfaceName {
propertyName: type
}
Interface vs Type Alias
| Aspect | Interface | Type Alias |
|---|---|---|
| Object types | ✅ | ✅ |
| Other types (union, primitive) | ❌ | ✅ |
| Extensibility | via extends |
via & operator |
Recommendation: Prefer type aliases when possible due to greater flexibility.
Interface Inheritance
interface Animal {
species: string
weight: number
}
interface Dog extends Animal {
breed: string
bark: () => void
}
const myDog: Dog = {
species: 'canine',
weight: 15,
breed: 'golden retriever',
bark() {
console.log('Woof')
}
}
Extending Types with Intersection
type Vehicle = {
wheels: number
}
type Electric = {
batteryCapacity: number
}
type ElectricVehicle = Vehicle & Electric
The & operator creates an intersection, requiring all properties from both types.
Tuples
Fixed-length arrays with specific types at each position:
let coordinates: [number, number] = [40.7128, -74.0060]
Common use case: geographic coordinates in mapping applications.
Literal Types
Restrict values to specific options:
type CardinalDirection = 'north' | 'south' | 'east' | 'west'
function move(direction: CardinalDirection) {
// implementation
}
move('north')
Enums
Enumerations provide named constants:
enum Status {
Pending,
Active,
Completed,
Cancelled
}
function updateStatus(status: Status) {
console.log('Current status:', status)
}
updateStatus(Status.Active)
updateStatus(Status.Pending)
Numeric Enums with Values:
enum HttpCode {
OK = 200,
NotFound = 404,
ServerError = 500
}
Enums support reverse lookups: HttpCode[200] returns 'OK'.
Unspecified enum values default to numeric indices starting from 0.
Any Type
The any type opts out of type checking entirely:
let flexible: any = 'string'
flexible = 123
flexible.something() // no error, no suggestions
Avoid using any — it eliminates type safety and removes IDE assistance.
Situations that implicitly become any:
- Variables declared without type and without initial value
- Function parameters missing type annotations
Type Assertions
Override TypeScript's type inference when you know more than the compiler:
function getElement(): HTMLElement | null {
return document.getElementById('container')
}
const element = getElement() as HTMLDivElement
element.classList.add('active')
Use assertions when the actual runtime type is more specific than what TypeScript can infer.
Generics
Generics create reusable components that work with multiple types:
function identity<T>(value: T): T {
return value
}
identity<number>(42)
identity<string>('hello')
Type Inference with Generics:
function wrap<T>(value: T): T {
return value
}
wrap(999) // inferred as number
wrap('test') // inferred as string
Generic Constraints:
Require generic types to implement specific properties:
interface HasLength {
length: number
}
function logLength<T extends HasLength>(item: T): void {
console.log(item.length)
}
logLength('hello') // works - strings have length
logLength([1, 2, 3]) // works - arrays have length
logLength({ length: 10 }) // works - object has length property
The constraint extends HasLength ensures the type argument has a length property, enabling type-safe access to that property.