Solving the 'any' problem is harder than you think

Поделиться
HTML-код
  • Опубликовано: 30 сен 2024
  • Solving the 'any' problem in a TypeScript codebase is harder than just taking out all the any's. You need a really solid mental model for figuring out how to type utility functions.
    You can't just put unknown in those slots - because having 'unknown' flowing through your codebase is almost as bad as 'any'.
    Become a TypeScript Wizard with my free beginners TypeScript Course:
    www.totaltypes...
    Follow Matt on Twitter
    / mattpocockuk
    Join the Discord:
    mattpocock.com...

Комментарии • 87

  • @MiceInDownpour
    @MiceInDownpour Год назад +22

    Where does the PropertyKey-Type in the generic example come from? Actually while I like this video I would've preferred and explanation for the generic solution as well. Just for the sake of completeness.

    • @TheBswan
      @TheBswan Год назад +10

      PropertyKey is built in, it's just a union of string | symbol | number, i.e. any valid type for accessing properties on an object

    • @MiceInDownpour
      @MiceInDownpour Год назад

      @@TheBswan Ah, thank you - I was actually referencing the typescript docs, because I suspected it to be a builtin type. But I must have taken a wrong turn, because I didn't find it :D

  • @mattpocockuk
    @mattpocockuk  Год назад +15

    Sorry for the previous failed upload! This one's much better, trust me.

  • @aprilmintacpineda2713
    @aprilmintacpineda2713 Год назад +17

    This reminds me of my first experience with TypeScript, we were a team of JS devs and decided to migrate to TS for the reason that it makes switching between multiple projects better, that was 2020, and damn we had tons of ANY in our code base, we even has as unknown as any in there if I remember correctly!

  • @DuongLe-em4dg
    @DuongLe-em4dg Год назад +6

    This is exactly what I faced when I was in my internship. Gladly I ended up using generic thank to your video about object keys lol

    • @MyPhuckDub
      @MyPhuckDub Год назад

      Dam you're pretty good for an intern

  • @gosnooky
    @gosnooky Год назад +19

    Best TypeScript guy on the interwebs. I started using it in 2016 in a React project and it's been really fun to watch the language grow up, especially with its inference capabilities.

  • @neociber24
    @neociber24 Год назад +2

    I really like generics, is like a cool math problem, but oh boy, I use a lot of time doing that.

  • @JoepKockelkorn
    @JoepKockelkorn Год назад +1

    Any are you okay, would you tell us, that you’re okay?

  • @edgeeffect
    @edgeeffect Год назад +1

    I'm an old hand at programming but an absolute newbie at TypeScript. When I was "having a go at this TypeScript thing" I instinctively smelt something bad about `any`. Fortunately I knew there must be some generics in there somewhere and was rescued quite quickly. I hated the arrival of weak typing back in the 90s... I'm highly amused by the arrival of TypeScript, type-hinting in PHP and Python, and so on. I feel vindicated for spending the last 30 years with a clothes-peg on my nose now.

  • @faisalantu
    @faisalantu Год назад +2

    You are the only TS GURU I need!!, Thanks.

  • @Peshyy
    @Peshyy Год назад +2

    While a great video, I'd definitely take a different approach.
    Directly return `arr.reduce()` instead of using `forEach` and defining a variable inside the function. This will also help with typing the values and inferring types. And to improve code readability, I'd define custom types/interfaces and use those instead of inline typing everything.

  • @jamesgl
    @jamesgl Год назад

    I was so disappointed until you mentioned at the very end you have a generics video explaining them.
    Imo, I would’ve liked that info right after you said you wouldn’t explain it bc here I was like “this guy is gonna NAME how to fix it but not SHOW how to fix it, cmon” 😅

  • @TheBooker66
    @TheBooker66 Год назад +1

    I think the place "any"s are most common isn't utility functions, but rather returning API calls. I just never know what the API returns and that can be problematic for me.

  •  Год назад +2

    Very interesting subject. I still find the video very hard to track and things and explanations fly by very fast for me.
    But i guess that is my problem.

    • @MyPhuckDub
      @MyPhuckDub Год назад

      I agree. I had to practice so much just to understand the basics, I'm using TS for like 7 months and sometimes it's still hard to grasp. I'm not really intellectual LOL!

  • @CoericK
    @CoericK Год назад

    Daf*ck the solution doesn’t show the implementation of the generics, I prefer the any then 😂

  • @nghiaminh7704
    @nghiaminh7704 Год назад

    in the example, item["age"] is a number. why can you cast it to string?

  • @edwardkeselman5118
    @edwardkeselman5118 9 месяцев назад

    What if my function is something like:
    const fetch = (url:string, options: Record ) => {
    //...some code
    const someVal = options.entry ? someArrFromAbove[options.entry as string] : 0; // Use as string here to fix unknown cannot be used is index error.
    }
    It's a utility function and it can be used in different cases and projects so I don't really know which properties options will have.
    Is using unknown in this case is a valid option or should I try to work around with with generics as well

  • @wlockuz4467
    @wlockuz4467 Год назад +2

    I love the analogy of a single "any" leaking more "anys" 🤣

  • @leo11877
    @leo11877 Год назад

    any, are you okay? So, any, are you okay? Are you okay, any? any, are you okay?

  • @victorlongon
    @victorlongon Год назад +2

    Nice one, Matt! What's the PropertyKey type in the example?

    • @omarlopez4340
      @omarlopez4340 Год назад +3

      This type is built-in in the language and basically is a union type of `string | number | symbol`

    • @victorlongon
      @victorlongon Год назад +2

      @@omarlopez4340 really?! I didnt know that! Thanks!

    • @anush8
      @anush8 Год назад

      ​@@omarlopez4340Thanks

  • @ColinRichardson
    @ColinRichardson Год назад +3

    Not private this time then?

    • @mattpocockuk
      @mattpocockuk  Год назад +2

      Screwed up the previous edit! This one's much cleaner

    • @ColinRichardson
      @ColinRichardson Год назад

      @@mattpocockuk It's all good... The more times you try it, the better it will end up

  • @FakeNeo
    @FakeNeo Год назад

    If your function could be called from pure JS, using unknown with the explicit validation of arguments is safest, as this will guard against undefined behavior in production. In most other situations, I would avoid using unknown inputs until I have a specific reason to use it.
    One special exception is that I prefer unknown over void for the return value of function arguments, because TS will allow non-void return types to be cast implicitly as void through a nasty edge case:
    ```ts
    const returnsVoid = (fn: () => void): void => fn();
    const doesNotReturnVoid = () => 'example';
    const expr: void = returnsVoid(doesNotReturnVoid);
    console.log(expr);
    ```
    Typescript is trying to be helpful by allowing you to take a ()=>string in a location where a ()=>void is expected. On the surface this is convenient, but in this example the erasure of return type information causes the TS compiler to erroneously allow returnsVoid to implicit cast values to void.

  • @arturamorfati
    @arturamorfati Год назад +1

    glad to see your channel growing!❣

  • @ShotgunAFlyboy
    @ShotgunAFlyboy Год назад

    I basically view Unknown as "I'm deliberately making this section of code painful because you didn't type it and you should".

  • @Dev-Siri
    @Dev-Siri Год назад

    infer > > unknown > any;

  • @AlexAegisOfficial
    @AlexAegisOfficial Год назад

    TLDR realize when you want to define something and when you just want to constraint something. Use generics with extends for the latter.

  • @nomadshiba
    @nomadshiba Год назад

    the problem is mostly with how typescript works (in some case)
    not just any(s)
    in some edge cases logic of typescript is just doesn't make sense, like it shouldn't do that, it should be a bug, but does it, and it wont be fixed because it would be a big breaking change.
    sometimes i look at typescript and say, "why, why handle this like that"
    but its what we have now, still better than just javascript
    i love the idea that types are more than just types with typescript
    i keep thinking what if we made a new more strictly typed language but also inspired by the meta programming of the typescript but even better
    i think rust is doing some cool stuff with "types" and "macros"

  • @prionkor
    @prionkor Год назад

    I am still so confused with generics :(

    • @mattpocockuk
      @mattpocockuk  Год назад

      Does this help?
      ruclips.net/video/dLPgQRbVquo/видео.html

  • @wobsoriano
    @wobsoriano Год назад

    Titanic....

  • @johtso1
    @johtso1 Год назад +1

    another banger!

  • @aleksander5298
    @aleksander5298 Год назад

    But there is no protection here against reading a key that does not exist, isn't there some way to return exact values in such a function?

  • @thechaoslp2047
    @thechaoslp2047 Год назад +1

    If you dont know generics, take the 10 minutes to learn genereics

    • @ChillAutos
      @ChillAutos Год назад

      TBF whilst you can learn the basics of generics in 10 minutes the end solution is still going to cause issues for a lot of developers. Its a lot more complicated than just learn generics and then we end up in a situation where only 1-2 people can understand that code. You an say just get good but you could say that about anything really.

    • @culi7068
      @culi7068 Год назад +1

      Uh I don't know if I agree. It's much harder to refactor our a deeply embedded `any` than it is to fix a generic. Generics with the `extend` keyword are very safe and extremely useful
      speaking as someone who just refactored some really painful typescript

  • @TheKayser1
    @TheKayser1 Год назад

    great video as usual!! It wouldn't be better to only allow to groupBy Records which values are string | number | symbol and avoid casting?
    function groupBy(arr:T[], key:K){
    let result ={} as Record
    arr.forEach(item=>{
    const resultKey = item[key]
    if(result[resultKey]){
    result[resultKey].push(item)
    }else{
    result[resultKey] = [item]
    }
    })
    return result
    }

    • @mattpocockuk
      @mattpocockuk  Год назад

      No, because then you can't pass in things that aren't PropertyKeys in the values, which seems overly restrictive given that we only care about one value.

  • @jeromealtariba7339
    @jeromealtariba7339 Год назад

    result : make sure to know generics when using typescript 😀😁

  • @elraito
    @elraito Год назад +6

    As a beginner the hardest part for me has been to type things that can error or fail. Say a fetch that can return a general error, correct result or one of several custom errors where you can have one or many error messages that are not standard. So far ive been using type unions as a substitute for anys as return types and it works-ish but id be interested to see how the more pros handle things.

    • @mattpocockuk
      @mattpocockuk  Год назад +4

      Yes - this is complex because technically, in a try/catch, any error can be thrown. You can't know the errors ahead of time.
      So returning a union type that may contain an error is very legit, and used often. I think it's actually the default behaviour in langs like Rust. I think it's called an Either type? Though likely wrong there as I don't use Rust.

    • @johtso1
      @johtso1 Год назад

      @@mattpocockuk this is something I've been wondering about. From a typing perspective is it better to return rather than throw errors? Better to use safeParse rather than parse? Maybe the distinction is, don't throw errors for things that are expected to happen often in the "normal" operation of the code?

    • @mattpocockuk
      @mattpocockuk  Год назад +2

      @@johtso1 It's a tricky one, because whenever you throw anything, you're throwing it into a hole that can't be typed. I.e. unknown is truly the only correct typing for an error.

    • @johtso1
      @johtso1 Год назад

      @@mattpocockuk I guess you can think of it as, errors allow the caller to choose not to handle certain results (just don't catch the error). In contrast, properly typed return values force the caller to deal with all possible outcomes. I like the idea of Either and how honest it keeps you. Exceptions seem like kind of an escape hatch

    • @bigpopakap
      @bigpopakap Год назад +1

      I'd say if you want to type your errors, a good first option is to return a tagged union, so you can check whether it was success or error, what the specific error was, and properties about that error, all while remaining type safe.
      Another good solution would be to use fp-ts or @effect, which has a bunch of primitives for the Either type (and many others) and tons of stuff to be fully type safe about errors (and concurrency, and composability, and dependency injection and so much more).
      Of course, this is JavaScript and nothing will stop you from a row developer just throwing any old error that you don't know about

  • @culi7068
    @culi7068 Год назад

    Here's how I did a very similar function just the other day. In just 11 lines of code!
    The function is to take an array of objects that have, e.g., an `"id"` key, and return a record of those objects indexed by their key. Same TypeScript principles apply
    ```
    /**
    * Create a lookup table from an array by an arbitrary key
    */
    export const createRecordFromKey = <
    K extends string,
    T extends Record
    >(
    array: T[],
    key: K
    ): Record =>
    array.reduce(
    (acc, item) => ({ ...acc, [item[key]]: item }),
    {} as Record
    );
    ```
    the limitation here is `K` is typed as a `string`. It could be a `symbol or keyof T` instead, but tbh I haven't come across a usecase for that expansion

    • @culi7068
      @culi7068 Год назад

      The other benefit is, if you don't have the eslint rule that forces you to annotate the return value on exported functions, you can omit the return value entirely and rely on TS' type inference

    • @culi7068
      @culi7068 Год назад

      Also props to you Matt for always using realistic examples. I almost always am able to see how the example relates to my own code

  • @SergeiGrishchenko-o5m
    @SergeiGrishchenko-o5m Год назад

    I saw that you used something like `T extends (...args: any[]) => any` as generic bound in some your previous video. And you said something like "Sometimes you have to use `any` in your code". I believe that technically it is not necessary. You can use something like `T extends (...args: never[]) => unknown` for it. I really don't understand why people don't use this pattern to avoid `any` in their code. It would be great if you make a video about it.
    Thank you though.

    • @mattpocockuk
      @mattpocockuk  Год назад

      Any's in generic constraints are absolutely fine - they don't actually reach the rest of your code. So, T extends (...args: any[]) => any is fine.

    • @SergeiGrishchenko-o5m
      @SergeiGrishchenko-o5m Год назад

      @@mattpocockuk Yea, I know, but it is more about code style. There are eslint rules, that check that you don't use `any` type in your code, and you don't need to make exceptions for such rules even in case of generic bounds.

  • @aram5642
    @aram5642 Год назад

    Assume you publish a lib that deals with errors and allows to pass a cb to handle an error. Would you type the error param as any or unknown?

    • @mattpocockuk
      @mattpocockuk  Год назад +1

      Definitely unknown! Same as the way catches are handled in TS

  • @olduniverse9270
    @olduniverse9270 Год назад

    3:49 that's good

  • @caoimhe801
    @caoimhe801 Год назад +1

    first : )

  • @AndersGustafsson87
    @AndersGustafsson87 Год назад

    4:20 "the result is typed properly"
    is it really though? :p

    • @mattpocockuk
      @mattpocockuk  Год назад

      Yes!

    • @AndersGustafsson87
      @AndersGustafsson87 Год назад

      @@mattpocockuk Was referring to keys being number. For example Object.keys(result) will not actually give you number[]

    • @mattpocockuk
      @mattpocockuk  Год назад

      @@AndersGustafsson87 Keys in JS are automatically coerced to strings, so all keys are essentially strings.

    • @AndersGustafsson87
      @AndersGustafsson87 Год назад

      @@mattpocockuk yes

  • @mikechurvis9995
    @mikechurvis9995 Год назад +7

    This is a good video, Matt. My only concern with the Generics approach is that it gives a false sense of security, because Generics in TypeScript, while good for tooling, make no guarantee of *runtime* type validity and are thus insufficient for validation.
    In the example that uses `unknown`, you are forced to implement a validator that demonstrates that the thing you *think* you're about to receive is in fact the correct type. This is necessary if you are writing a public-facing API or a function that converts one object type to another, i.e. from JSON.parse()'s `any` to a typed object. TypeScript makes this easy in the form of Type Predicate functions { `(thing: unknown) => thing is MyType` }, as they let your validator perform type narrowing. As boolean functions, Type Predicates also force you to consider the code path where the unknown is *not* the type you think it is.
    In short, I like `unknown` for the reason you seem to dislike it: it demands defensive diligence. It requires the developer to programmatically guarantee the validity of a type *at runtime*. To my knowledge, Generics can not do this in TypeScript. Please correct me if I'm wrong!
    What are your thoughts?

    • @peterjwest3
      @peterjwest3 Год назад

      I think generics are the solution I’m this case, but obviously unknown is perfect in other situations, such as user input. I’m sure Matt is aware of this as his library changes the output of JSON.parse to unknown

    • @mattpocockuk
      @mattpocockuk  Год назад +16

      I think you should be validating at your app's I/O boundaries, but not validating elsewhere.
      Defensive diligence is important for I/O, far less important otherwise.

    • @mikechurvis9995
      @mikechurvis9995 Год назад +1

      @@mattpocockuk Agreed, for the most part. I tend to mentally focus on I/O boundaries just because that's where most of the bugs come from, in my experience. Thanks for sharing!

    • @mk72v2oq
      @mk72v2oq Год назад +2

      Very strange concern. It is obvious because TS does not provide runtime guarantees like at all. Generic or not - does not matter.
      With your logic we should use unknown everywhere instead of actual types. Just because in runtime it is technically possible to e.g. call a function with wrong arguments.

    • @rutabega306
      @rutabega306 Год назад +2

      The runtime vs compile time concern is not a problem of Generics, it's a "problem" of TypeScript

  • @UocLv
    @UocLv Год назад

    TS is garbadge, this makes me so mad. I better add aditional tests, then learn this boilerplate.

  • @allan_archie
    @allan_archie Год назад

    What of Runtime costs?

  • @tmahad5447
    @tmahad5447 Год назад

    that's why you should not use ts