Type for search...
codete Reconsider Type Script 1 main 240e7e7532
Codete Blog

Reconsider TypeScript

avatar male f667854eaa

19/01/2018 |

9 min read

Artur Kulig

TypeScript is not a new project. The work on it started around 2012, but only in 2014, its 1.0 version was released. It may seem like a novelty because it did not gain popularity until recently when the Angular team switched from Dart to TypeScript for the second version of Angular.

When it comes to everything that Microsoft produces, devs are still very cautious, although it really seems that they understand past mistakes and work hard on changing their company and the company image. To name a few, there is Edge that replaced IE, Microsoft supports Mono project, “reverse Wine” called “Bash on Ubuntu on Windows” and, of course, TypeScript - Microsoft’s investment in web technologies, which, what is strongly understandable, taking into consideration another one - ASP.NET.

Using TypeScript in that early (1.x) version felt like it was going against what you could do with JavaScript, instead of helping. Things used to be made in a way that resulted in such an experience, but the core TypeScript team and contributors took their time and used it well, so since the 2.1 update majority of these issues are gone and you should reconsider using TypeScript for your next project.

So what has changed, what might you have missed and what makes it suitable for safe usage?

 

Type inferring

This is not really a new feature, but a feature that some might not be aware of. The default behaviour of the TypeScript compiler is to try to figure out the type when it is not given implicitly. It doesn’t have to be given implicitly.

const n = 3

parseInt(n, 10)

…will trigger a warning as it doesn’t make sense, it is not compliant to parseInt function signature that resides inside TypeScript library…

declare function parseInt(s: string, radix?: number): number;

It didn’t require us to specify n as in:

const n: number = 3

…and yet TypeScript understands that this is a number due to default type inferring.

 

Union types

Let’s take the following function. It’s pretty clear that it will work for almost all reasonable input.

function appendUnit(v, unit = 'px') {
    return v + unit
}

But if I would like to make this type annotated, I’d prefer not to be forced to produce two functions or complex definitions to indicate that this function accepts only numbers and strings. It’s simple and easy with Union types

function appendUnit(v: number | string, unit = 'px') {
    return v + unit
}

 

Intersection types

It is rather a common case to merge two objects into one. TypeScript can handle it and offers type checking for the product of such operation, even if you are not sure about the object’s shape.

Let’s assign properties amount of object in that object with the following function:

function dictionaryOf<T extends Object>(source: T): T & { length: number } {
    return Object.assign(
      {
        length:
          Object
            .keys(source)
            .filter(k => k !== 'length')
            .length
      },
      source
    )
}

In this particular example specifying result type T & { length: number } is unnecessary. TypeScript would be able to figure it out on its own due to Object.assign definition.

Similarly, but not exactly the same object spread is handled:

const a = {a: 1} // is of type {a: number}
const b = {...a, b: 2} // is of type {a: number, b: number}
b.a // 1
b.c // wrong!

 

Mapped types

As JavaScript is very flexible and Object may be a class instance, a unique value (mostly pre-Symbol solution) or a structure counterpart, it is expected of TypeScript to keep up with all the twists and tricks that JavaScript is capable of.

Consider this scenario where TypeScript has recently improved in partial application.

function defaults(defaults, partial) {
    return { ...defaults, ...partial }
}

function vector2d(partialVector) {
    return defaults({x: 0, y: 0}, partialVector)
}

const result = vector2d({x: 2})

Mentioned above code sample is fine in TypeScript, but to provide a better developer experience, to have more strict type checking, to better express intentions, and eliminate confusion it would be best if partial had only properties that were present on defaults too. Fortunately, we can do that!

Let’s read this generic function:

function defaults<T> /* generic argument T */ (
  /* default should be exactly this type */
  defaults: T,
  /* partial type should be nothing more than this type */
  /* This is mapped type */
  partial: {[id in keyof T]?: T[id]}
  /* result will be of this type */
): T {
    return Object.assign({}, defaults, partial)
}

For common cases, there are few helpers that allow expressing intention more swiftly. In this case, you can use Partial, which expresses the same mapped signature as above.

function defaults<T>(defaults: T, partial: Partial<T>): T {
    return Object.assign({}, defaults, partial)
}

With mapped types, you can ensure not only that two objects have the same keys, but that the same properties in both objects have the same type. This is T[id] part of {[id in keyof T]?: T[id]}.

To broaden the understanding of what it might be used to, let’s create a function that resolves all promises provided within an object and returns the Promise of that object instead. It’s a reversed example of previous mapped types usage where you start with a mapped type, but for TypeScript it is all the same.

/* as generic argument T will be used
  to create others and not directly
  let's call it archetype */
