DEV Community

Cover image for 5 Secret Typescript Tricks You Should Know
cynthia
cynthia

Posted on

5 Secret Typescript Tricks You Should Know

Welcome to the world of TypeScript, where modern JavaScript and static typing work together to provide developers better control over the quality, maintainability, and productivity of their code. Although you might be familiar with TypeScript's fundamentals, there are a few less well-known tips and tactics that can help you advance your TypeScript proficiency. This article will provide five exclusive TypeScript tips that will make your code simpler, more productive, and more expressive.

These techniques, which range from sophisticated type manipulation to ingenious uses of TypeScript's features, will not only impress your coworkers but also develop your coding skills. These tips will broaden your horizons and inspire you to go beyond the box whether you are an experienced TypeScript developer or just getting started.

So grab a seat as we explore these TypeScript secrets. Prepare to develop your programming abilities and fully utilize TypeScript's possibilities. Let's go out on a journey to discover five top-secret TypeScript techniques that you must be aware of!

  1. Mapped Types with Key Remapping: Mapped types with key remapping is a powerful TypeScript feature that allows you to construct new kinds by altering the keys of an existing type. This is incredibly handy when you need to adapt or modify the keys of an object type without modifying the data associated with those keys. Here's how it works:

Assume you already have a type named OriginalData:

type OriginalData = {
  id: number;
  name: string;
};
Enter fullscreen mode Exit fullscreen mode

Now, you want to create a new type where each key of OriginalData is modified by adding a prefix "modified_". You can achieve this using mapped types with key remapping:

type OriginalData = {
  id: number;
  name: string;
};

type ModifiedKeysData = {
  [K in keyof OriginalData as `modified_${string & K}`]: OriginalData[K];
};

// Usage
const data: ModifiedKeysData = {
  modified_id: 1,
  modified_name: "John",
};
Enter fullscreen mode Exit fullscreen mode

From the code above,The ModifiedKeysData type is constructed in this example by iterating through each key K in the keyof OriginalData. To add the "modified_" prefix to each key, use the remapping syntax modified_$string & K. The resulting ModifiedKeysData type has keys like modified_id and modified_name while retaining the original OriginalData values.

  1. Conditional Types with Inference Conditional types with inference are a powerful tool in TypeScript that allow you to create types that depend on certain conditions and also infer types based on those conditions. This enables you to create more dynamic and flexible type definitions. Here's how it works:
type Transform<T> = T extends string ? number : T;

function transformValue<T>(value: T): Transform<T> {
  if (typeof value === "string") {
    return value.length;
  }
  return value;
}

// Usage
const result: number = transformValue("hello"); // Result: 5

Enter fullscreen mode Exit fullscreen mode

The Transform type in the above instance accepts a type parameter T. If T is a string, the resulting type is number; otherwise, it is the same as T; if the input value is a string, it returns the length of the string (which is a number); otherwise, it returns the value as is. Because the input "hello" is a string, the result1 is inferred as a number.

  1. Template Literal Types for String Manipulation: TypeScript's template literal types allow you to manipulate and construct new kinds by utilizing template strings in the same way that template literals operate with strings at runtime. Here's how you can use template literal types to manipulate strings:
type Capitalize<S extends string> = `${Uppercase<S>[0]}${Lowercase<S> extends `${infer L}${infer R}` ? R : ""}`;

type CapitalizedHello = Capitalize<"hello">; // Result: "Hello"
Enter fullscreen mode Exit fullscreen mode

Here's a breakdown of the Capitalize type:

${Uppercase[0]}: This part extracts the first letter of S and converts it to uppercase using the Uppercase utility type. It uses indexing [0] to get the first character.

${S extends ${infer First}${infer Rest} ? Rest : ""}: This part checks if S can be split into ${First}${Rest} using the template literal syntax. If it can, it takes the Rest part (which is the remaining characters of the string after the first character) and adds it to the result. If not, it adds an empty string.

  1. Inferring Tuple Element Types Inferring tuple element types is another powerful feature in TypeScript that allows you to extract and infer the individual types of elements within a tuple. This is particularly useful when you want to create functions that operate on tuples and preserve type information. Here's how it works:
function getFirstAndLast<T extends any[]>(arr: T): [T[0], T[number]] {
  return [arr[0], arr[arr.length - 1]];
}

// Usage
const result = getFirstAndLast([1, 2, 3]); // Result: [1, 3]

Enter fullscreen mode Exit fullscreen mode

From the code above example, the function getFirstAndLast takes an input arr of type T, which is a tuple type due to the restriction T extends any[]. The function's return type is a tuple, with the first element's type being T[0] and the second element's type being Tnumber. Lastly typeScript infers that the result is of type [number, number] because the input array [1, 2, 3] is inferred as a tuple of numbers.

  1. Distributive Conditional Types: Distributive conditional types are a fascinating feature in TypeScript that enable type transformations to be applied to each element of a union type separately. This distribution happens automatically when a conditional type is used with a union type. Let's delve into this concept:

Consider the following example of a distributive conditional type:

type StringOrNumber<T> = T extends string ? string : number;

type Distributed = StringOrNumber<"a" | "b">; // Result: "a" | "b"

Enter fullscreen mode Exit fullscreen mode

The StringOrNumber type in this example accepts a type parameter T. It examines whether T extends (is assignable to) string within the conditional type. The type evaluates to string if true; else, it evaluates to number.

This is where distribution comes into play. When you apply StringOrNumber to a union type, such as "a" | "b", TypeScript applies the conditional type to each element of the union independently. To put it another way, it spreads the conditional type among the union elements.

Conclusion

TypeScript is a complex programming language with advanced functionality beyond the fundamentals. Exploring these lesser-known approaches might help you uncover new levels of expressiveness, maintainability, and flexibility in your software.

Top comments (0)