Home > Software design >  Typescript - Literal Type inference - Different behavior from strings and numbers
Typescript - Literal Type inference - Different behavior from strings and numbers

Time:10-04

As per TypeScript Literal Types, we can refer to specific strings and numbers in type positions. So the below is valid.

function printText(s: string, alignment: "left" | "right") {
  // ...
}
printText("Hello, world", "left");  // This works
printText("G'day, mate", "centre"); // This gives error - Argument of type '"centre"' is not assignable to parameter of type '"left" | "right"'.

However, this does not apply when a variable is initialized with an object because TypeScript assumes that the properties of that object might change values later. The workaround to this is to add a type assertion (in either of the locations - change 1 and change 2).

For e.g.

// Change 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// Change 2
handleRequest(req.url, req.method as "GET");

The above will work. But if I try to insert some different strings in method property, those will also be considered valid. Although not if I add a number. For e.g.

function handleRequest(url: string, method: "GET"|"POST"):void{
console.log(method);
}

const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method as "POST"); // This is accepted and works

const req1 = { url: "https://example.com", method: "ABCD" };
handleRequest(req1.url, req1.method as "GET"); // This is accepted and works

const req2 = { url: "https://example.com", method: 2 };
handleRequest(req2.url, req2.method as "GET"); // This throws an error - Conversion of type 'number' to type '"GET"' may be a mistake because neither type sufficiently overlaps with the other.

TypeScript is able to properly compile when comparing to numbers but not when other strings are involved ?

CodePudding user response:

Type assertions are a way to tell typescript to not check your work. If you use them incorrectly, you can introduce bugs. Because of this possibility to cause bugs, typescript does do some minimal checking even when you do a type assertion, and if it sees that the type you're asserting is wildly different than the calculated type, it gives you a type error.

So when you try to assert that a general string is a specific string, typescript thinks "yeah, that's close enough, i'll do as they asked". But when you try to assert that a number is a string, typescript say "Woah, that makes no sense. Are you sure?". If you really are sure, then you can shut typescript up for good by doing, in your case, req2.method as unknown as "GET"

PS: a safer option would be to use as const. This tells typescript to assume that the values won't be changed, but otherwise to use exactly what it sees in your code. For example:

const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method); // This is accepted and works

const req1 = { url: "https://example.com", method: "ABCD" } as const;
handleRequest(req1.url, req1.method); // This correctly throws an error

const req2 = { url: "https://example.com", method: 2 } as const;
handleRequest(req2.url, req2.method); // This correctly throws an error

Or, give it an explicit type and skip the need for assertions entirely.

const req1: { url: string, method: 'GET' | 'POST' } = {
  url: "https://example.com",
  method: "ABCD", // Error here
}
  • Related