Hacker News new | past | comments | ask | show | jobs | submit login
TypeScript 3.7: The Biggest Features and How to Use Them (httptoolkit.tech)
140 points by based2 on Sept 12, 2019 | hide | past | favorite | 46 comments



If you're too impatient to wait for first-class optional chaining in TS, there's a nice open-source library for it that's been around for a little while:

Repo: https://github.com/rimeto/ts-optchain

Blog post with some details: https://medium.com/inside-rimeto/optional-chaining-in-typesc...

(I don't work for Rimeto, but a close friend did, which is how I stumbled on that blog post.)


Hadn't seen this before, but I've been using a library from Facebook called idx:

https://github.com/facebookincubator/idx

Idx is a bit more verbose because you have to write an arrow function for each invocation, and it doesn't support default values.


The "Assert signatures" feature looks excellent. The biggest issue with typescript has always been validating external inputs actually meet the type contracts that your code says they do. With this, it looks like this could be written as library!


How do you verify it now? I've been using typeguards, but since i haven't found a good way to generate interfaces from typeguard functions or typeguard functions from interfaces I have to do each manually:

  interface ContactInfo {
    address_1: string;
    address_2?: string;
    city: string;
    state: string;
    zip: string;
    phone: number;
  }
  function isContactInfo(arg: any): arg is ContactInfo {
    let valid = false;
    const contactInfoFields: { readonly [key: string]: string[] } = {
      address_1: [ 'string', ],
      address_2: [ 'string', 'undefined' ],
      city: [ 'string', ],
      state: [ 'string', ],
      zip: [ 'string', ],
      phone: [ 'number' ],
    };

    if (typeof arg !== 'object') {
      return valid;
    }
    for (const key in contactInfoFields) {
      if(!contactInfoFields[key].includes(typeof arg[key]) || arg[key] === '' || Number.isNaN(arg[key])) {
        return valid;
      }
    }
    valid = true;
    return valid;
  }


Not related to the new assert signatures feature (which is great!), but IMO the two best approaches to do what you want today are:

- io-ts[1]

This requires you to write your types as a runtime value, and allows you to extract static types from those, e.g.:

    const ContactInfo = t.type({
       address_1: t.string,
       ...
    })

    type ContactInfo = t.TypeOf<typeof ContactInfo>
You can validate objects with `ContactInfo.decode(someObject)`

Note that we can have the same name for the type and value because they live in different namespaces. When you do ContactInfo.decode, you're calling the decode property of `const ContactInfo`. When you use `ContactInfo` in a type position (e.g. `function x(arg: ContactInfo)`), you're using the `type ContactInfo` declaration

- typescript-is[2]

This uses TypeScript's transformer API. You can use it with https://github.com/cevek/ttypescript. It generates validators for your interfaces/types/etc at compile-time.

[1]: https://github.com/gcanti/io-ts

[2]: https://github.com/woutervh-/typescript-is


typescript-is looks like it can be a drop-in solution since we're only dealing with PODS. Thanks!


You can extract a type from a typeguard using a conditional type!

  type TypeOf<T> = (x: any) => x is (infer U) ? U : never;
I wrote a really small and simple validator library called narrows that works this way [1]. You could simplify your code like so:

  import { number, optional, record, string, TypeOf } from 'narrows';

  const isContactInfo = record({
    address_1: string,
    address_2: optional(string),
    city: string,
    state: string,
    zip: string,
    phone: number
  })
  
  type ContactInfo = TypeOf<typeof isContactInfo>;
The source is less than 50 lines and super readable if you want to dig in. The libraries in the sibling comments are great too!

[1] https://www.npmjs.com/package/narrows


I've been hesitant to switch to something that demands custom type declarations, because we're already autogenerating a lot of types/interfaces with express-openapi (we lean heavily on their request and response validators, too but those don't cover all the types we make), and getting that to play nicely with custom declarations is not my idea of fun. But for the types that aren't tied back to an json-endpoint (we have a few endpoints where we're getting xml or csv or xlsx and a few db<-->backend only interfaces), this looks like a great solution.

Typescript-is looks like it might have an overall edge, though, because we can use it on the autogenerated types as well with almost no hassle.


In addition to the suggestions in the sibling comments, I've had good luck with ts-interface-checker/ts-interface-builder . You add a code generation step to your build process (rather than ttsc) that generates runtime equivalents of your TS types, and there's a library to perform a runtime check.

https://github.com/gristlabs/ts-interface-checker


Check runtypes module.


Definitely. Using Flow, type refinement has been one of my biggest annoyances, because the list of things the type system recognizes as such is hardcoded and can't be extended:

  instanceof
  typeof
  Array.isArray()
  == null
And if you want to refine object types it gets even worse; you have to just check for properties that may or may not be there, which means those properties have to be rigidly distinguishable between object types in a union, etc. etc. Being able to do custom, rich object validation and give it first-class hooks into the type system sounds incredible.


My wishlist for Typescript is:

1. Pattern matching switch against types(like Scala, Haskell, Ocaml)

2. Input validation based on the already defined typescript interfaces


1. Use discriminated union types: https://www.typescriptlang.org/docs/handbook/advanced-types.... - TypeScript these days sticks with the runtime semantics of JavaScript, and discriminated unions are a very natural way to express algebraic data types in JS.

2. Use https://github.com/YousefED/typescript-json-schema to generate a JSON schema, then use a json schema validator to do runtime validation. That's how https://github.com/Polymer/tachometer handles config file parsing and it works very well.


My only problem with 1. is that it desn’t work that well with Error


Also, (1) won’t be exhaustive.


There are a few ways to get exhaustiveness checking in TypeScript when using tagged unions. The way I usually do it is an assertNever function like this:

    function assertNever(value: never): any {
      return value;
    }
Then if you call assertNever on your variable in the default switch case or the last else block, TypeScript will error if you didn't handle every case, since it expects the variable to have been narrowed to the "never" type since there's no remaining value that it can take on. That's also a good place to do a runtime check since TS types aren't guaranteed to match runtime values.


Any who builds a new language and doesn’t make pattern matching a big part of the language is seriously missing out.

It’s easily the #1 thing I miss syntax-wise after using Elixir/Erlang for any period of time.

Patterns fit perfectly with the typed and functional approach, especially with Maybe monads which are used everywhere in some apps.


TypeScript isn't really a new language. It's a (mostly) type-checking-only extension to JavaScript. The team has made a policy of not adding any more runtime behavior changing features. JavaScript will need to add pattern matching for TypeScript to get it.


Oh I know, I actually don't expect it from Typescript. It's portability with JS code is TS's best feature and I don't see how you could marry the two (maybe I'm wrong here). I just wanted to share how much I like pattern matching.


Have been using io-ts to accomplish the latter. We create io-ts types first and then leverage its TypeOf function to generate the regular TS types.


The biggest advantages of io-ts over the jsonschema based type generation approach is that our data doesn't have to be json and we can handle non-serializable and complex data structures eg. class instances etc.

This makes it additionally useful for scenarios where we are writing a typescript library but also want good descriptive error messages for users who use vanilla javascript.

Even for type-checking at io boundaries [1], it is more useful than jsonschema because using io-ts we can, besides validating the data, also transform our incoming and outgoing data into richer data-structures (eg. string <-> date instance, plain javascript objects <-> class instances) through use of encoders and decoders.

[1] https://lorefnon.tech/2018/03/25/typescript-and-validations-...


2. Check out fp-ts or io-ts for runtime type checking.


This is really starting to remind me of Swift.

Coming from Swift, undefined and null handling always felt clumsy in both Javascript and Typescript.

Glad I'll finally get my optionals and nil coalescing back; I've missed them ever since I moved from iOS development to FaaS.


I spent a bit of time in Swift and came back to web and got into frontend which certainly filled a gap for me!

Now we just need `if let` and `guard let` in TypeScript and we'll be good!


it feels like the new features for recent languages are starting to look the same because they're nice. Kotlin has this stuff too. Rust and Kotlin have a similar style of null handling.


Point well made. I've spent some time with TS, Rust, and Kotlin recently and I really enjoy how they all have these kinds of features. Really feels like they see how well it works elsewhere and learn from one another.


or ruby


I don't really care for typescript much but the optional chaining and null coalescing for vanilla are very nice.


Yeah I was going to say the same thing - love that optional chaining syntax... Would cut down so many checks and balances needed to avoid those errors.


Availability of recursive-types is a great news for users of libraries like Mobx state tree [1] which heavily lean on inferred & derived types.

Circular type references among MST models have so far required quite a bit of ceremony [2] to deal with. Really looking forward to get rid of that boilerplate.

[1] https://github.com/mobxjs/mobx-state-tree

[2] https://lorefnon.tech/2019/08/15/dealing-with-circular-type-...


These are all amazing features

I am really hoping for private properties and compiler support of class properties (it supports the syntax, I think, but doesn't compile them down from what I understand).

I also hope they update their decorator support to be in line with https://github.com/tc39/proposal-decorators

I know its stage 2 (and it was stage 3, I think) but I think the reasoning behind the major(ish) changes are solid ones.

Thats pretty much it. Now tc39 just needs to acceept Reflect.metadata :)



Nice to see optional chaining. It was the one feature I missed the most from CoffeeScript. The null coalescing is also nice - I’ve been bitten more than once with falsy values reverting to defaults on assignment.


Anyone have some book or course recommendations for learning Typescript??


The TypeScript Handbook [0] is where I've learned the most from. The New Handbook [1] is starting to come together as well, it's not complete though.

[0]: https://www.typescriptlang.org/docs/handbook/basic-types.htm...

[1]: https://microsoft.github.io/TypeScript-New-Handbook/outline/


that's pretty good actually, thanks!


Basarat's guide is pretty much the best introductory resource : https://basarat.gitbooks.io/typescript/


thanks!


I may not be the sharpest, but isn't top level await just shoehorning synchrony into a language that has been laughing at synchrony for decades?


top level await doesn't make async operations synchronous.

It mostly eliminates the need to have immediately evaluated wrapper async functions that we would have needed so far.

(async () => { await foo(); })();

The added benefit is that imported scripts would be implicitly awaited upon, which has been clunky to do otherwise - you'd need to have a module that exposes an async function and expect the consumer to await on them. But the moment you do that you'd no longer be able to directly run the script through node, so you'd end up needing one more file that imports this module and runs it directly.

Availability of top-level await makes experimenting with (promise-returning) APIs in REPL much easier. It is also a bit step forward in making typescript more useful as a general purpose scripting language (outside webdev context).


> top level await doesn't make async operations synchronous

No, it just lets us write them like they look in synchronous languages.

So we're jumping through even more hoops to avoid the realities of asynchronous JS. IMO it's better for the asynchrony to be explicit. A whole generation of frontend devs aren't going to have any idea how their code is actually running.


Does anyone know when TypeScript plans to introduce dynamic typing for function/method parameters? It would be very useful if TypeScript could infer the type of variables at runtime so that you never have to specify it.


I may have missed your point, but wouldn’t this be essentially useless? You then have no type information about the parameters at all. Wouldn’t this just be the same as the existing implicit “any” type when not in strict mode?


Strongly typed functional languages (including Reason) can often figure out a function's parameter types based on how the variables are used in the function. For example, given `(a, b) => a.push(b * 2)`, it could figure out that `b` must be of type `number` and `a` must be of type `Array<number>`, so the function author wouldn't need to write the type annotations.

I think it's likely that TypeScript will never implement this (at least not at the level of sophistication of typical functional languages), given that it adds a lot of complexity to the language, and making types explicit at boundaries is usually good practice anyway.


It's arguable that they might, actually, add this in the future, but ... it would probably show up as part of how you can use TypeScript to type-check your JavaScript code such as when it infers types from JSDoc.


I think it's a joke




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: