TypeScript Type features for cleaner code
TypeScript Type features for cleaner code
TypeScript is a superset of JavaScript. This means that it sits on top of JavaScript and compiles down to output valid JS files. The features and functionalities that are in TypeScript tend to be a few steps ahead of JavaScript itself. It incorporates syntax sugaring to help speed up the coding process and ensure consistency across the project.
JavaScript is a loosely typed language, meaning that types are automatically assigned when they are created. It can also change based on context. This can create issues, especially when it comes to error handling and checking value types. Here is a quick guide on TypeScript types and what they can do for your project.
Only specific values allowed in TypeScript
You can limit the values a variable can have using String Literal Types. Here is an example of how to use String Literal Types in TypeScript.
const x: 'hello' | 'hi' | 'hola' | 'namaste' = 'namaste';
TypeScript will throw an error if the type doesn't match the expected. This feature can come in handy for error handling logic. For example, error code maps and their messages can be coordinated for more information rich handling.
Here is an example:
type TLoginError = | 'user-not-found' | 'wrong-password' | 'network-request-failed' | 'too-many-requests'; const loginErrorMessages: { [error in TLoginError]: string } = { 'network-request-failed': `Network request failed. Try to log in again.`, 'user-not-found': 'Email not found in our database', 'wrong-password': 'Email and Password do not match', 'too-many-requests': 'Too many login attempts. Try again in a minute', };
loginErrorMessages
won't take a property other than those specified in the type TLoginError
.
Here is an example for number type in TypeScript:
type FontWeights = 100 | 200 | 300 | 400 | 500 | 600 | 700; const fontWeight: FontWeights = 200;
Here is an example of an array in TypeScript:
const searchFilters: ('name' | 'email' | 'phone' | 'designation')[] = [];
Template Literal Types in TypeScript
If you've ever worked with Tailwind color palettes, you'd experience the pain of maintaining combinations. Here is an example:
type ColorPalette = // Blue | 'blue-100' | 'blue-300' | 'blue-500' | 'blue-700' // Green | 'green-100' | 'green-300' | 'green-500' | 'green-700' // Yellow | 'yellow-100' | 'yellow-300' | 'yellow-500' | 'yellow-700' // Red | 'red-100' | 'red-300' | 'red-500' | 'red-700' // Cyan | 'cyan-100' | 'cyan-300' | 'cyan-500' | 'cyan-700';
The above consists of 220 string literals. Imagine if we had to expand the color palette and support them all. This is where Template Literal Types comes in. This feature shipped in TypeScript 4.1.0
, and increased the productivity of JavaScript developers. What Template Literal Types do is allow us to apply dynamic behavior in your literal types.
Here is an example:
type Color = 'blue' | 'green' | 'yellow' | 'red' | 'cyan'; type Shade = 100 | 300 | 500 | 700; type ColorPalette = `${Color}-${Shade}`;
This is much easier to visually digest and maintain.
Tuples in TypeScript
In a nutshell, a tuple is a collection of data that is ordered and unchangeable. This is important for data that needs to maintain immutability. However, JavaScript's implementation of immutability is not one of the language's main features. Forcing types through TypeScript ensures that the data being passed in the correct format.
Here is an example of a tuple used in TypeScript:
type Coordinates = [number, number]; function getLocation(coords: Coordinates) { const [x, y] = coords; /** Logic */ }
coords
can only be given values that are of number
type, and only 2 values. Here is another example where the data breaks the expected typing:
type Grid = [[number, number], [number, number]]; const x: Grid = [ [1, 2], [3, 4], ]; // ✅ Correct const y: Grid = [[1, 2], 3]; // ❌ ERROR
The main use of Tuples is in limiting the types, as well as the number of items it can take. Types don't have to be same, you can mix and match as needed to enforce the tuple.
type UserData = [string, number, number, string, UserRole];
Intersection types in TypeScript
In MySQL, you can split up related data into different tables. For example, instead of a single table containing all the fields:
| id | name | email | username | password | gender | dob | acc_created | last_seen |
We can split it up into multiple tables to contain specific sets of data.
login_info
:
| id | email | username | password |
personalInfo
:
| id | name | gender | dob |
account_details
:
| id | account_created | last_seen |
Then we join all these together in the query when we need to mix and match our intersecting data. We can do something similar in TypeScript. Here is an example:
type LoginFields = { id: number; email: string; username: string; password: string; }; type PersonalFields = { id: number; name: string; gender: 'male' | 'female' | 'non-binary' | 'prefer-not-to-say'; dob: Date; }; type AccountDetails = { id: number; accountCreated: Date; lastSeen: Date; }; type User = LoginFields & PersonalFields & AccountDetails;
Using the 'Pick' helper in TypeScript
The Pick
helper type can help you 'pick' specific properties from an interface.
Let's take a look at the example below. We want to create a type with only id
, gender
, name
, and these properties should match those in the original User
type. So we can write something like this:
type FilteredUserFields = { id: number; gender: 'male' | 'female' | 'non-binary' | 'prefer-not-to-say'; name: string; };
However, TypeScript provides a helper type Pick
which filters properties based on only what you need.
type FilteredUserFields = Pick<User, 'id' | 'gender' | 'name'>;
Overall, TypeScript offers increased efficiency in how you think and write your code. Typings in TypeScript is only one of the multitude of features available that makes the syntax sugar truly icing for your JavaScript. Other features include classes, modules, type manipulation such as generics, operators, and conditionals.