Tricksters of Typescript
Tags: typescript, javascript, tricks, programming
Published: 2023-11-08 19:07:47

TypeScript is a statically typed superset of JavaScript that helps developers write safer and more maintainable code. It enforces strict typing rules that catch many common programming errors at compile-time rather than runtime. However, like any language, TypeScript has its fair share of quirks and tricks that can catch even experienced developers off guard. In this blog, we'll explore some of the tricksters of TypeScript and provide code examples to help you understand and overcome these challenges.

---

### The "this" Keyword Trickster

One of the common sources of confusion in TypeScript is the behavior of the this keyword. Understanding how `this` works in different contexts is essential for writing reliable TypeScript code.

```TypeScript
class Trickster {
  private name: string = "The Trickster";

  printName() {
    console.log(this.name);
  }

  withTimeout() {
    setTimeout(function () {
      console.log(this.name); // Oops, this.name is undefined!
    }, 1000);
  }
}

const tr = new Trickster();
tr.printName(); // Works as expected
tr.withTimeout(); // Throws an error

```

In the code above, when you call `tr.printName()` it logs "The Trickster" to the console as expected. However, when you call `tr.withTimeout()`, you might expect it to log the same value, but it actually logs `undefined`. This is because `this` inside the `setTimeout` callback refers to a different context.

To fix this, you can use an arrow function, which preserves the outer `this` context:

```TypeScript
withTimeoutFixed() {
  setTimeout(() => {
    console.log(this.name); // Works as expected
  }, 1000);
}
```

---

### The "any" Type Trickster 

TypeScript is all about static typing, but sometimes, you might encounter the `any` type, which effectively turns off type checking. While `any` can be useful in certain situations, overusing it can undermine the benefits of TypeScript.


```TypeScript
function add(a: any, b: any): any {
  return a + b; // No type checking here
}

const result = add(5, "10"); // No error at compile-time
console.log(result); // "510"
```

Here, the function `add` accepts two arguments of type `any`, and it returns `any`. This means that you can pass any types to this function without TypeScript raising any warnings or errors. In the example, adding a number and a string results in a concatenated string, which might not be what you intended.

To fix this, you should specify the types explicitly or use union types:

```TypeScript
function addTyped(a: number, b: number): number {
  return a + b; // Type-safe
}

const result = addTyped(5, 10); // Type error if you pass incompatible types
console.log(result); // 15
```
---

### The "Never" Type Trickster

The `never` type in TypeScript represents values that never occur. It is often used in functions that throw exceptions or never return.

```TypeScript
function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {
    // Infinite loop
  }
}
```

The `never` type can be tricky to understand, especially when you encounter it for the first time. It essentially signifies that a function won't produce a value, and it's useful for making your code more expressive and error-resistant.

---

### The "Type Assertion" Trickster

Type assertions, denoted by the `<Type>` or `as Type` syntax, allow you to tell the TypeScript compiler to treat an expression as a specific type.

```TypeScript
const someValue: any = "Hello, TypeScript!";
const strLength: number = (someValue as string).length;
```

While type assertions can be helpful in certain situations, they should be used cautiously. Relying too much on them can lead to runtime errors and bypass TypeScript's type checks.

---

### The "Type Narrowing" Trickster

Type narrowing in TypeScript refers to the process of refining the type of a variable within a block of code, based on conditions or operations. While it's a powerful feature for making your code type-safe, it can sometimes behave unexpectedly.

```TypeScript
function narrowTypeExample(input: string | number) {
  if (typeof input === 'string') {
    // Within this block, TypeScript knows that input is a string
    console.log(input.toUpperCase()); // Works fine
  } else {
    // TypeScript considers input to be a number here
    console.log(input.toUpperCase()); // Error: Property 'toUpperCase' does not exist on type 'number'
  }
}
```

In the code above, TypeScript narrows the type of `input` to `string` within the first `if` block, allowing you to call the `toUpperCase` method. However, in the `else` block, TypeScript treats `input` as a `number`, resulting in a type error when trying to use `toUpperCase`.

---

### The "Type Inference" Trickster

Type inference is one of TypeScript's strengths, but it can sometimes lead to surprising behavior if you're not careful. TypeScript often infers types from the values you assign to variables, and this can lead to unexpected results.

```TypeScript
let x = 5; // TypeScript infers the type of x as number

x = "Hello, TypeScript!"; // Error: Type 'string' is not assignable to type 'number'
```

In the code above, TypeScript initially infers that `x` is a number based on the assigned value. However, when you later try to assign a string to `x`, TypeScript throws an error because it expects `x` to be of type `number`. This is where type annotations can be useful to explicitly specify the type of a variable.

```TypeScript
let x: number = 5; // Explicitly specifying the type
x = "Hello, TypeScript!"; // Error: Type 'string' is not assignable to type 'number'
```

---

### The "Tuple vs. Array" Trickster

TypeScript allows you to define both arrays and tuples, but understanding the differences between them is crucial. Arrays are meant for collections of values of the same type, while tuples allow you to specify the types of elements at specific positions.

```TypeScript
let array: number[] = [1, 2, 3];
let tuple: [number, string] = [1, "two"];
```
Here's where the trickster comes in:
```TypeScript
let array: number[] = [1, "two", 3]; // No error at compile-time
let tuple: [number, string] = [1, "two", 3]; // Error: Type 'number' is not assignable to type 'string'
```

In the first case, TypeScript doesn't catch the error because arrays are meant to hold elements of the same type. In the second case, TypeScript correctly identifies the type mismatch because tuples have predefined types for each position.

---

### The "Optional Properties" Trickster

TypeScript allows you to define optional properties in interfaces and object types using the `?` syntax.

```TypeScript
interface Person {
  name: string;
  age?: number; // Age is an optional property
}

const john: Person = { name: "John" }; // Works fine
```
This flexibility can lead to subtle bugs if you forget to check for the existence of optional properties:
```TypeScript
function greet(person: Person) {
  console.log(`Hello, ${person.name}! You are ${person.age} years old.`);
}

greet(john); // Error: Object is possibly 'undefined'.
```
In this example, the `greet` function assumes that the `age` property exists, but TypeScript raises a type error because `age` is optional and might not be present on the `john` object.

To handle optional properties safely, you can use a conditional check or the nullish coalescing operator (`??`):

```TypeScript
function greetSafe(person: Person) {
  console.log(`Hello, ${person.name}! You are ${person.age ?? 'unknown'} years old.`);
}

greetSafe(john); // No error, handles optional property gracefully
```

---

> I hope you found this blog helpful. If you have any questions, please feel free to leave a comment below. 🤗

I can write a blog like that about any programming language or framework/lib. What would you like me to blog about next time?
[Send me a coffee ☕](https://www.buymeacoffee.com/hakimovDev) with what you would like me to write about next time and I will definitely consider your suggestion and if I like your suggestion I will write about your suggestion in the next blog post. 😉