3 ms·
> and although the tsc _says it works_, I always have a suspicion it could _not work_ TypeScript fails me almost exclusively due to `!`. The association is so
by dmitryminkovsky 5y ago
> and although the tsc _says it works_, I always have a suspicion it could _not work_
TypeScript fails me almost exclusively due to `!`. The association is so strong that whenever I encounter an error, I am excited to see if it's due to `!`. In the absence of non-null type assertions, TS feels very safe to me, largely due to experience of not seeing it fail.
- dtech 5y agoThrow in an eslint rule to disallow ! outside of tests, you won't regret it
- dmitryminkovsky 5y agoUnfortunately I have some spots where it’s unavoidable due to generated types that I can’t control being not quite right. It’s sad! Yes, otherwise I’d never use it, or far more sparingly (sometimes it is “correct”, though with effort it can be avoided)
- renke1 5y agoI have a utility function that I use for cases where I know for sure that a value is defined: export function assertIsDefined<V>( value: V, buildMessage = defaultAssertIsDefinedMessage ): asserts value is NonNullable<V> { if (value === undefined || value === null) { throw new Error(buildMessage(value)); } } It would prevent follow up errors if the value in fact was not defined.
- dmitryminkovsky 5y agoYeah I could definitely do this, and maybe should. But the issue is so systemic due to the bad generated types that this would cloud the code without much added benefit.
- renke1 5y agoI had my own share of problems with generated code, so I feel you. I often use that utility when I have some type where under certain condition a value will always be defined, but a union type would make it too complex to be useful.
- dmitryminkovsky 5y agoYeah in my case I am using Postgres via Hasura, which exposes the Postgres DB via a GraphQL API, which in turn can be used to generate types. Unfortunately the nullability of fields in a Postgres view cannot be determined, so all fields in the corresponding types are nullable [0], even if they aren’t in the actual underlying table. So I end up using ! a lot because I know the field is non null in Postgres. It’s a unfortunate. [0] https://github.com/hasura/graphql-engine/issues/1965 https://github.com/hasura/graphql-engine/issues/1965
- jraph 5y agoWhat do you mean, could you explain / give an example?
- dmitryminkovsky 5y agoTypeScript has the ! operator [0], which can be used to override the type system’s null check. For example, suppose I have a variable of type type Name = string | null const name : Name = null and I pass it to a function that expects just a string (not null), function len(str: string) { return str.length; } Then len(name); will fail type checking, but len(name!); will not fail type checking, but will fail at runtime. [0] https://stackoverflow.com/questions/38874928/operator-in-typescript-after-object-method https://stackoverflow.com/questions/38874928/operator-in-typ...
- 8note 5y agoOh wow, I did not know that this existed. Definitely gonna need a linter to make sure it's never used. Do you know why it was added? Ive used an annotation that disables type checking on a couple unit tests, but this seems much harder to notice
- dmitryminkovsky 5y agoDefinitely not a TS expert but I think it’s for cases where you know something “better” than it is known to the type system. An example that pops to mind is how React’s createContext() typings for some reason require a non-bill default value, but you may not have such a value at the time the call is made. Personally I think it’s totally legit to just do createContext<T>(null!). I don’t think this leads to errors that won’t get quickly noticed. But if you get carried away, it’ll bite you for sure, especially in code that has more going on than define a React context.
- TranquilMarmot 5y agoI write TypeScript every day, and sometimes it can be pretty useful. Like, if you have an interface... interface MyInterface { someValue?: string; /** Let's say this only exists if `someValue` exists */ someOtherValue?: string; } And then in some code later... const myFunction = (someParam: MyInterface) => { if (someParam.someValue) { myOtherFunction(someParam); } } // let's say this function is only EVER called if `someValue` exists // we know more than the type checker in this case const myOtherFunction = (someParam: MyInterface) => { console.log(someParam.someValue!.length); console.log(someParam.someOtherValue!.length); } There are definitely more "correct" ways to do this (i.e. using `Partial` or re-casting to another type) but sometimes there are edge cases that aren't worth doing things more correctly (like, if this is only happening in one spot) Lots of folks make the mistake of assuming the TypeScript is strongly typed, but it's not really... I like to think of its type system as "squishy" in that you can very very easily fake things or trick it into compiling. I think of the types more as comments and hints about the code and not there to ensure program correctness. This is actually one of my favorite features of TypeScript; it's not as strict as, say, Java or Scala, and it lets you get away with some things without having to rework all of your types. This can be a bit of a footgun, BUT if you're also writing unit tests and integration tests then you'll have all of your bases covered.