TypeScript Basics
After declaring the variable and before assigning it, use :
to specify the types
let name: string // Can takes string value
let age: number // Takes number
let isStudent: boolean // Takes boolean
let hobbies: string[] // takes array of string (read from right to left)
let role: [number, string] // this defines a tuple that will take a number and then a string
role = [5, 'typescript']
Defining Objects
let person: Object // Just checks if person is of type object or not
// To defined entire structure use type or interface
type Person = {
name: string
age: number
}
let person: Person = {
name: 'test',
age: 25,
}
// Now age is optional
type Person = {
name: string
age?: number
}
let person: Person = {
name: 'test',
}
let selectedPerson: Person[] // Array of type Person
// We can use interface also instead of type
interface Point {
x: number
y: number
}
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x)
console.log("The coordinate's y value is " + pt.y)
}
printCoord({ x: 100, y: 100 })
Union Type
let salary: number | string // Can take either number or string
Defining functions
function printName(name: string) {
console.log(name)
}
printName('Adesh')
// Function with return values
function squareNumber(a: number, b: number): number {
return a * b
}
// Also we can define void
function printName(name: string): void {
console.log(name)
}
printName('Adesh')
Defining arrow functions
const sum = (x: number, y: number): number => {
return x + y
}
The above methods work when you are defining the function, when you need to just declare the function in a interface or type, use the following syntax :
interface Props {
todo: string
handleAdd: () => void // Declares a function that will not return anything
sum: (x: number, y: number) => number // Declare a function which takes 2 numbers and returns a number
}
Defining object parameters
function printCoord(pt: { x: number; y: number }) {
console.log("The coordinate's x value is " + pt.x)
console.log("The coordinate's y value is " + pt.y)
}
printCoord({ x: 4, y: 5 })
Types Vs Interfaces
Extending Types
type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}
const bear: Bear = {
name: 'Hary',
honey: true,
}
Extending Interfaces
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
const bear: Bear = {
name: 'Hury',
honey: false,
}
Adding new fields to an existing interface
interface Person {
name: string
}
interface Person {
age: number
}
const jarrod: Person = {
name: 'Jarrod',
age: 32,
}
Adding new fields / properties does not work in type
type Person = {
name: string
}
type Person = {
age: number
}
// Error: Duplicate identifier 'Person'.
Summary:
Interfaces and types can mostly be used interchangeably.
- Interface can be implemented by classes (supports class method)
- Interfaces can extend each other
- Types can do unions and intersections
- Interfaces are used for
objects
where as types can be used for anything.
Defining types for Actions
in Reducer hook
type Actions =
| {type: 'add', payload: string}
| {type : 'remove', payload : number} // since payload is id
| {type : 'done', payload : number} // again number
const TodoReducer = (state : Todo[], action : Actions) => {
// Usual stuff
switch(action.type){
case 'add' :
return [
...state,
{id : Date.now(), todo : action.payload, isDone: false}
]
case 'remove' :
return state.filter((todo) => todo.id != action.payload)
case 'done' :
return state.map((todo) => todo.id !== action.payload ? {...todo, isDone : !todo.isDone})
default:
return state
}
}
const [state, dispatch] = useReducer(TodoReducer, [])
Type Assertions Vs Type annotations
The as
keyword is used for type assertions. It tells TypeScript that you're certain about the type of a value, even if TypeScript's type inference might suggest a different type.
const myCanvas = document.getElementById('main_canvas') as HTMLCanvasElement
The :
syntax is used for type annotations. Type annotations explicitly specify the type of a variable or a function parameter. They're helpful when you want to provide type information that TypeScript cannot infer automatically.
let myName: string = 'Alice'
In most cases, TypeScript's type inference is sufficient, and you don't need to explicitly provide type annotations.
Generics
Consider this example where you have the same function for two different data types:
// for number type
function fun(args: number): number {
return args
}
// for string type
function fun(args: string): string {
return args
}
You can use generics to write a single function that works for both data types:
function fun<T>(args: T): T {
return args
}
fun<number>(42) // 42
fun<string>('hello') // 'hello'
In this example, <T>
is a type parameter that represents the type that the function will work with. When calling the function, you can specify the type you want to use, making the code more flexible and type-safe.
Generics with Classes
class Stack<T> {
private items: T[] = []
push(item: T) {
this.items.push(item)
}
pop(): T | undefined {
return this.items.pop()
}
}
const numberStack = new Stack<number>()
numberStack.push(1)
numberStack.push(2)
console.log(numberStack.pop()) // 2
const stringStack = new Stack<string>()
stringStack.push('a')
stringStack.push('b')
console.log(stringStack.pop()) // 'b'
In this example, the Stack
class has a type parameter <T>
, which allows it to work with different data types while maintaining type safety.
Generics with Interfaces
interface KeyValuePair<T, U> {
key: T
value: U
}
const kv1: KeyValuePair<string, number> = { key: 'one', value: 1 }
const kv2: KeyValuePair<number, string> = { key: 2, value: 'two' }
Here, the KeyValuePair
interface has two type parameters, <T>
and <U>
, representing the types of the key and value properties, respectively
Partials
Used to get some elements from a type. Essentially make all the types optional so that we have the flexibility to choose what we want to send.
interface Todo {
id: number
title: string
description: string
done: boolean
}
type UpdateTodo = Partial<Todo>
Usage
function updateTodo(id: number, newProps: UpdateTodo) {
// patch request to update the TODO in the DB
}
updateTodo(1, {
description: 'Go to Gym',
})
// or
updateTodo(2, {
done: true,
})
Pick
Similar to Partial
we have Pick
which allows you to construct a new type by selecting properties (Keys) from an existing type (Type).
Pick<Type, Keys>
Example:
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = Pick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}