Last active
June 25, 2020 16:23
-
-
Save adrienjoly/e09a253cbff8d0111099c3b255f2c850 to your computer and use it in GitHub Desktop.
TypeScript helpers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// (from https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions) | |
// Assertion Function that will either throw at runtime or | |
// help TypeScript infer/refine the type of the parameter. | |
function assertIsDefined<T>(val: T): asserts val is NonNullable<T> { | |
if (val === undefined || val === null) { | |
throw new Error(`Expected 'val' to be defined, but received ${val}`); | |
} | |
} | |
// Usage / Example | |
const data: { val: number } | undefined = JSON.parse('undefined'); | |
data.val; // ❌ TS warning (2532): Object is possibly 'undefined'. | |
assertIsDefined(data); // may throw at runtime | |
data.val; // ✅ no TS warning, thanks to call assertion function |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Iterate over defined properties of a mapped type. (incl. Record<>) | |
// Will call fct() for each property of obj that is not undefined. | |
// Contract: obj should only contain properties that were specified in its type definition. | |
const forEachPopulatedProp = <T>( | |
obj: T, | |
fct: (key: keyof T, val: Required<T>[keyof T]) => unknown | |
) => | |
(Object.keys(obj) as Array<keyof T>).forEach((key) => { | |
if (obj[key] !== undefined) fct(key, obj[key]); | |
}); | |
// Usage / Example | |
forEachPopulatedProp({ a: 1, b: undefined }, (key, value) => { | |
assert(key === 'a'); // => typeof key === 'a' | 'b' (i.e. more specific than string) | |
assert(value !== undefined); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// (from https://dev.to/busypeoples/notes-on-typescript-recursive-types-and-immutability-5ck1) | |
// Makes a deeply immutable version of Type, using recursion. | |
type MakeReadOnly<Type> = { | |
readonly [Key in keyof Type]: MakeReadOnly<Type[Key]>; | |
}; | |
// Usage / Examples | |
const shape = { | |
color: 'green', | |
attributes: [] as number[], | |
} as const; | |
shape.color = 'red'; // ✅ (2540) Cannot assign to 'color' because it is a read-only property. | |
shape.attributes.push(1); // 🔴 this remains allowed, because `as const` does not apply deep immutability | |
const immutableShape: MakeReadOnly<typeof shape> = shape | |
immutableShape.color = 'red'; // ✅ (2540) Cannot assign to 'color' because it is a read-only property. | |
immutableShape.attributes.push(1); // ✅ Property 'push' does not exist on type 'readonly number[]'.(2339) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Type-aware alternative to Object.entries(). | |
// The returned pairs are types accordingly to the type definition of obj. | |
const objectEntries = <T>(obj: T): [keyof T, T[keyof T]][] => | |
(Object.keys(obj) as (keyof T)[]).map((key) => [key, obj[key]]) | |
// ⚠️ Be aware that, at runtime, obj may actually have more keys than its type defines. | |
// This function will not protect you against those additional keys => make sure to | |
// check that at run time, if necessary. | |
// It can be used to iterate on the properties of a typed object, | |
// while respecting the type of their keys: | |
// - Problem: key is not typed when iterating over an object using a for-in loop | |
type MyType = { a: unknown, b: unknown }; | |
const key: MyType = { a: 1, b: 2}; | |
for (const key in batch) { | |
batch[key]; // ts(7053): Element implicitly has an 'any' type because expression of type 'string' can't be used to index the type of 'batch' | |
} | |
// - solution 1: specify the type of the key before the for loop | |
const batch: MyType = { a: 1, b: 2}; | |
let key: keyof MyType; // 😩 | |
for (key in batch) { | |
batch[key]; // OK | |
} | |
// - solution 2: cast the key at time of use | |
const batch: MyType = { a: 1, b: 2}; | |
for (const key in batch) { | |
batch[key as keyof MyType]; // OK, but still 😩 | |
} | |
// - solution 3: cast the iterated array | |
const batch: MyType = { a: 1, b: 2}; | |
(Object.keys(batch) as Array<keyof MyType>).forEach((key) => { | |
batch[key]; | |
}); | |
// - solution 4: extract the possible key values from string (https://stackoverflow.com/a/56133795/592254) | |
const batch: MyType = { a: 1, b: 2}; | |
Object.keys(batch).forEach((key: Extract<keyof MyType, string>) => { | |
batch[key]; | |
}); | |
// - solution 5: inherit the type of keys | |
const batch: MyType = { a: 1, b: 2}; | |
for (const [key, value] of objectEntries(batch)) { | |
batch[key]; // It works! 🎉 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Omit properties of an object and return its resulting type. | |
function omitProps<Source, Exclusions extends Array<keyof Source>>( | |
object: Source, | |
...propNames: Exclusions | |
): Omit<Source, Exclusions[number]> { | |
const result: Omit<Source, Exclusions[number]> = Object.assign({}, object); | |
for (const prop of propNames) { | |
delete (result as any)[prop]; | |
} | |
return result; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment