Dynamic function arguments with GENERICS - Advanced TypeScript

Поділитися
Вставка
  • Опубліковано 28 тра 2022
  • Become a TypeScript Wizard with Matt's upcoming TypeScript Course:
    www.mattpocock.com/
    Follow Matt on Twitter
    / mattpocockuk

КОМЕНТАРІ • 66

  • @ward7576
    @ward7576 2 роки тому +150

    This is enough to make a grown man cry.

    • @fridric2916
      @fridric2916 7 місяців тому

      It really is...

    • @tomasburian6550
      @tomasburian6550 4 місяці тому

      Advanced TS FTW yo!

    • @dheerajsinghnagdali
      @dheerajsinghnagdali Місяць тому

      It's not that difficult, to be honest.

    • @MeLuCk3R
      @MeLuCk3R 26 днів тому +1

      @@dheerajsinghnagdaliits ridiculously difficult for such simple thing)

    • @dheerajsinghnagdali
      @dheerajsinghnagdali 26 днів тому

      @@MeLuCk3R I'm looking for a partner to practice Typescript technical interviews if you're interested, lemme know. I agree btw.

  • @texoport
    @texoport 2 роки тому +13

    I spent many hours a while back trying to figure this one out! I have at least four places in my current codebase that could use this. Great video!

  • @RomanOstolosh
    @RomanOstolosh Рік тому +2

    This is simply mind blowing technique.
    Thank you Matt for everything you are doing. Every single video of yours is like a piece of wizardry, a potion that makes me a superhero

  • @jam-trousers
    @jam-trousers 2 роки тому +10

    This is great, been experimenting with generic void types as optional args, failed miserably haha

  • @arthurarthur732
    @arthurarthur732 Рік тому

    My gosh, it's exactly what I was trying to achieve recently and failed! This is super useful. Thank you, man!

  • @avaze7
    @avaze7 4 місяці тому

    This is crazy i just had this problem and solved it with function overloads generics and some helper types, googled an eternity how to do this better without results and a few days later this just pops up in my feed. Worked first try, youre a legend!

  • @0xAndy
    @0xAndy Рік тому

    I love this-thank you

  • @somebody-17546
    @somebody-17546 Рік тому

    Very useful . I will share with my friend

  • @noisypl
    @noisypl 2 роки тому +2

    I love this video format. Short video, with bits of knowlade. I've subscribed hoping I will be able to learn more TS with you, one small bit at a time :)

    • @willkuerlich
      @willkuerlich Рік тому

      Those 2 minutes videos takes me like to 2 days to partially absorb all those wizardry

  • @sharadindupaul5933
    @sharadindupaul5933 7 місяців тому

    You deserve way more subscribers

  • @XRoydX
    @XRoydX Рік тому

    Pure gold thx

  • @giodefreitas
    @giodefreitas 9 місяців тому +2

    I just don't get why we need to use Extract. will not that result the same as Event? I think this also does the job:
    function sendEvent(
    ...args: TEvent extends {payload: infer TPayload} ? [type: TEvent["type], payload: TPayload]: [type: TEvent["type"]]
    )
    Or am I missing something?

  • @serjio8781
    @serjio8781 10 місяців тому

    You're a true wizard

  • @yeorinim2sida
    @yeorinim2sida Місяць тому

    Based on this video, I created code that specifically changes the return type. I have the following union type and wanted to narrow down the type of the returned array depending on whether it's 'title' or 'body'.
    export type IndexMap =
    | { type: 'title'; keyword: string; target: Title }
    | { type: 'body'; keyword: string; target: Body }
    Therefore, I defined the return type as follows:
    function searchAt(
    content: string
    ): Extract['target'][] {
    return []
    }
    If the type is 'title', it returns a Title[] and if it is 'body', it returns a Body[].
    searchAt('.....') returns Title[]
    searchAt('....') returns Body[]

  • @KirtFitzpatrick
    @KirtFitzpatrick Рік тому

    That's wild.

  • @doniaelfouly4142
    @doniaelfouly4142 Рік тому

    thanks

  • @_the_one_1
    @_the_one_1 2 роки тому +2

    This one is quite complex

  • @gustavcoetzee5018
    @gustavcoetzee5018 Рік тому

    I subscribed please teach me your voodoo. Self taught in the industry now for 5 years transitioned to backend 2 years ago. So much to learn.

  • @Jean-sanson
    @Jean-sanson 2 роки тому

    Very cool

  • @buondevid
    @buondevid 2 роки тому

    Great video Matt!
    A question a bit related: Is there a way to show some suggested autocompletions before some others? For example, a Button component properly typed receives some custom props (e.g. size, color, shape...) but also all the normal HTMLButton attributes (like type, value, etc...). The problem I faced is that when I trigger autocomplete for props it shows me all the props in alphabetic order, which is a bummer since I'd like to see my custom props/attributes first and then all the HTMLButton ones because otherwise they get kinda lost. Thanks :)

    • @mattpocockuk
      @mattpocockuk  2 роки тому +1

      I don't think so - you'd need to build a VSCode extension for that sort of info.

  • @otaviocr9434
    @otaviocr9434 2 роки тому

    I have a situation similar to yours, but my "Event" type could be of 5 different types. What should I do? I've tried some approaches like using `payload as Interface1` or `payload as Interface2` before passing the parameters to the function. I tried using UnionTypes too, but it does not work because the payload could be Interface1 | Interface2 | Interface3 ..... Should i write a TypeGuard for each one?

    • @wiggel2161
      @wiggel2161 Рік тому

      You could use an Interface to hold different types.
      Like this:
      interface Event {
      login: [username: string, password: number],
      logout: [username: string],
      signin: [username: string, password: string, mail: string],
      }
      function sendEvent(type: Type, ...args: Event[Type]) {

      }
      if you only have one argument, you can use it directly as the type in the interface while droppong the varargs.
      In this however vscode won't infer the type of the arg, even if it knows the value of type, while it does infer with the varargs.
      The calling behavior is the same in both situations.

  • @user-lm5ju8nc2t
    @user-lm5ju8nc2t Рік тому

    Args variable inside function is of type `(parameter) args: [type: Type] | [type: Type, payload: unknown]`. Why doest TS infer the type of payload as `unknown`?

  • @aram5642
    @aram5642 Рік тому

    Setting the scalability aside for a moment, would the result be the same if we used method overload for both events?

    • @TheBswan
      @TheBswan Рік тому

      I don't understand questions like this. You're on the internet. Write a little code and see what happens

  • @Espere
    @Espere Рік тому +1

    Very cool, very interesting!
    My question would be: what is the advantace of this syntax over passing an object in the form of:
    type Event = {
    type: "LOG_IN"
    payload: any
    } |
    { type: "SIGN_OUT" }
    function sendEvent(event: Event): void {
    return
    }
    sendEvent({ type: "LOG_IN", payload: "" })
    sendEvent({ type: "SIGN_OUT" })
    in this case, TypeScript would not let me append any payload on a function calling SIGN_OUT promting me that 'payload' does not exist in type '{ type: "SIGN_OUT"; }'.
    Or is there something to this that I'm missing completely?

    • @mattpocockuk
      @mattpocockuk  Рік тому +1

      Absolutely - but I wanted to show how, if you wanted to, you could describe dynamic function arguments.

  • @letmein985
    @letmein985 2 роки тому

    Why this one does not seem to work? It is pretty similar:
    const sendEvent = (
    ...args: Extract extends {payload: infer TPayl}
    ? [type: T["type"], payload: TPayl]
    : [type: T["type"]]
    ) => {};

  • @ongvu3917
    @ongvu3917 Рік тому

    Madness.

  • @jellyfish1772
    @jellyfish1772 2 місяці тому

    Did you create typescript?

  • @Matt23488
    @Matt23488 2 роки тому +2

    This is really nice and elegant for the consumer, however there is a major problem with how you have set this up, which you didn't run into since you didn't actually write any code inside the function. The problem is that TypeScript always thinks the payload is of the `unknown` type, even after type guarding the first argument. I tried a different approach but still didn't get it to work properly. I have solved this in the past, though I'd have to look up how I did it, it's been a couple years. It certainly wasn't as elegant as this.

    • @mattpocockuk
      @mattpocockuk  2 роки тому +5

      Yes, inside generic functions you're going to need to do some casting. TS just isn't clever enough to figure out their internals. I generally make sure that everything works for the consumer, then cast a bunch inside the function and make sure it's tested.

    • @rt-rd5rq
      @rt-rd5rq Рік тому

      ​@@mattpocockuk if possible, I want to watch casting examples, as extending this good sample movie.

  • @anishshobith9707
    @anishshobith9707 2 роки тому

    How would I go about doing this with a interface?
    Amazing Video by the way!

    • @mattpocockuk
      @mattpocockuk  2 роки тому

      How do you mean?

    • @anishshobith9707
      @anishshobith9707 2 роки тому

      @@mattpocockuk Thank you for the response, imagine I have
      interface Events {
      "ready": [... parameters]
      //etc...
      }
      If there was a better way to communicate I could give a proper example with links! Thank you!

    • @mattpocockuk
      @mattpocockuk  2 роки тому

      @@anishshobith9707 If you send a TS playground that should give me a better idea.

  • @ericsiddiq7634
    @ericsiddiq7634 Місяць тому

    Gose over my head 😂😮

  • @alkolaqi83
    @alkolaqi83 2 роки тому +4

    Ohh noo this one looks really bad to maintain, what if you have multiple args

    • @NathanHedglin
      @NathanHedglin 2 роки тому +1

      You can have multiple generic arguments

    • @neyk91
      @neyk91 2 роки тому +4

      I don’t know how there is nobody freaking out with these monsters. Building such an inline hell just to type a language. Honestly just considered a compiled language

  • @pratikchakravorty983
    @pratikchakravorty983 Рік тому +1

    Doesn’t this make code much harder to read and understand if you are not super familiar with Typescripts internals and quirks

    • @mattpocockuk
      @mattpocockuk  Рік тому

      This isn't TS internals, it's all documented API's. If you want, you can link them to this video in the comments!

  • @moishinetzer
    @moishinetzer Рік тому +1

    INSANITY!!

  • @PadamShrestha-qo4tu
    @PadamShrestha-qo4tu Рік тому +1

    How to get args type inside function?
    const sendEvent2 = (
    ...args: Extract extends { payload: infer TPayload }
    ? [type: Type, payload: TPayload]
    : [type: Type]
    ) => {
    const eventType = args[0];
    const payload = args[1]
    };
    eventType has proper type but payload type is unknown

    • @brandonleboeuf
      @brandonleboeuf Рік тому +2

      I was able to access the arguments by changing the union type and then casting args[1]. This kinda kills the whole point of it being generic though ,so I would also love to see how @mattpocockuk would answer your question...:
      type LogIn = {
      type: "LOG_IN";
      payload: {
      userId: string;
      };
      }
      type SignOut = {
      type: "SIGN_OUT";
      };
      export type Event = LogIn | SignOut
      const sendEvent = (
      ...args: Extract extends { payload: infer TPayload }
      ? [type: Type, payload: TPayload]
      : [type: Type]
      ) => {
      const eventType = args[0];
      const userId = args[1] as LogIn["payload"]
      if (userId) {
      ...
      }
      };

    • @vadimgalanov3702
      @vadimgalanov3702 Рік тому

      You can separate this Extract part as a type helper and then use it to cast types inside the function:
      type EventArgs =
      Extract extends { payload: infer TPayload }
      ? [payload: TPayload]
      : [];
      const emit = (eventType: T, ...args: EventArgs) => {
      switch (eventType) {
      case 'SIGN_IN':
      const [payload] = args as EventArgs;
      }
      };

  • @bullettime2808
    @bullettime2808 Рік тому

    🤯🤯

  • @VACatholic
    @VACatholic 2 роки тому

    Why not just have separate functions?

    • @mattpocockuk
      @mattpocockuk  2 роки тому +1

      Let's imagine you want to add TS types to a JS function that looks like this. Having separate functions wouldn't work for that case.

  • @trash_dev
    @trash_dev Рік тому +1

    wheres the playground

  • @CristianTorres-fx4rn
    @CristianTorres-fx4rn Рік тому

    The only downside I see on this approach is that It loses conciseness since before we were receiving an eventType and an optional payload that might be null or an object, but at javascript runtime level we are converting this args into an array and later need to destructure it
    const [type, payload = undefined] = args

    • @CristianTorres-fx4rn
      @CristianTorres-fx4rn Рік тому

      const sendEvent = (
      type:T, payload?: Extract extends {payload: infer U} ? U : never
      ) =>{
      console.log(type, payload);
      }
      sendEvent("LOG_IN", {userId:"test"})
      sendEvent("SIGN_OUT")

  • @egorsozonov7425
    @egorsozonov7425 2 роки тому

    Actually, none of this is necessary. Just pass 1 argument of type Event, and the type system will support it. For instance, in the branch where arg.type === "SIGN_OUT", there will be no payload. Problem solved.

    • @mattpocockuk
      @mattpocockuk  2 роки тому +5

      Of course, you can design an API where this is not necessary. But what about typing existing API's where this is an established behaviour?

  • @gilneyn.mathias1134
    @gilneyn.mathias1134 2 роки тому

    thats witchcraft lol

  • @pixiedev
    @pixiedev 4 місяці тому

    in cpp there is method overloading but in ts 🥲 thats horrible