Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
January 14, 2022 06:50 pm GMT

TypeScript Tips and Tricks

TypeScript is a superset of JavaScript. Its like JavaScript, but with superpowers.

Vanilla JavaScript is a dynamically typed language. For example, if you assign a number type to a variable, then assign a string to that same variable further in the code, JavaScript compiles just fine. Youll only get an error when something breaks in production.

If you're set against using static typing tools like TypeScript, you can use JavaScript linters to provide some type checks. A linter would have helped catch the error in our example. Linters have their limits, however. For instance, linters dont support union types well explore this further in the article and they also cant lint complex object structures.

TypeScript provides more type features to JavaScript, enabling you to structure your codebase better. It type-checks your codebase during compile-time and helps prevent errors that may make it to production.

TypeScript improves JavaScript development in many different ways. But in this article, well zoom in on six areas, including helpful tips and tricks for using TypeScript. Some of these tips will discuss the ways TypeScript supports functional programming.

Read-Only Types

Functional programming generally demands immutable variables and by extension, immutable objects as well. An object with four properties must have the same properties throughout its life, and the values of those properties cant change at any point.

TypeScript makes this possible with the Readonly utility type. Heres a type without it:

...  type Product = {  name: string  price: number  }const products: Product[] = [  {  name: "Apple Watch",  price: 400,  },  {  name: "Macbook",  price: 1000,  },  ]products.forEach(product => {  // mutating here  product.price = 500  })  ...

In the code, we mutated the price property. Since the new value is a number data type, TypeScript doesnt throw an error. But using Readonly, our code is as follows:

...  const products: Readonly<Product>[] = [  {  name: "Apple Watch",  price: 400,  },  {  name: "Macbook",  price: 1000,  },  ]products.forEach(product => {  // mutating here  product.price = 500  })  ...

code

As we can see in this screenshot, the price property is read-only and cant be assigned another value. Attempting to mutate this objects value will throw an error.

Union Types

TypeScript allows you to combine types in compelling ways. The combination of two or more types is called a union type. You use the | symbol to create a union type. Lets look at some examples.

Number and String Union Type

Sometimes, you need a variable to be a number. Other times, you need the same variable to be a string.

With TypeScript, you can achieve this by doing something as simple as:

function(id: number | string) {  ...  }

The challenge with the union type declared here is you cant call id.toUpperCase because TypeScript doesnt know if youre going to pass a string or a number during the function declaration. So, to use the toUpperCase method, you must check if the id is a string using typeof === "string."

But, if it cant apply a standard method to all members that make a particular union, TypeScript wont complain.

Limiting Acceptable Types

With unions, you can also limit a variables acceptable data-type values. You do this using literal types. Heres an example:

function(type: "picture" | "video") {  ...  }

This union type comprises the string literal types of picture and video. This code causes other string values to throw an error.

Discriminated Unions

Another positive thing about unions is that you can have object types of different structures, each with a common distinguishing property. Heres an example:

...  type AppleFruit = {  color: string;  size: "small" | "large"  }type OrangeFruit = {  isRipe: boolean;  count: number;  }function describeFruit(fruit: AppleFruit | OrangeFruit) {  ...  }  ...

In this code, we have a union type, fruit, made of two different object types: AppleFruit and OrangeFruit. The two types of fruits have no properties in common. This difference makes it difficult for TypeScript to know what fruit is when we use it, as the following code and screenshot illustrate:

...  function describeFruit(fruit: AppleFruit | OrangeFruit) {  if (fruit.color) {  // throw error...see Figure B.  }  }  ...

code

The error in this screenshot shows that color doesnt exist on the orange type. There are two solutions to this.

The first solution is to check, in a more acceptable way, if the color property exists. Heres how:

...  function describeFruit(fruit: AppleFruit | OrangeFruit) {  if ("color" in fruit) {  // now typescript knows fruit is of the apple type  }  }  ...

We check if the color property is in the fruit object. Using this check, TypeScript can correctly infer the type, as this screenshot shows:

code

The second solution is to use discriminated union. This method implies having a property that clearly distinguishes both objects. TypeScript can use that property to know which type is being used at that particular time. Heres how:

...  type AppleFruit = {  name: "apple";  color: string;  size: "small" | "large";  }type OrangeFruit = {  name: "orange";  isRipe: boolean;  count: number;  }function describeFruit(fruit: AppleFruit | OrangeFruit) {  if (fruit.name === "apple") {  // apple type detected  }  }  ...

Since both types have the name property, fruit.name wont throw an error. And, using the value of the name property, TypeScript can determine the type of fruit.

Intersection Types

In contrast to union types, which involve type1, type2, or type3, intersection types are type1, type2, and type3.

Another significant difference between these types is that while union types can be strings or numbers, intersection types cant be strings and numbers. Data cant be a string and a number at the same time. So, intersection types involve objects.

Now that weve discussed the difference between union and intersection types, lets explore some ways of doing intersections:

...  interface Profile {  name: string;  phone: string;  }interface AuthCreds {  email: string;  password: string;  }interface User: Profile & AuthCreds  ...

Profile and AuthCreds are examples of interface types that exist independently of each other. This independence means you can create an object of type Profile and another of type AuthCreds, and these objects may not be related together. However, you can intersect both types to make a bigger type: User. This types structure is an object with four properties: name, phone, email, and password, all of the string type.

Now you can create a User object like this:

...  const user:User = {  name: "user";  phone: "222222",  email: "[email protected]"  password: "***"  }  ...

TypeScript Generics

Sometimes, when you create a function, you know its return type. Here's an example:

...  interface AppleFruit {  size: number  }interface FruitDescription {  description: string;  }function describeFruit(fruit: AppleFruit): AppleFruit & FruitDescription {  return {  ...fruit,  description: "A fruit",  }  }const fruit: AppleFruit = {  size: 50  }describeFruit(fruit)  ...

In this example, the describeFruit function takes in a fruit parameter of the AppleFruit type. It returns an intersection type made of the AppleFruit and FruitDescription types.

However, what if you wanted this function to return descriptions for different fruit types? Generics are relevant here. Heres an example:

...  interface AppleFruit {  size: number  }interface OrangeFruit {  isOrangeColor: boolean;  }interface FruitDescription {  description: string;  }function describeFruit<T>(fruit: T): T & FruitDescription {  return {  ...fruit,  description: "A fruit",  }  }const appleFruit: AppleFruit = {  size: 50  }describeFruit(appleFruit)const orangeFruit: OrangeFruit = {  isOrangeColor: true  }describeFruit<OrangeFruit>(orangeFruit)  ...

The generic function describeFruit accepts different types. The code determines the type of fruit to pass when it calls the function.

The first time we call describeFruit, TypeScript automatically infers T to be AppleFruit because appleFruit is of that type.

The next time, we specify the Ts type to be OrangeFruit using "OrangeFruit" before calling the function.

These lines do the same thing, but, in some cases, the automatic inference may not be accurate.

We can pass different types to that function in our example, which simply returns an intersection of FruitDescription and the type we passed.

Heres an example of passing a type to a function using generics:

code

The describeFruit function has a type, as we originally defined it with OrangeFruit.

Path Aliases with TypeScript

You might usually import like this:

...  import Button from "../../../../components/Button"  ...

This command to import may be in different files that require this component. When you change the location of the Button file, you also need to change this import line in the various files that use it. This adjustment also results in more file changes to track in version control.

We can improve how we import by using alias paths.

TypeScript uses the tsconfig.json" file to store the configurations that enable it to work as you want. Inside, theres the paths property. This property lets you set path aliases for different directories in your application. Heres how it looks, using compilerOptions, baseUrl, and paths:

...  {  "compilerOptions": {  "baseUrl": ".", // required if "paths" is specified.  "paths": {  "components/*": ["./src/components/*"] // path is relative to the baseUrl  }  }  }  ...

With the components alias, you can now import like this:

...  import Button from "components/Button"  ...

Regardless of how deep you are in a directory, using this command correctly resolves the Button file.

Now, when you change your components location, all you must do is update the paths property. This method means more consistent files and fewer file changes for version control.

Using Libraries with Built-In TypeScript Support

When you use libraries without TypeScript support, you miss TypeScripts benefits. Even a simple mistake such as using the wrong data types for an argument or object property may cause you headaches without the warning TypeScript provides. Without this alert, your app might crash because it expected a string, but you passed a number instead.

Not every library has TypeScript support. When you install a library that does support TypeScript, it installs the distributed code with TypeScript declaration files.

An example of this type of library is ActiveReportsJS, a reporting solution for visualizing data in front-end applications. Libraries like this come with a complete set of TypeScript definitions that enable you to enjoy TypeScripts static typing.

Conclusion

In this article, weve explored how TypeScript improves JavaScript coding. Weve also discussed how TypeScript supports some functional programming techniques. This ability makes TypeScript suitable for object-oriented programming (OOP) developers and functional programmers.

As a next step, you can dive into TypeScripts config options. TypeScript provides this exhaustively thorough resource to help you build your next app. Also, explore GrapeCitys ActiveReportsJS to enhance your app with gorgeous charts and helpful reports.


Original Link: https://dev.to/grapecity/typescript-tips-and-tricks-26ki

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To