Hey developers! I know you are working on an exciting project. I assume that, while starting the project, a person from management asked you to use Typescript and you are aware of TS. A year ago when Typescript was an immense fear for me, I never thought I will be using it in my every project and loving it. Typescript is an excellent language that has many cool features like
- Catches errors at compile-time.
- Provides documentation.
- Allows editors to flag errors while typing and provides completion.
- Makes refactoring less error-prone.
- Makes some tests unnecessary.
- Disallows many JavaScript-type coercions.
- Very helpful in frontend frameworks like Vue and React, obviously in angular too
Typescript comes with a serious learning curve, and no doubt for beginners, it can give you a tough time but you have to learn it and grasp its advanced concepts because now the industry demands this technology almost in all new projects. But stay with me, because I am going to tell you some senior typescript concepts, I know you‘ll be loving them. Let’s go developers.
Remember major Types in typescript
"Hello"
is a string2
is a numbertrue/false
is a boolean{}
is an object[]
is an arrayNaN
is not a number, not basically type real type.null
is empty or unknownundefined
means no value is set
What is type noImplicitAny
Typescript’s default behavior is to set the default type to function, or variable and mostly it types any
for variables. Here is the function;
Copiedfunction testijgNoImplicitAny(args) { return args }
So here typescript will infer type automatically. Typescript gives you control over types. If making a variable typed is mandatory then there is an option to tell the compiler to produce an error on untyped code, so here is how to do this
Add "noImplicitAny": true
Copied{ "compilerOptions": { "target": "esnext", "noImplicitAny": true }
underlined error in red color - very nice it is working
To remove this error we can use any type such as any
, number, string
What is type unknown
The unknown
type is similar to any
type in that all types are assignable to any
, and unknown
type.
- Assignments to and from
unknown
require a type check, whereas assignments to and fromany
do not. - The
unknown
is a more restrictive type thanany
, meaning that fewer operations are allowed on values of typeunknown
without a type check. unknown
can be narrowed to a more specific type using type guards, whereasany
cannot.
Copiedfunction example1(arg: any) { const a: str = arg // no error const b: num = arg // no error } function example2(arg: unknown) { const a: str = arg // 🔴 Type 'unknown' is not assignable to type 'string'.(2322) const b: num = arg // 🔴 Type 'unknown' is not assignable to type 'number'.(2322) }
any
type is bidirectional, whereas unknown
is unidirectional.
Union and intersection types
An intersection type is a type that combines several types into one; a value that can be any one of several types is a union type. The & symbol is used to create an intersection, whereas the | symbol is used to represent a union.
Union types
Are joining multiple types with the "pipe" (|
) symbol. For example, the type string | number
is a union type that can be either a string or a number.
Copiedlet value: string | number value = 'hello' console.log(value.toUpperCase()) // okay, value is of type 'string' value = 42 console.log(value.toFixed(2)) // Error: value.toFixed is not a function
Intersection types
Are joining multiple types with the "ampersand" (&
) symbol. For example, the type string & number
is an intersection type meaning one variable is a string and the other is a number.
Copiedinterface A { x: number } interface B { y: string } let value: A & B value = { x: 1, y: 'hi, ts developers' } // okay console.log(value.x) // 1 console.log(value.y) // "hi, ts developers"
Using Union and Intersection types together rock, for example
Copiedinterface Person { name: string age: number } interface Employee { employeeId: number salary: number } interface Student { studentId: number major: string } type PersonType = Person & (Employee | Student) let person: PersonType person = { name: 'John', age: 25, employeeId: 123, salary: 50000, } console.log(typeof person.name, person.name) // string John console.log(typeof person.employeeId, person.employeeId) // number 123
How to use Keyof
in typescript
In TypeScript, the keyof
operator is used to creating a new type that represents the keys of a given object type. The new type is a union of the string literals of the object's property names. To be honest, since I knew this type I am learning to use it. I mostly use it with Typescript record types.
A bit of usage of keyof
Copiedinterface Person { name: string age: number } type PersonKeys = keyof Person let key: PersonKeys key = 'name' // okay key = 'age' // okay key = 'address' // Error: "address" is not a property of type 'PersonKeys'
PersonKeys
is a union of the string literals of the property names of Person
, only the values "name"
and "age"
are valid values for key
.
Use-case of using keyof
Copiedinterface Car { make: string model: string year: number } const myCar: Car = { make: 'Toyota', model: 'Camry', year: 2020, } function updateCar(car: Car, prop: keyof Car, value: string | number) { car[prop] = value } // 👍 okay updateCar(myCar, 'make', 'Honda') // 👍 okay updateCar(myCar, 'year', 2022) // 👎 Error: "color" is not a property of type 'keyof Car' updateCar(myCar, 'color', 'red') // 👎 Error: "year" is not a property of type 'string' updateCar(myCar, 'year', '2022')
There is an interface Car
that defines a car with three properties: make
, model
, and year
. There is an object named myCar
interfaced Car
.
The updateCar
the function takes three arguments:
car
of typeCar
.prop
of typekeyof Car
.value
of typestring | number
which can bestring
ornumber
based on the prop.
The function modifies the value of the property identified by prop
to value
.
How Typeof in Typescript is different**
JavaScript already has a typeof
operator you can use in an expression context:
Copied// Prints "string" console.log(typeof 'Hello world')
TypeScript adds a
typeof
operator you can use in a type context to refer to the type of a variable or property.
Copiedlet text: string const firstName: typeof text const lasrName: typeof text
firstName, lastName
are a variable whose type is inferred using the typeof
operator and the value of text
. Because text
is of type string
.
ReturnType in typescript
The ReturnType
utility type in TypeScript is used to extract the return type of a function or method.
Copiedfunction add(a: number, b: number): number { return a + b } let addReturnType: ReturnType<typeof add>
add
is a function that takes two numbers as arguments and returns a number.
addReturnType
is a variable whose type is inferred using the ReturnType
utility type and the function add
. And ReturnType<typeof add>
is the same as number
.
Use-case of ReturnType
Here is a real-world example using array functions.
data
Copiedconst data = [ { id: 1, name: 'John' }, // id -> number, name -> string { id: 2, name: 'Jane' }, ]
implementing some array functions
Copiedconst findById = (id: number) => data.find((item) => item.id === id) const getName = (item: any) => item.name const toUpperCase = (str: string) => str.toUpperCase()
extracting ReturnType
and storing them in variables
Copiedlet foundItem: ReturnType<typeof findById> = findById(1) console.log(foundItem) // { id: 1, name: 'John' } // { id: number, name: string } let itemName: ReturnType<typeof getName> = getName(foundItem) console.log(itemName) // 'John' // string let upperCasedName: ReturnType<typeof toUpperCase> = toUpperCase(itemName) console.log(upperCasedName) // 'JOHN' // string
What are Generics in Typescript
As per typescript docs,
Being able to create a component that can work over a variety of types rather than a single one. This allows users to consume these components and use their own types.
When you create a generic component, you can specify a placeholder type (also called a type parameter) that represents the actual type that the component will work with. Mostly this parameter is set to T like <T>
and these angle brackets are known as type-interface
.
Syntax of Generics
A simple generic function that takes an array of elements and returns the first element of the array:
Copiedfunction syntax<T>(arg: T): T { return arg }
T
is the type parameter that represents the actual type of elements in the array. When you call the syntax
function, you can specify the type of elements by providing a type argument. So in simple words, arguments will decide the final type.
Copiedfunction getFirst<T>(arr: T[]): T { return arr[0] } let numbers = [1, 2, 3] let firstNumber = getFirst<number>(numbers) // 1 let strings = ['a', 'b', 'c'] let firstString = getFirst<string>(strings) // 'a'
type of the elements in the array is inferred from the array argument.
Using Conditional types in Typescript
The conditional ternary operator is a very well-known operator in Javascript. The ternary operator takes three operands. A condition, a return type if the condition is true, and a return type is false.
syntax
Copiedcondition ? returnTypeIfTrue : returnTypeIfFalse
Before going forward a bit of note about extending types
<aside>
💡 In TypeScript, you can use the extends
keyword to create new types that are based on existing types.
Copiedinterface Person { name: string age: number } interface Employee extends Person { salary: number }
Here is how to use conditional types by leveraging generics <T>
.
Copiedinterface IdWithString { id: string } interface IdWithNumber { id: number } type Id<T> = T extends string ? IdWithString : IdWithNumber let idA: Id<string> = { id: 'stringId' } // Type is inferred as IdWithString let idB: Id<number> = { id: 1 } // Type is inferred as IdWithNumber
The Id
type is a generic type that takes a type parameter T
, and it uses a conditional type to check if T
is assignable to string
Using typeof, and keyof types together
It is rocking to use keyof
and typeof
together in TypeScript. The keyof
keyword allows you to extract the keys of an object type while typeof
allowing you to extract the type of a variable.
so here is how
Copiedconst colors = { red: '#ff0000', green: '#00ff00', blue: '#0000ff', } // 1 type Colors = keyof typeof colors // 2, note let selectedColor: Colors = 'green' // 3 let anotherSelection: Colors = 'yellow' // 4 console.log(colors[selectedColor]) // 5 // docs /** 1. Type '"yellow"' is not assignable to type '"red" | "green" | "blue"' 2. trying to get types of properties 3. yeah, it is correct 4. Type '"yellow"' is not assignable to type '"red" | "green" | "blue"' 5. '#00ff00' note: keyof typeof colors is equal to "red" | "green" | "blue" */
How the TypeScript Record Type Works
The Record
type is used to create an object type that has a set of properties with a specific key type and a specific value type. For a beginner, it is a bit tough to understand types per data type.
When I was young in typescript I usually use an empty object {}
as a type of object, then IDE told me, dude! there is a Record type, use that.
Syntax of Record type
CopiedRecord<K extends keyof any, T>
Where K
is the type of the keys of the object and T
is the type of the values of the object.
Copiedtype TRecords = Record<string, number> let tryingRecords: TRecords = { key1: 1, key2: 2, }
tryingRecords
is an object type that has properties with keys of type string
and values of type number
.
Using typescript Records and union types together
A practical example of how you can use Record
and union types together in TypeScript:
Copiedinterface User { id: number name: string } interface Admin { id: number name: string permissions: string[] } interface Guest { id: number name: string expiration: string } type UserType = 'user' | 'admin' | 'guest' type Users = Record<UserType, User | Admin | Guest> let users: Users = { user: { id: 1, name: 'John' }, admin: { id: 2, name: 'Jane', permissions: ['read', 'write'] }, guest: { id: 3, name: 'Jim', expiration: '2022-12-31' }, }
- There are three interfaces that represent different types of users (
User
,Admin
, andGuest
), - There is a union type
UserType
to combine three types into a single type that can be one of"user"
,"admin"
or"guest"
. - There is an object with
Record
type to define a new typeUsers
.TypeUsers
is an object type with properties that have keys that can only be the string literals"user"
,"admin"
, and"guest"
, - values of type
User | Admin | Guest
which is a union of all three interfaces.
Using record types with typeof
, and keyof
without interfaces
But there is a better way to type objects without even the use of interfaces, and I name it time-saving types.
Copied// Data const productInventory = { shirts: { small: 10, medium: 15, large: 20, }, pants: { small: 5, medium: 10, large: 15, }, shoes: { size7: 25, size8: 30, size9: 35, }, }
Copied// logic type Product = keyof typeof productInventory type ProductSizes = (typeof productInventory)[Product] function checkInventory(product: Product, size: keyof ProductSizes): boolean { return productInventory[product][size] > 0 } console.log(checkInventory('shirts', 'medium')) // true console.log(checkInventory('pants', 'small')) // true console.log(checkInventory('shoes', 'size9')) // true console.log(checkInventory('pants', 'extra large')) //compile error, extra large is not key of ProductSizes
productInventory
→ information about different products and the sizes available for each product,Record
type to define a new typeProductSizes
ProductSizes
is an object type with properties that have keys of theproductInventory[Product]
object and values of type number.Product
is the keyofproductInventory
to reference the specific product.ProductSizes
is the type ofproductInventory[Product]
to reference the specific product sizes.
Closing words
For front-end developers, TypeScript can provide several benefits to improve the development experience. TypeScript can help catch errors early by providing better type safety. which can make your code more robust and maintainable.
Using TypeScript in frontend development can also help you take advantage of modern JavaScript features such as JSX, async/await, and destructuring, while still providing the benefits of static type checking. This can make it easier to write and maintain complex and large-scale applications.