I bet you don't know these five typescript tips
Table of Content
Click to expand
Looking back
Around a year ago, I wrote an article saying āI bet you donāt know these javascript tips and tricksā. Well this is going to be an upgrade on that. I truly bet that you donāt use these typescript tips and tricks. So keeping the old vows, tell me how many you know and are being used in your day to day life. So letās begin shall we ?
The five tips
1. Using IDE to get the type of literals:
We can get the literals of each types in typescript. Here is an example for each of the major types and or mismatch. It comes pre-installed in Visual Studio Code. Hover with mouse over the element you want to see.
Viewing the type in visual studio code using mouse hover
If you use my neovim setup. Pressing Ctrl + K
to get the type.
Viewing the type in my neovim. Neovim config is used.
2. Making null safe
You can make a value that can be null or undefined in its definition act as if it is not null. Use the !
operator which is non-null assertion operator. Letās consider an interface given below.
interface UserProfile {
id: number; // required property
name: string | null; // can be string or null
email?: string; // optional property (can be undefined)
phone?: string | null; // optional and can be null
}
const user: UserProfile = {
id: 1,
name: null, // valid due to `string | null`
// email is optional, so it's not provided
phone: null // valid due to `string | null | undefined`
};
function printName(name:string){
return name + "Hello"
}
Before: Without non null assertion operator
Here you can see that the type is string|null|undefined
and it cannot be assigned to parameter string
After: With not null assertion operator
With the introduction of !
, we can see that the type is same but the function accepts the argument.
Using this argument will not prevent any error. Incase if the value of phone is actually null
3. Getting parameters type for a method from library
Lets assume that you are using a library. One of the popular library you will use. Lets assume the library has a function who 1st argument is something that is not exported.
// lets asssume that this interface is not exportted by the library
// How can we get the same interface without using DatabaseConnectionParams
interface DatabaseConnectionParams{
host:string,
port: number,
user: string,
password: string,
}
function connectToDatabase(params:DatabaseConnectionParams){
return `CONNECTED to ${params.host}:${params.port}`;
}
const connectionParams = {
host:"localhost",
port: 5432,
user: "testuser",
password:"test12345",
}
connectToDatabase(connectionParams);
You can also use ReturnType for to get the return value type
We can use the following pattern to get the above interface.
We can use types like [0] for type and [āmethodNameā] for accessing methods in a class
Here we can see that we used the following operators. Parameters
typeof
and [0]
Here are the breakdown of each operators:
- Parameters is used to get all the parameters of a function or method. It will return every type of parameters for each arguments of the function in tuple form.
- typeof: typeof operator is used when we want to get the type of an typescript legal literal.
- [0]: Here we used [0] to get the type of tuple parameters.
4. Not using Readonly, Pick, Omit, and Partial
In most projects, I see code missing the opportunity to the use of Readonly, Pick, Omit and Partial. Look on your code once more and look into how you can reuse it. Here is how they work.
- readonly: The readonly utility type makes all properties of an object immutable, meaning they cannot be modified after the object is created.
type Person = {
name: string;
age: number;
};
const person: Readonly<Person> = {
name: "John",
age: 30,
};
person.age = 31; // Error: Cannot assign to 'age' because it is a read-only property.
- omit: The Omit utility type creates a new type by removing one or more keys from an existing type.
type Person = {
name: string;
age: number;
address: string;
};
type WithoutAddress = Omit<Person, 'address'>;
// This results in a type that has 'name' and 'age' but no 'address'
- pick: The Pick utility type creates a new type by selecting a subset of properties from an existing type.
type Person = {
name: string;
age: number;
address: string;
};
type NameAndAge = Pick<Person, 'name' | 'age'>;
// This results in a type that only has 'name' and 'age'
- partial: The Partial utility type makes all properties of an object optional.
type Person = {
name: string;
age: number;
address: string;
};
const partialPerson: Partial<Person> = {
name: "John",
};
// 'age' and 'address' are optional now.
4. Conditional Types
Conditional Types provide a way to introduce logic to type definitions. They are similar to conditional expressions (if statements) in regular JavaScript, but they operate at the type level.
Here is an structure of typescript types
T extends U ? X : Y
āIf type T extends (or is assignable to) type U, then use type X, otherwise use type Y.ā
Here is an example of how to use conditional types:
type IsString<T> = T extends string ? "Yes" : "No";
type A = IsString<string>; // "Yes"
type B = IsString<number>; // "No"
5. Dynamic parameter type
Dynamic types can be achieved in typescript and is really useful when making a library or making types for some complex code.
Below is an example when I used when making a mail library which will take enum and some object with interface changing based on the enum value provided. Mostly useful for email sending library or web-socket library.
type Role = "admin" | "user";
interface BaseUser {
name: string;
}
type UserWithRole<T extends Role> = T extends "admin"
? BaseUser & { permissions: string[] }
: BaseUser;
const adminUser: UserWithRole<"admin"> = {
name: "Alice",
permissions: ["read", "write", "delete"],
};
const regularUser: UserWithRole<"user"> = {
name: "Bob",
// No 'permissions' field here
};
This can be useful when making complex types. Here is an example of how I use it for email clients. Here depending upon the type of T, type of K will be changed.
export enum EmailTemplateEnum {
NewInquiryReceivedToAdmin = 4590262,
NewInquiryReceivedToClient = 4576327,
BidAcceptedOnOrderForClient = 4659400,
BidAcceptedOnOrderForPartner = 4659358,
BidEditedByPartnerForAdmin = 4659320,
BidReceivedOnOrderForAdmin = 4659305,
OrderBookingReceivedToAdmin = 4659255,
OrderBookingReceivedToClient = 4659177,
NotifyPartnerForOrderCreation = 4659288,
OrderConfirmationByClientForPartner = 4659479,
PaymentSuccessfulForClient = 4659518,
PaymentSuccessfulForAdmin = 4736125,
PaymentSuccessfulForPartner = 4736123,
OrderFollowUp1 = 4659643,
OrderFollowUp2 = 4659602,
OrderFollowUp3 = 4659657,
}
// for auto complete based on choice of enum
// we will need to create all these 7 interface
export type EmailEnumToEmailVariableInterfaceMap = {
[EmailTemplateEnum.NewInquiryReceivedToAdmin]: NewInquiryReceivedToAdminInterface;
[EmailTemplateEnum.NewInquiryReceivedToClient]: NewInquiryReceivedToClientInterface;
[EmailTemplateEnum.BidAcceptedOnOrderForClient]: BidAcceptedOnOrderForClientInterface;
[EmailTemplateEnum.BidAcceptedOnOrderForPartner]: BidAcceptedOnOrderForPartnerInterface;
[EmailTemplateEnum.BidEditedByPartnerForAdmin]: BidEditedByPartnerForAdminInterface;
[EmailTemplateEnum.OrderBookingReceivedToAdmin]: OrderBookingReceivedToAdminInterface;
[EmailTemplateEnum.OrderBookingReceivedToClient]: OrderBookingReceivedToClientInterface;
[EmailTemplateEnum.NotifyPartnerForOrderCreation]: NotifyPartnerForOrderCreationInterface;
[EmailTemplateEnum.OrderConfirmationByClientForPartner]: OrderConfirmationByClientForPartnerInterface;
[EmailTemplateEnum.BidReceivedOnOrderForAdmin]: BidReceivedOnOrderForAdminInterface;
[EmailTemplateEnum.OrderFollowUp1]: OrderFollowUpInterface;
[EmailTemplateEnum.OrderFollowUp2]: OrderFollowUpInterface;
[EmailTemplateEnum.OrderFollowUp3]: OrderFollowUpInterface;
[EmailTemplateEnum.PaymentSuccessfulForClient]: PaymentSuccessfulForClientInterface;
[EmailTemplateEnum.PaymentSuccessfulForPartner]: PaymentSuccessfulForPartnerInterface;
[EmailTemplateEnum.PaymentSuccessfulForAdmin]: PaymentSuccessfulForAdminInterface;
};
async sendEmail<
T extends EmailTemplateEnum,
K extends EmailEnumToEmailVariableInterfaceMap[T],
>(emailArgs: EmailProducerInterface<T, K>){}
Upon using the sendEmail
method, we will get the following types changes. Look below how the variable type/interface changes for different enum values.
Conclusion
Tell me how many did you know, and how many did you get right in the comments below. Thank you for reading this far.
For the time being, comments are managed by Disqus, a third-party library. I will eventually replace it with another solution, but the timeline is unclear. Considering the amount of data being loaded, if you would like to view comments or post a comment, click on the button below. For more information about why you see this button, take a look at the following article.