-
-
Save angelikatyborska/cea5696595be44b930498bfd5e124454 to your computer and use it in GitHub Desktop.
// If I define a union type of strings and want to use it as a type for object keys: | |
type Food = 'Banana' | 'Berries' | |
const calorieCounts: { [index in Food]: number } = {} // <- NOT `index: Food` | |
// ---------- | |
// If I define an object and want to use its keys as a type: | |
// NOTE the object shouldn't have a type definition for this to work! | |
const planetYearsToEarthYears = { earth: 1, mercury: 0.24 } | |
type Planet = keyof typeof planetYearsToEarthYears | |
// ---------- | |
// Official TS docs recommend to use Interface over Type | |
// https://www.typescriptlang.org/play?&e=83#code/PTAEBUAsFMCdtAQ3qALgdwPagLaIJYB2ammANgM4mgAm0AxmcgqjKBZIgA4KYBmSQgFgAUCFCYARgCsGqAFygiqOH0T1oVRIRpoAnjyRl8iCpoB0okFbBRoepCgBucBxXw58TWABpBuvkxYNDYcTApUUHpMHDDielNNGyR6SNYECkQcaEsRUVQDBAAhfFgacELQAF5QAG9RUFB0IgBzCkUAJgBuUQBfHryRZVV1YtKaAElCFVg1DTqGptb20G6+0VFowgjQSXGARkUSsorDGtqlwjbO0H7NzG3IvbKOo-GpmbmEc8vr1duBskigxEABXMwQQoAZXosHwXEi+C07FQsFBqVBsEQZH0hgoegi0BwPmS+FQAHIqFxwu5JGQWNhhrBPAAPELQUqgcEMzC5e6PXbjADMbzKHxG8xqzxo+0BYlsMAckkwrHYoK41NgkWgLJUOlaEnSwSZXy0OlxFmSpwQ2JMZioNGwrCRoCcJiU0zgZlS+AeFoofnQkC80FJntmoyonBcSFAAGt7FgynyRAVDAB5dA4n6ETAY2CEbGKVGghC9UAAMlAx3KhQGaYQACUpERqnVQLn84WyIo1JQy5Xq+9w185SbRqAAArQdR5uOgHV6mhUGvWhYiRrRMiYTF8UE9tBo6ADRp8YyaXvYswDXqicfzADCwfoCeIi+gOhXw8+E-qG6i5A7rM+6Xv2J6gGe+AXhBV7Husgz0pEmBZoombZu2zRXCsHR+J2qCYt2xZHgCoiIVEz6vooT74C+H5thcmF-DhAHbruIEwf2fiQdBfYQncgziFAiCRJk+A0IGCDwNEsQfroeg7lyEL3poEguMEDbJLaiQUOYoBQjw9D4HwNHYmQeh+JIILcqA8mgksZA4i00BPM5MwLrAsBBMk2QUJkTk6aAEwCLZoCQJgamqXA7IQeQ26Me5nmwAGNkKQkwjyuw0AIGF6CQjwMJwgiUTaKAXCeW6dBoF6UXaLoYQoIE9DcjQ3maH5KlBnRSZxgazSqspVDGAmoDUbRhApsh6H0BRH4DNNNGvm2k1yuI6aEAgeDSEEtBGXwcAfvMlkYFlxANkYdoqU4VADZpKCsMJHo-hoWgoJgPDELVFrndpjgIIw4TQDQuSCcGVDZNoVAhWlC66rJgiPRKCCSA4dCMMgBpkppmVbLoqAeDkGxDCOE4ANJknq66NFwmJJYoyrkDOhA3oTymgGTqAU3+m6AZiigRHCVzMwJYBTNFKowMECQQogX1pbmLnkdoTm6DuqDuJV-CkmrtAMN4wm+uNhNnZO6pcA45yLFuQR86irQ3nKxum+blMkASiiEKCOCWbA9uE+IAAi0DvTQBp+rZwQtJgV5+M6Dq7fthDzNE+66JZSDJNS7h4zG20y+tLT6y4ukABLIdAEWBME1N0jRZkw5ngPJA2yVkpSsZHW5CQOdQeDDekODw8pKaretEgCOkuyaJE8AUEBz0xcEZgclcRg4vw0WA05yRSypyA7uazeutdxPPTHoMouo87haocVN5A8CILoLoy5HmC6FwTDzKg2ARMgCh+2ASAHMuDtBAL-F819ZhxXMNJYAABHUsEQDYUGAEKAA7B0IU6ChQAFZgDN1hPCVAABaAaxCrrEObsAHBHQABsAAODoTCAAMABiGhDCmEdGYaIIAA | |
// ---------- | |
// > The `as const` suffix acts like const but for the type system, | |
// > ensuring that all properties are assigned the literal type instead of a more general version like string or number. | |
const req = { url: "https://example.com", method: "GET" } as const; | |
// ---------- | |
// `in` is for checking keys in objects, not for arrays! | |
> 'x' in {x: 31, y: 23} | |
true | |
> 'x' in ['x', 'y'] | |
false | |
> ['x', 'y'].includes('x') | |
true | |
// ---------- | |
// how to overload type definitions for one function | |
// (e.g. if given argument of type X, returns type A, if given argument of type Y, returns type B) | |
function plusOne(x: string): string; | |
function plusOne(x: number): number; | |
function plusOne(x: string | number) { // <- DO NOT define return type here | |
if (typeof x === 'number') { | |
return x + 1 | |
} else { | |
return `${x} plus one` | |
} | |
} | |
// (correct TS, but might break some eslint rules like no-redeclare and @typescript-eslint/explicit-function-return-type :<) | |
// ---------- | |
// `never` appears when TypeScript determines there’s nothing left in a union. | |
function fn(x: string | number) { | |
if (typeof x === "string") { | |
// do something | |
} else if (typeof x === "number") { | |
// do something else | |
} else { | |
x; // has type 'never'! | |
} | |
} | |
// ---------- | |
// object properties can be marked as readonly, but | |
// TypeScript doesn’t factor in whether properties on two types are readonly | |
// when checking whether those types are compatible | |
interface SomeType { | |
readonly prop: string; | |
} | |
// ---------- | |
// you can define an interface with both known and unknown keys of known types | |
interface NumberOrStringDictionary { | |
[index: string]: number | string; | |
length: number; // ok, length is a number | |
name: string; // ok, name is a string | |
} | |
// ---------- | |
// tuples are arrays of known length | |
type StringNumberPair = [string, number]; | |
type Either2dOr3d = [number, number, number?]; | |
// ---------- | |
// hints for using React with TS: | |
// https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/basic_type_example | |
// ---------- | |
// Types for React refs: | |
// - `MutableRefObject<T>` if you yourself assign to `ref.current` | |
// - `RefObject<T>` if you let React handle assignment | |
// | |
// In both cases, initial value of null as allowed even if T doesn't cover null | |
// ---------- | |
@jiegillet No, that doesn't achieve the same thing. With your example, calling const a = plusOne('hi')
results in the type of a
being string | number
, but the goal is to make it only a string.
I attempted a lot of things yesterday to make it happen, based on tutorials I saw, but nothing with generics worked. For example, this is wrong even though https://www.youtube.com/watch?v=lMfGp29Ht8c&list=PLIvujZeVDLMx040-j1W4WFs1BxuTGdI_b recommends this:
function plusOne<T extends number | string>(x: T): T extends number ? number : string {
if (typeof x === 'number') {
return x + 1
} else {
return `${x} plus one`
}
}
That gives me errors, including:
TS2322: Type 'number' is not assignable to type 'T extends number ? number : string'.
But my understanding of narrowing in TS is that it should work...
Right, I just got to the same result too, there must be something...
Ok, this works but... meh..
type GetReturnType<T> = T extends number ? number : string
function plusOne<T>(x: T): GetReturnType<T> {
if (typeof x === 'number') {
return x + 1 as GetReturnType<T>
} else {
return `${x} plus one` as GetReturnType<T>
}
}
YES! that works
...but it's also not correct 😵💫 if I accidentally mess up the number branch to return a string, TypeScript won't notice, it will still think it returned a number.
Yeah, I think it's a consequence of using as
, it basically mean "shut up, I know better than you, stupid machine"
...but it's also not correct 😵💫 if I accidentally mess up the number branch to return a string, TypeScript won't notice, it will still think it returned a number.
My knowledge of Typescript is very limited, but I don't think what you want can ever work with Typescript: the type erasure is done at "compile"/build time, not at runtime. Typescript cannot evaluate the if
clause at build time to know that one branch needs to return a number while the other one a string; it can only check that the function returns a type that satisfies the declared return type.
//
in
is for checking keys in objects, not for arrays!
That's the plain old Javascript in
operator though, not Typescript, right?
My knowledge of Typescript is very limited, but I don't think what you want can ever work with Typescript: the type erasure is done at "compile"/build time, not at runtime. Typescript cannot evaluate the if clause at build time to know that one branch needs to return a number while the other one a string; it can only check that the function returns a type that satisfies the declared return type.
I have been talking here about compile time checks only, no runtime stuff. It can work. TypeScript does a lot of static code analysis to narrow down types. See this modified example, with reloaded function signatures, where I make one of the return values have a type that doesn't fulfill the signature:
function plusOne(x: string): string;
function plusOne(x: number): number;
function plusOne(x: string | number) {
if (typeof x === 'number') {
return x + 1
} else {
return false
}
}
It produces this error:
file.tsx:83:10 - error TS2394: This overload signature is not compatible with its implementation signature.
83 function plusOne(x: string): string;
~~~~~~~
file.tsx:85:10
85 function plusOne(x: string | number) {
~~~~~~~
The implementation signature is declared here.
That's the plain old Javascript in operator though, not Typescript, right?
Yes! But I literally never used in
in JavaScript, and as it started appearing in TypeScript for type definitions and type guards, I got confused 😵
For
plusOne
, a good alternative is to use generics: