Skip to content

rmatu/typescript-knowledge-bank

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Knowledge bank on advanced TypeScript

Knowledge gained from

Description

00-tuple-to-object

YouTube Code

Give an array, transform into an object type and the key/value must in the given array.

type TupleToObject<TTuple extends readonly PropertyKey[]> = {
  [TIndex in TTuple[number]]: TIndex;
};

type PK = PropertyKey; // string | number | symbol;

type ArrayMember = typeof tuple[number]; // "tesla" | "model 3" | "model X" | "model Y";

const tuple = ["tesla", "model 3", "model X", "model Y"] as const;
const tupleNumber = [1, 2, 3, 4] as const;
const tupleMix = [1, "2", 3, "4"] as const;

type cases = [
  Expect<
    Equal<
      TupleToObject<typeof tuple>,
      { tesla: "tesla"; "model 3": "model 3"; "model X": "model X"; "model Y": "model Y" }
    >
  >,
  Expect<Equal<TupleToObject<typeof tupleNumber>, { 1: 1; 2: 2; 3: 3; 4: 4 }>>,
  Expect<Equal<TupleToObject<typeof tupleMix>, { 1: 1; "2": "2"; 3: 3; "4": "4" }>>
];

/**
 * Should error
 */
type error = TupleToObject<[[1, 2], {}]>;

01-index-access

YouTube Code

interface ColorVariants {
  primary: "blue";
  secondary: "red";
  tertiary: "green";
}

type PrimaryColor = ColorVariants["primary"]; // type PrimaryColor = "blue"
type NonPrimaryColor = ColorVariants["secondary" | "tertiary"]; // type NonPrimaryColor = "red" | "green"
type EveryColor = ColorVariants[keyof ColorVariants]; // type EveryColor = "blue" | "red" | "green"

type Letters = ["a", "b", "c"];

type AOrB = Letters[0 | 1]; // type AOrB = "a" | "b";
type Letter = Letters[number]; // type Letter = "a" | "b" | "c";

interface UserRoleConfig {
  user: ["view", "create", "update"];
  superAdmin: ["view", "create", "update", "delete"];
}

type Role = UserRoleConfig[keyof UserRoleConfig][number]; // type Role = "view" | "create" | "update" | "delete"

02-operating-on-object-keys

YouTube Code

type RemoveMaps<T> = T extends `maps:${infer U}` ? U : T;

interface ApiData {
  "maps:longitude": string;
  "maps:latitude": string;
  awesome: boolean;
}

type RemoveMapsFromObj<T> = {
  [K in keyof T as RemoveMaps<K>]: T[K];
};

type DesiredShape = RemoveMapsFromObj<ApiData>;

// type DesiredShape = {
//   longitude: string;
//   latitude: string;
//   awesome: boolean;
// }

03-loose-autocomplete-react

YouTube Code

type IconSize2 = "sm" | "xs" | Omit<string, "xs" | "sm">;
type IconSize = LooseAutocomplete<"sm" | "xs">;

type LooseAutocomplete<T extends string> = T | Omit<string, T>;

interface IconProps {
  size: IconSize;
}

export const Icon = (props: IconProps) => {
  return <></>;
};

const Comp1: React.FC = () => {
  return (
    <>
      <Icon size="xs" />
      <Icon size="something" />
    </>
  );
};

04-readonly

YouTube Code

Implement the built-in Readonly<T> generic without using it.

Constructs a type with all properties of T set to readonly, meaning the properties of the constructed type cannot be reassigned.

interface Todo {
  title: string;
  description: string;
}

interface Todo2 {
  title: string;
  description: string;
  address: {
    street: string;
    houseNumber: number;
  };
}

type MyReadonly<TInput> = {
  readonly [Key in keyof TInput]: TInput[Key];
};

type MyResult = MyReadonly<Todo2>;

// type MyResult = {
//   readonly title: string;
//   readonly description: string;
//   readonly address: {
//       street: string;
//       houseNumber: number;
//   };
// }

const todo: MyReadonly<Todo> = {
  title: "Hey",
  description: "foobar",
};

/**
 * Should error
 */
todo.title = "Hello"; // Error: cannot reassign a readonly property
todo.description = "barFoo"; // Error: cannot reassign a readonly property

05-dynamic-function-arguments

YouTube Code

export type Event =
  | {
      type: "LOG_IN";
      payload: {
        userId: string;
      };
    }
  | {
      type: "SIGN_OUT";
    };

const sendEvent = <Type extends Event["type"]>(
  ...args: Extract<Event, { type: Type }> extends { payload: infer TPayload }
    ? [type: Type, payload: TPayload]
    : [type: Type]
) => {};

