typescript notes (typeof, keyof, ReturnType, Parameters, Extract)

 

typeof

  • extract a type from runtime code, typeof "make a type of it" (i.e. from it)
  • e.g.   typeof "Hello world"  => string
  • only legal on variable names
  • can be used with functions or any other expressions

aside: typeof vs keyof: keyof takes a type and creates a string/number union of it's keys. So keyof creates a new type from a type ....whereas typeof creates a type from an expression. good post

Given a function:

const myFunc = (name: string) => {

  return `hello ${name}`;  

};

type the function into MyFunc

type MyFunc =  typeof myFunc;

type a function return type (using built in ReturnType)

type MyFuncReturn = ReturnType<typeof myFunc>;  // string

When the function changes, the types change!

You can also type the parameters using utility type Parameters

    type MyFuncParams = Parameters<typeof myFunc>;  // string

but Parameters will return a tuple (array), so you could index result like so:  MyFuncParams[0]

  • i.e. can use indexes on tuple types

Parameters is useful to extract type info from code you don't control e.g. external libraries


The return type of an async function is a promise (which wraps the result returned), so use the global Promise type when typing it

Awaited utility type is a way to unwrap Promises to get the returned type e.g. from async functions


Create a union type from keys of an object

  • using keyof and typeof together can give us the type we want, which updates automatically when the object changes
    • in this example myKeyedObject instance is converted to a type and then we use keyof to access the keys 
const myKeyedObject = { name: "denis", age: 40 };
type MyKeyedObjectKeys = keyof typeof myKeyedObject;
MyKeyedObjectKeys   // "name" | "age"
  •  
  • here's a case where we get all the values of the keys of an object using indexed access keyof as a shortcut
type Obj = typeof myKeyedObject;
type MyKeyedObjectKeyTypes = Obj[keyof Obj]
MyKeyedObjectKeyTypes   // "string" | "number" 

  • note if we had added "as const" to end of myKeyedObject then the last line ^ would have returned literals "denis" | "40" (instead of "string" | "number" )    

  • here's a cool helper to get values as types using a ValueOf generic

type ValueOf<T> = T[keyof T];

const myObj = {
  start: "startedit",
  created: "createdit",
  paid: "paidit"
} as const;

type MyObjType = typeof myObj

type ObjValue = ValueOf<MyObjType>;  // "startedit" | "createdit" | "paidit"
type ObjKey = keyof MyObjType;       // "start" | "created" | "paid"
 

...the ValueOf is same as: type ObjValue = MyObjType[keyof MyObjType]


Discriminating union

  • A union of types where each type in the union has a common field which is the "discriminator" to allow tell them apart
    • e.g. state where status could be one of "loading" | "success" | "error" (just an example)
  • within an if or switch (to narrow down) typescript will know which exact type you're using

Extract (utility type)
  • extract from a union 
    • type LoadingEvent = Extract<Event, { status: "loading"} >;
    • if any in union extend the 2nd param, then extract it
      • could end up with a union if more than one match
type Fruit = "apple" | "banana" | "orange"  
type BananaAndOrange = Extract<Fruit, "banana" | "orange"> 

If you just want to get the union type of a discriminated union then this will work:
          type StatusType = Event["status"];   // "loading" | "success" | "error"
...this will also work for any common property in the types


Exclude (utility type)
  • extract from a union all which don't match e.g. all but success
    • type LoadingEvent = Exclude<Event, { status: "success"} >;

Indexed Access Types (on objects or arrays)
  • access one or more properties on a type using indexed access type, ts docs
  • e.g.
  • type Person = { age: number; name: string; alive: boolean, addr: { st: number } } };
    type Age = Person["age"];
    type I1 = Person["age" | "name"]
    • type I2 = Person[keyof Person];
  •  
  • you can go deep into a nested object to get a type
    • type Z3 = Person['addr']['st']
  • also works indexing on an array!
  • you can also convert an object to a type using typeof and then index to get types and create a union like this:
type PE = typeof programModeEnumMap;
export type IndividualProgram = PE["ONE_ON_ONE"] | PE['SELF_DIRECTED'] | 
  PE['PLANNED_ONE_ON_ONE'];

OR...pass a union to the type and get back a union

export type IndividualProgram = typeof programModeEnumMap[

  | "ONE_ON_ONE"

  | "SELF_DIRECTED"

  | "PLANNED_ONE_ON_ONE"

  | "PLANNED_SELF_DIRECTED"

  • you can also index into arrays, e.g. 

const fruits = ["apple", "banana", "orange"] as const;
type AppleOrBanana = typeof fruits[0 | 1];
type Fruit = typeof fruits[number];



as const

  • convert entire object (or array) to type literals, in code below you can't write req.method = "POST" 
    • works with object nesting (unlike Object.freeze which only works on 1st level)
    • as const freezes values as literal types and also adds a readonly annotation
  • literal unions make use of this e.g. type Status = "loading" | "error";
  • you can combine with non-literal types

const req = {
url: "https://example.com",
method: "GET" }
as const;



template literals
  • its like regex for types!
  • you could define a type check to ensure that routes must start with a "/" e.g. "/", "/users" etc
type Route = `/${string}`

  • create type for those routes which have substitution param
type Routes = "/users" | "/users/:id" | "/posts" | "/posts/:id";
type DynamicRoutes = Extract<Routes, `${string}:${string}`>;
// "/users/:id" | "/posts/:id" 

    • union of strings of all permutations (this is wild)

    type BreadType = "rye" | "brown" | "white";
    type Filling = "cheese" | "ham" | "salami";
    type Sandwich = `${BreadType} sandwich with ${Filling}`;
    // "rye sandwich with cheese" | "rye sandwich with ham" | etc


    • using ts-toolbelt to split and join type literals 

    import { S } from "ts-toolbelt";
    type Path = "Users/John/Documents/notes.txt";
    type SplitPath = S.Split<Path, '/'>;  // ["Users", "John", "Documents", "notes.txt"]
    type BackAgain = S.Join<SplitPath, ':'>  // "Users:John:Documents:notes.txt"

    • turn string literals into object with names as keys
    type TemplateLiteralKey = `${"user" | "post" | "comment"}${"Id" | "Name"}`;
    type ObjectOfKeys = Record<TemplateLiteralKey, string>;   // { userId: string, userName: string...}

    type UprObjectOfKeys = Record<Uppercase<Event>, string>;  // uppercase






    Comments

    Popular posts from this blog

    deep dive into Material UI TextField built by mui

    angular js protractor e2e cheatsheet

    react-router v6.4+ loaders, actions, forms and more