Skip to content

Instantly share code, notes, and snippets.

@adrienjoly
Last active June 25, 2020 16:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adrienjoly/e09a253cbff8d0111099c3b255f2c850 to your computer and use it in GitHub Desktop.
Save adrienjoly/e09a253cbff8d0111099c3b255f2c850 to your computer and use it in GitHub Desktop.
TypeScript helpers
// (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
// 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);
});
// (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)
// 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! 🎉
}
// 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