/**
 * Correct
 */
sendEvent("SIGN_OUT");
sendEvent("LOG_IN", { userId: "123" });

/**
 * Should error
 */
sendEvent("SIGN_OUT", {});
sendEvent("LOG_IN", { userId: 123 });
sendEvent("LOG_IN", {});
sendEvent("LOG_IN", {});

06-tuple-length

YouTube Code

For given a tuple, you need create a generic Length, pick the length of the tuple

type Length<TTuple extends readonly any[]> = TTuple["length"];

const tesla = ["tesla", "model 3", "model X", "model Y"] as const;
const spaceX = ["FALCON 9", "FALCON HEAVY", "DRAGON", "STARSHIP", "HUMAN SPACEFLIGHT"] as const;

type cases = [
  Expect<Equal<Length<typeof tesla>, 4>>,
  Expect<Equal<Length<typeof spaceX>, 5>>,
  /**
   * Should error
   */
  Length<5>,
  Length<"hello world">
];

07-first-of-array

YouTube Code

Implement a generic First<T> that takes an Array T and returns it's first element's type.

type First<TArray extends any[]> = TArray extends [infer TFirst, ...any[]] ? TFirst : never;

type Result = First<["a", "b"]>;

type cases = [
  Expect<Equal<First<[3, 2, 1]>, 3>>,
  Expect<Equal<First<[() => 123, { a: string }]>, () => 123>>,
  Expect<Equal<First<[]>, never>>,
  Expect<Equal<First<[undefined]>, undefined>>
];

/**
 * Should error
 */
type errors = [First<"notArray">, First<{ 0: "arrayLike" }>];

08-easy-pick

YouTube Code

Implement the built-in Pick<T, K> generic without using it.

Constructs a type by picking the set of properties K from T

type MyPick<TObj, TKey extends keyof TObj> = {
  [SpecificKey in TKey]: TObj[SpecificKey];
};

type Result = MyPick<{ a: number; b: number }, "a">;

// type Result = {
//   a: number;
// }

type AnotherResult = MyPick<{ a: number; b: number }, "a" | "b">;

// type AnotherResult = {
//   a: number;
//   b: number;
// }

09-modules-into-types

YouTube Code

export type ActionModule = typeof import("./constants");

export type Action = ActionModule[keyof ActionModule]; // "ADD_TODO" | "REMOVE_TODO" | "EDIT_TODO";

10-deep-partial

YouTube Code

type DeepPartial<T> = T extends Function
  ? T
  : T extends Array<infer InferredArrayMember>
  ? DeepPartialArray<InferredArrayMember>
  : T extends object
  ? DeepPartialObject<T>
  : T | undefined;

interface DeepPartialArray<T> extends Array<DeepPartial<T>> {}

type DeepPartialObject<T> = {
  [Key in keyof T]?: DeepPartial<T[Key]>;
};

interface Post {
  id: string;
  comments: { value: string }[];
  meta: {
    name: string;
    description: string;
  };
}

const post: DeepPartial<Post> = {
  id: "1",
  meta: {
    description: "123",
  },
};

/**
 * TypeScript Partial works only 1 level deep
 */
const secondPost: Partial<Post> = {
  id: "1",
  //@ts-expect-error
  meta: {
    description: "123",
  },
};

11-decode-search-params

YouTube Code

import { String, Union } from "ts-toolbelt";

const query = `/home?a=wonderful&b=wow`;

type Query = typeof query; // /home?a=wonderful&b=wow;

type SeconQueryPart = String.Split<Query, "?">[1]; // "a=wonderful&b=wow"
type QueryElements = String.Split<SeconQueryPart, "&">; // ["a=wonderful", "b=wow"]

type QueryParams = {
  [QueryElement in QueryElements[number]]: {
    [Key in String.Split<QueryElement, "=">[0]]: String.Split<QueryElement, "=">[1];
  };
}[QueryElements[number]];

const obj: Union.Merge<QueryParams> = {
  a: "wonderful",
  b: "wow",
};

// const obj: {
//   a: "wonderful";
//   b: "wow";
// }

12-remove-member-of-union

YouTube Code

export type Letters = "a" | "b" | "c";

type RemoveC<TType> = TType extends "c" ? never : TType;

type WithoutC = RemoveC<Letters>; // "a" | "b"

13-deep-value

YouTube Code

const getDeepValue = <Obj, FirstKey extends keyof Obj, SecondKey extends keyof Obj[FirstKey]>(
  obj: Obj,
  firstKey: FirstKey,
  secondKey: SecondKey
): Obj[FirstKey][SecondKey] => {
  return {} as any;
};

