Typing

Fusion.js supports Flow out of the box for static type checking.

Why use static types in JavaScript?

JavaScript lacks language level support for static types, making it difficult to ensure that code you write has the correct types at compile time. Although you can write JavaScript code without any type annotations and have it work, there is the risk that bad code leads to malformed inputs being provided to methods or functions that have to then either gracefully fail (e.g. runtime type checking with error handling) or fail hard (e.g. unhandled exceptions). In the worst case, a mismatched type goes unchecked and cascades to a failure down the road leading to lengthy debugging and unintended side effects.

At its core, static type checking aims to verify and enforce that values being used by your code meet the invariant conditions specified by the types at compile time. This offers a number of advantages over dynamic typing:

  • Early bug and error detection due to type mismatches
  • Self-documenting code through type annotations
  • Cleaner error handling to check variable or argument types at runtime
  • Increases portability of code
  • Improves the testing experience - no more tediously testing for mistyped inputs

Why use Flow?

Flow is not the only solution for static type checking in JavaScript. It is, however, an elegant solution that provides benefits without having to change any of your existing code. Flow has the ability to infer types within your code, ensuring that it can help catch type errors from the get go. And adding type annotations allows for incrementally improving type coverage. We recommended that you use Flow since Fusion.js provides types for core components of the framework that can be used in your application.

Getting Started

If you are new to Flow, we recommended that you check out the Getting Started and Type Annotations documentation.

If you use Visual Studio Code as your primary IDE, check out the Flow for Visual Studio Code extension.

Annotating a simple React component

Annotating React components with type annotations can be very powerful for ensuring your components are used as expected. To illustrate this, let's write a very simple component that displays a user's name.

// src/components/display-name
// @flow
import React from 'react';

type Props = {|
  firstName: string,
  lastName: ?string // may be undefined, if last name not provided
|}
class DisplayNameComponent extends React.Component<Props> {
  render() {
    const fullName = `${this.props.firstName} ${this.props.lastName || ''}`;
    return <div>{fullName}</div>;
  }
}

If we run flow run check, we'd expect to see find no errors. Let's see what happens if we try to also display a user's age.

class DisplayNameComponent extends React.Component<Props> {
  render() {
    const fullName = `${this.props.firstName} ${this.props.lastName || ''}`;
    const age = this.props.age; // type error!
    return <div>{fullName}</div>;
  }
}

In this case, we get the an error that the property age was not found in Props, as illustrated here.

We recommended that you check out the Flow + React documentation.

Annotating a simple Fusion.js plugin

To see how we can leverage built in Fusion.js types, let's write a very simple Fusion.js plugin that logs a random cat fact on every request to /log-cat-fact.

// src/plugins/cat-facts.js
// @flow

import {createPlugin} from 'fusion-core';
import {LoggerToken} from 'fusion-tokens';
import type {Context} from 'fusion-core';

/* Helpers */
const FACTS: Array<string> = [ // provided by: https://catfact.ninja/fact
  "A 2007 Gallup poll revealed that both men and women were equally likely to own a cat.",
  "Most cats adore sardines.",
  "Cats have been domesticated for half as long as dogs have been.",
  "The average litter of kittens is between 2 - 6 kittens.",
];
function getCatFact(): string {
  return FACTS[Math.floor(Math.random() * FACTS.length)];
}

export default createPlugin({
  deps: {logger: LoggerToken},
  middleware: deps => (ctx: Context, next: () => Promise<*>) => {
    if(ctx.url === '/log-cat-fact') {
      deps.logger.log(getCatFact());
    }
    return next();
  }
});

Let's break this down. We import a type {Context} from fusion-core which is used in our middleware signature. This let's us ensure safe accessibility of the ctx object. We are also using the dependency injected logger which is registered with LoggerToken. If we look at the type of logger, we would expect to see:

// defined in https://github.com/fusionjs/fusion-tokens/blob/master/src/index.js#L24
export type Logger = {
  log(level: string, arg: any): void,
  error(arg: any): void,
  warn(arg: any): void,
  info(arg: any): void,
  verbose(arg: any): void,
  debug(arg: any): void,
  silly(arg: any): void,
};

If we tried to do deps.logger.notALogMethod(...) we would expect a type error. Fusion.js will automatically hook up these injected types when they are available, ensuring more seamless type safety across the Fusion.js ecosystem.