TypeScript 3.7: The Biggest Features and How to Use Them
httptoolkit.tech
httptoolkit.tech
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.)
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.
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;
}- 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.
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!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.
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.1. Pattern matching switch against types(like Scala, Haskell, Ocaml)
2. Input validation based on the already defined typescript interfaces
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.
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.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.
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-...
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.
Now we just need `if let` and `guard let` in TypeScript and we'll be good!
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-...
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 :)
[0]: https://www.typescriptlang.org/docs/handbook/basic-types.htm...
[1]: https://microsoft.github.io/TypeScript-New-Handbook/outline/
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).
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.
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.