function resolveAll<T>  (
  /* an object with same keys as T,
    but every property value
    will not be the same type,
    but rather that "expected" type,
    inside of a Promise */
  promises: {
    [id in keyof T]: Promise<T[id]>
  }
  /* we'll return not that type,
    but that ach
): Promise<T> {
    // (implementation)
}

 

JSX

Despite close cooperation between TypeScript and Angular teams, there is no obstacle, but actually, a lot of help when it comes to writing applications with React (or React compatible) libraries.

Consider these examples:

// ES5 + CommonJS - factory function
module.exports = React.createClass({
    propTypes: {
        foo: React.PropTypes.number.isRequired,
        bar: React.PropTypes.number
    },
    render() {
        return <div>{this.props.foo * (this.props.bar | 0)}</div>
    }
})


/// ES6 class
export default class Whatever extends React.Component {
    render() {
        return <div>{this.props.foo * (this.props.bar | 0)}</div>
    }
}
Whatever.propTypes = {
    foo: React.PropTypes.number.isRequired,
    bar: React.PropTypes.number
}

In development mode, React will check whether these definitions match the actual input, a component receives. This however requires code to be actually run. With TypeScript you will get errors before that.

// TypeScript
export default class Whatever extends React.Component<{foo: number, bar?: number}> {
    render() {
        return <div>{this.props.foo * (this.props.bar | 0)}</div>
    }
}

TypeScript compiler will notify you if you try to use Whatever component without specifying fooproperty. If your editor is smart about TypeScript you’ll see an indication ever earlier.

 

Async/await

Probably the most notable feature of (still) new JavaScript (in most cases already supported) works fine with TypeScript too. Basically, TypeScript compiler does what you would expect from Babel.

function whatever () {
Promise.resolve(5)
    .then(value => { /* `value` is a number */ })
}

// ...but also
async function whatever () {
const value = await new Promise.resolve(5)
/* `value` is a number */
}

Although I assume it to be a feature that will not be widely used, it is still worth mentioning that TypeScript already has some support for asynchronous iterators as well and as time goes by it will be better.

 

Modules

Initially, TypeScript had its own module system, which made it hard to use with other tools. If you had seen TypeScript file a while ago, you might have seen code like this:

module Foo {
    export function bar() { return 'ok' }
}
Foo.bar() // ok

Modules now are namespaces, to avoid confusion as it used to be and is pretty clear that modules are going to be supported with future versions of JS.

namespace Foo {
    export function bar() { return 'ok' }
}
Foo.bar() // ok

It seems namespaces are only to provide compatibility with existing code utilizing modules. As TypeScript supports ES6/CommonJS module syntax, consider modules/namespaces deprecated and TypeScript in line with JavaScript.

 

Dynamic import

If we are on the topic of the module system, I have to mention dynamic imports as well. In the 2.6 version, transpired, if not in esnext target, dynamic imports allow using import keyword to load a module asynchronously.

The result of that call is typed just like static import, so this is a fully functional part of the language. Mind that you still need browser/bundler/runtime support for that feature, transpired or not.

 

Third-party libraries support

This was probably the most cumbersome question of all. Without proper cooperation with legacy code and regular JavaScript libraries, it was just a curiosity. How well it integrates with 3rd party libraries then? 

For a long time answer was not satisfying - you have to provide definitions. There was, of course, DefinitelyTyped project, but it had some issues with version compatibility. Since 2.0 version of TypeScript, you can use @types/* (notice namespace) packages from npm. It handled different 3rd party libraries' versions problem way better, but you still had to do it, which was troublesome, if you use not a popular library that did not have definitions up to date or at all.

This changed with 2.1 and “you can import a JavaScript module without needing a type declaration”. Popular libraries are still a reason to use @types/*, but for cases without type definitions - you are still good to go!

…but wait, that’s not all!

TypeScript handles well other features that do not require explanation, but merely a notice:

  • template strings
  • arrow functions
  • decorators (behind –experimentalDecorators flag)
  • ** operator

 

It does the job

I and my team of 4 front-end developers are using that across 3 distinctive targets for over a year:

  • browser
  • browser + SSR with node (next.js)
  • react-native

We also maintain a portion of code that we share across our projects. We use TypeScript with React and a few more libraries.

My favorite is a class - connector for our back end, we do not write by hand, but we generate using provided by that backend definition of responses of every endpoint. This helps with developing a lot, as very little to annotate by hand remains in general!

We are very satisfied with TypeScript and I can tell that for sure I’m not coming back to just JS in any version.

Rated: 5.0 / 1 opinions
avatar male f667854eaa

Artur Kulig

Senior Software Developer

Our mission is to accelerate your growth through technology

Contact us

Codete Przystalski Olechowski Śmiałek
Spółka Komandytowa

Na Zjeździe 11
30-527 Kraków

NIP (VAT-ID): PL6762460401
REGON: 122745429
KRS: 0000696869

Offices
  • Kraków

    Na Zjeździe 11
    30-527 Kraków

  • Lublin

    Wojciechowska 7E
    20-704 Lublin

  • Berlin

    Wattstraße 11
    13355 Berlin

Copyright 2022 Codete