const obj = {
  foo: {
    a: true,
    b: 2,
  },
  bar: {
    c: "cool",
    d: 2,
  },
};

const numberResult = getDeepValue(obj, "bar", "d"); // number
const stringResult = getDeepValue(obj, "bar", "c"); // string

14-props-from-react-component

YouTube Code

const MyComponent = (props: { enabled: boolean }) => {
  return null;
};

class MyOtherComponent extends React.Component<{ enabled: boolean }> {}

type PropsFrom<TComponent> = TComponent extends React.FC<infer Props>
  ? Props
  : TComponent extends React.ComponentClass<infer Props>
  ? Props
  : never;

const props: PropsFrom<typeof MyComponent> = {
  enabled: false,
};

// const props: {
//   enabled: boolean;
// }

15-key-remover

YouTube Code

const makeKeyRemover =
  <Key extends string>(keys: Key[]) =>
  <Obj>(obj: Obj): Omit<Obj, Key> => {
    return {} as any;
  };

const keyRemover = makeKeyRemover(["a", "b"]);
const newObject = keyRemover({ a: 1, b: 2, c: 3 });

/**
 * Correct
 */
newObject.c;

/**
 * Should error
 */
newObject.a;
newObject.d;

16-iterating-over-object-keys

YouTube Code

export const myObject = {
  a: 1,
  b: 2,
  c: 3,
};

const objectKeys = <Obj>(obj: Obj): (keyof Obj)[] => {
  return Object.keys(obj) as (keyof Obj)[];
};

objectKeys(myObject).forEach((key) => {
  console.log(myObject[key]); // key: "a" | "b" | "c"
});

17-custom-errors

YouTube Code

type CheckForBadArgs<Arg> = Arg extends any[]
  ? "You cannot compare two arrays using deepEqualCompare"
  : Arg;

export const deepEqualCompare = <Arg>(
  a: CheckForBadArgs<Arg>,
  b: CheckForBadArgs<Arg>
): boolean => {
  if (Array.isArray(a) || Array.isArray(b)) {
    throw new Error("You cannot compare two arrays using deepEqualCompare");
  }
  return a === b;
};

deepEqualCompare(1, 1);

/**
 * Should error
 */
deepEqualCompare([], []);

18-nested-object-keys

Code

export type DotPrefix<T extends string> = T extends "" ? "" : `.${T}`;

export type DotNestedKeys<T> = T extends Date | Function | Array<any>
  ? ""
  : (
      T extends object
        ? { [K in Exclude<keyof T, symbol>]: `${K}${DotPrefix<DotNestedKeys<T[K]>>}` }[Exclude<
            keyof T,
            symbol
          >]
        : ""
    ) extends infer D
  ? Extract<D, string>
  : never;

const obj = {
  a: { a1: "a1", a2: 2, a3: { "a3-1": "a3-1", "a3-2": "a3-2", "a3-3": "a3-3" } },
  b: true,
};

export type example = DotNestedKeys<typeof obj>; // "b" | "a.a1" | "a.a2" | "a.a3.a3-1" | "a.a3.a3-2" | "a.a3.a3-3"

19-modify-nested-object-values

Full Code

/**
/**
 * Clones object and sets all key values to empty string "".
 *
 * If you want to overwrite value of object's key, you can pass an array of option objects
 * that consists of key - which targets the value you want to modify, and a value that modifies the value
 *
 * For example:
 *
 * const example = { a: { a1: "a1", a2: 2 }, b: true };
 *
 * copyAndSetObjectValues(example, {options: [{ key: "a.a1", value: "new value" }]})
 *
 * Will return:
 *
 * { a: { a1: 'new value', a2: '' }, b: '' }
 * @param {T} obj - Object to clone.
 * @param {{ options?: {key: DotNestedKeys<T>, value: unknown}, defaultOverwrite?: unknown }} config - Config object that let you target keys or set defaultOverwrite value
 */
const copyAndSetObjectValues = <T>(obj: T, config?: Config<T>) => {
  return recursiveObjectModify(JSON.parse(JSON.stringify(obj)), config);
};

const example = { a: { a1: "a1", a2: 2 }, b: true };
copyAndSetObjectValues(example, {
  options: [{ key: "a.a1", value: "new value" }],
  defaultOverwrite: "defaultValue",
});
// { a: { a1: 'new value', a2: 'defaultValue' }, b: 'defaultValue' }

About

Collection of TypeScript Advanced typings

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published