Skip to content

Instantly share code, notes, and snippets.

@ajitid
Last active April 29, 2023 17:55
Show Gist options
  • Save ajitid/0c0e224ff5022e0b26b0f537349f361d to your computer and use it in GitHub Desktop.
Save ajitid/0c0e224ff5022e0b26b0f537349f361d to your computer and use it in GitHub Desktop.
TS type utils
type F1 = (a: string, b:string) => void
type F2 = (a: number, b:number) => void
// remember & and | are intersection and union types respectively, intersection needs to be mutually-exclusive
// don't simplify & and | to "and" and "or"
const fn1: F1 & F2 = () => {}
fn1('a', 'b') // passes
fn1('a', 2) // fails
fn1(1, 2) // passes
// ^ not sure if this is a valid alternative of interface syntax of function overloading
// ---------------------
type LolThisPasses<A> = {
[key in keyof A]: 's'
}
interface WtfThisFails<A> {
[key in keyof A]: 's'
}
// this passes
interface LooseType {
[key: string]: any
}
// ---------------------
type Hein<A> = {
[key in keyof A]: 's'
}
const v1: Hein<'one' | 'two'> = 'one' // passes
type WhatIsThisBehaviour<A> = {
[key in keyof A]: 's'
}[keyof A]
const v3: WhatIsThisBehaviour<'one'> = 'mckmfk' // valid
const v4: WhatIsThisBehaviour<'one'> = () => 'mckmfk' // also valid
// ---------------------
type Sizes = 'huuugge' | 'smol'
type SizeMap = {
[s in Sizes]: number
}
// oh both keys are required
const sizes1: SizeMap = {
huuugge: 9999,
smol: 68+1,
}
// now they are optional
const size2: Partial<SizeMap> = {
huuugge: 9999,
}
// ----------------------------
interface Props<GenericData> {
columns: Array<keyof GenericData>,
data: GenericData[]
}
const Table = <T,>(props: Props<T>) => {
return null
}
const data = [
{a: 1, b: '2'},
{a: 2, b: '22'},
]
const app = <Table data={data} columns={["a"]} />
// ^ these are getting suggested
// ----------------------------------------
// Labelled tuple elements
type Range = [start: number, end: number];
// ----------------------------------------
const [,second] = l
const [first,second, ,fourth] = l
// [first, ...rest] -> rest element can only appear last
// ----------------------
enum L {
Up,
Down = 7,
Left,
Right
}
console.log(L.Up); // 0
console.log(L.Left); // 8
// assign Down a string instead and TS will throw error for Left and Right
// -----------------------
// Generic at interface level
interface Oho<T> {
a: T,
b: (arg: T) => void
}
// Generic at function level
interface Fn1<T> {
(arg: T): void
}
// This should be same as above unless fn is overloaded
interface Fn2 {
<T>(arg: T): void
}
// A and B are same here, both have generic at function level
type A = <U>(arg: U) => U
type B = {<U>(arg: U): U}
type C = {<U>(arg: U): void}
const fn:C = () => {}
// First line of this answer is very important here https://stackoverflow.com/a/62625899/7683365
// -----------------------
// for function overloading
// this won't work
interface Fn() {
(v: number)
(): number
}
function sum(initial: number) {
let result = initial
// but this will
function fn(): number;
function fn(v: number): typeof fn;
function fn(v?: number) {
if(v === undefined) return result
result += v
return fn
}
return fn
}
const v = sum(2)(2)(3)()
// -----------------------------
/**
* Awaited generic to unwrap a Promise resolve value.
* awaited type would be introduced in a later typescript version, this can be removed then
* https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html#what-about-the-awaited-type
*
* for Typescript < 4.1
*/
export type Awaited<T> = T extends {
then(onfulfilled?: (value: infer U) => unknown): unknown;
}
? U
: T;
// New Awaited
type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;
type PromiseType<T> = T extends Promise<infer U> ? U : never
/**
* Merge 2 types, properties defined at top level from the latter override the ones defined on the former type
*/
export type Merge<M, N> = Omit<M, keyof N> & N;
/**
* Goes a step ahead of Required type by ensuring values are non-null
*
* Taken from https://github.com/microsoft/TypeScript/issues/15012#issuecomment-365453623
*/
export type Purify<T> = T extends object
? { [P in keyof T]-?: NonNullable<T[P]> }
: T;
// use `declare type Subset<T, U> =` in `.d.ts` to get it w/o importing it
/**
* Subset
* Taken from https://youtu.be/PJjeHzvi_VQ?t=752
*
* @desc From `T` pick properties that exist in `U`. Simple version of intersection
*/
export type Subset<T, U> = {
[key in keyof T]: key extends keyof U ? T[key] : never;
}
/**
* Split S on string/character D
*
* @example
* If S is `"Hey there"` and D is `" "` (space) then it will split on space and give `["Hey", "there"]`
*/
type Split<S extends string, D extends string> =
S extends `${infer T}${D}${infer U}`
? [T, ...Split<U, D>]
: [S];
// Also known as `ReadOnly` in other packages
type ImmutablePrimitive =
| undefined
| null
| boolean
| string
| number
// It's fine here
// eslint-disable-next-line @typescript-eslint/ban-types
| Function;
declare type Immutable<T> = T extends ImmutablePrimitive
? T
: T extends Array<infer U>
? ImmutableArray<U>
: T extends Map<infer K, infer V>
? ImmutableMap<K, V>
: T extends Set<infer M>
? ImmutableSet<M>
: ImmutableObject<T>;
type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment