Fundamentals of Typescript
Typescript is an essential resource for large applications because typescript ensures the code quality and the correct implementation and use of different components, also, it provides static testing to your code raising errors as soon you forget to type the complete set of properties/parameters/attributes in your code.
Typescript has evolved from type annotations to a complex type-level language, so you need to understand basic concepts to work successfully with it, in this post I will cover the basic and key concepts of this type-level language in the following topics:
- Primitives
- Static values
- Functions
- Unions, Intersections, Extension
- Generics
- Objects and Records
- Arrays
Primitives Types
Typescript like any other language, typescript has its own primitives which are:
- string
- number
- boolean
- symbol
- bigint
- undefined
- null
type myString = 'Hello'
type myNumber = 1
type myBoolean = true
type mySymbol = Symbol('test')
type myBigInt = BigInt(9007199254740991)
type myUndefined = undefined
type myNull = null
those are the primitives and the very commonly used are string, number, and boolean.
Static values
Static values are fixed values in our types, some examples are:
type IsTrue = true
const myBool: IsTrue = false // ❌ error false is not assignable to true
type Greet = 'hello'
const myGreet: Greet = 'bye' // ❌ error, my greet should be hello
this is the way to say that we are expecting a unique possible value.
Functions
the function type allows you to specify the input and output of a Javascript function:
// Any kind of function
type myFunction = Function
// Functions that not returns value
type mySecondFunction = () => void
// Functions that receive a value and returns a value
type myOtherFunction = (a: string) => string
Union, Intersection, and Extension
Unions are some like the Or logic operator in other languages, and are represented with | 'pipe', here are some examples:
type Greet = 'Hello' | 'Hi'
const myGreet:Greet = 'Hello' // ✅
const otherGreet:Greet = 'Hi' // ✅
const badGreet:Greet = 'Hey' // ❌
type NameOrAge = string | number
const name:NameOrAge = 'Bob' // ✅
const age:NameOrAge = 20 // ✅
const married:NameOrAge = false // ❌ expected string or number
Intersections are how we can combine two types in one, and are represented with & ampersand, here more examples:
type NameAndAge = { name: string } & { age: number }
const user:NameAndAge = { name: 'Bog', age: 20 } // ✅
const badUser:NameAndAge = { name: 'Bob', age: 20, married: false } // ❌
Extensions as well allow us to combine two types in one, and are represented with extends
keyword, we can reuse the previous example:
type WithName = { name: string };
type WithAge = { age: number };
interface User extends WithAge, WithName {}
const user:User = { name: 'Bog', age: 20 } // ✅
const badUser:User = { name: 'Bob', age: 20, married: false } // ❌
So, which is the difference between Intersections and Extension? well, with extension typescript will be aware when some properties are overlapped, on the other hand with intersection typescript just will mix the overlapped properties, for example:
type WithName = { name: string; id: string };
type WithAge = { age: number; id: number };
// Typescript will raise an error because ID type have a mismatch
interface UserInterface extends WithAge, WithName {}
// Typescript does not raise an error and it will mix the ID type, so, it should be string and number at the same time
type UserType = WithName & WithAge
we can say that extensions and interfaces are a safe way to combine types.
Generics
Generics are the way to create reusable types, as components that expect properties or functions that expect parameters, so, in typescript, the generic types expect an input, here are some examples:
// Without Generics
function exampleString(a: string): string {
return a
}
function exampleNumber(a: number): number {
return a
}
// With Generics
function exampleGeneric<T>(a: T): T {
return a
}
In the previous example, we are defining a generic type T and we are saying that the input parameter will be of generic type T, and, the output will be of the same generic type T.
Generics are a fundamental topic in Typescript, for instance, the correct typing for some array functions is defined in generics:
const array = [1, 2, 3]
// we use generics to declare the output of the map.
// the type of mapped is string[]
const mapped = array.map<string>(value => `${value}`)
Object and Records
Object and records are the most common structures in Typescript, they both are used to type JavaScript objects, here some examples:
Object
type Person = { name: string; age: number };
const person: Person = {
name: 'Molly',
age: 25,
};
Record
type PhoneNumbers = Record<string, number>;
const phoneNumbers: PhoneNumbers = {
work: 75555555
home: 64626444
}
So an Object is a type that have different properties and values with different types each, while Record is a special "Object" that has properties and values with same type.
More examples:
Objects could contain records as well to build more complex types:
type PhoneNumbers = Record<string, number>;
type Person = { name: string; age: number; phoneNumbers: PhoneNumbers };
const person: Person = {
name: 'Molly',
age: 25,
phoneNumbers: {
work: 75555555
home: 64626444
}
};
And records could specify exact names to their properties:
type PhoneNumbers = Record<'work' | 'home', number>;
const phoneNumbers = {
work: 642642642
home: 753373733
mobile: 909090101 <-- ERROR: as mobile is not in type PhoneNumber :)
};
A really useful typescript feature is that you could pass objects to other object types that fits in his defined type:
type Person = { name: string; age: number };
const molly = {
name: 'molly',
age: 24,
phone: 424264262
};
function anSimpleFunctionHere(person: Person) {
...
}
// You could pass molly as person because it fits into person type.
anSimpleFunctionHere(molly); <-- This is OK. :)
Arrays
Arrays are one of the most used object in Javascript and so on one of the most common type in Typescript:
type SomeArray = Array<string>;
const someArray: SomeArray = ['value-1', 'value-2'];
In the most common use case you will have arrays of objects which type looks like:
type Person = { name: string; age: number };
type Persons = Array<Person>;
const persons: Persons = [{
name: 'molly',
age: 24
},
{
name: 'alice'
age: 26
}
];
Extracting type from an array
Some times you will need to extract a type from an array, you could do it with by using the number
type:
type Person = { name: string; age: number };
type Persons = Array<Person>;
type ExtractedPerson = Persons[number]; // is of type { name: string; age: number } :)
this last one is really useful when you're inferring types, I will cover it in a next post :)
Comments ()