TypeScript — Learn the basics 📖

https://unsplash.com/photos/Xz7MMD5tZwA

This article is part of the collection “TypeScript Essentials”, this is the Chapter three.


If you come from chapter one and two
you’re now motivated to start using TypeScript, so, let’s get started! 🚀


Types? Typing? What is it?

In simple words, types (in web languages):

  • helps the compilers to 
    - optimise the storage of the values in memory — at runtime
    - optimise code (mainly for transpilers, ex: Dart) — at compile time
  • helps developers to know how to use a value (methods, structure, etc)

JavaScript — The dynamic weakly typed

A JavaScript variable can contain any type of data, 
JavaScript is dynamically typed, this means that the type of a variable is defined by the type of its data, at runtime.

let a = 1; // a is a number
a = 'ok'; // a is now a string

Though, since it’s weakly typed, you can do “what ever you want” in your expression.
This means that you don’t need to inform the compiler how to convert your values, example :

let a = 1;
let b = "2";
let c = a + b; // "12" -> c is a string
c = a * b; // 2 -> c is a number

The ECMA specification describe how implementations should behave based on values types during an operation (additive, etc): 
https://tc39.github.io/ecma262/#sec-additive-operators

This might look much simpler or cool than a * (int) b
however this weak typing can introduce some weird type conversion.

const sum = (a, b) => a + b;
sum(1, "2") // "12" -> ??!?!?!

That’s why sometimes you need to “force value type” to avoid runtime unexpected behaviours by using parseInt() or the + (unary operator) .

const sum = (a, b) => parseInt(a, 10) + parseInt(b, 10);
sum(1, "2") // 3
// Or 
const sum = (a, b) => (+a) + (+b);

Then, how to evaluate a variable type in JavaScript ?

  • by using typeof : typeof is a built-in operator that returns the type of a value by looking in the “type flag” (see MDN article)
    Though, beware of typeof null bug ⚠️
  • by using “duck typing” — With normal typing, suitability is assumed to be determined by an object’s type only. In duck typing, an object’s suitability is determined by the presence of certain methods and properties (with appropriate meaning), rather than the actual type of the object.
    ➡️ In few words, we “guess” the type looking at the value structure,
    like lodash do with isArrayLike() , isDate() , … helpers: 
    https://github.com/lodash/lodash/blob/master/isArrayLike.js#L26-L28

TypeScript —Static Structural typing (with erasure)

TypeScript, as a layer over JavaScript, 
add an optional static typing system.
As we’re going to see later, TypeScript bring a lot of more complete type system but some theoretical things must be covered. ⤵️

Structural Typing

Like “duck typing”, TypeScript will look the shape/structure of a type, instead of the type itself.
This is different from C# or Java, and it bring the same flexibility as JavaScript.

type Person = { name: string; };
type Animal = { name: string; kind: string; };
const isPerson(p: Person): p is Person => !!p.name;
let a: Animal = { name: 'flocon', kind: 'cat' };
isPerson(a) // true is TypeScript, impossible in C#/Java

More details in the official TypeScript FAQ.

Type erasure

Type erasure means that all types and annotations are removed at transpile time.
Since JavaScript is still dynamically typed, leaving some comments about types in the code would be pointless for V8 or other engine that have their owns optimisation systems.

However, if you look for Reflection, you should definitely subscribe for 
Chapter 6 : Make types “real”, the type guard functions.


Write our first types with scalars

https://unsplash.com/photos/OO89_95aUC0

The term “scalar” comes from linear algebra, where it is used to differentiate a single number from a vector or matrix. 
The meaning in computing is similar. 
It distinguishes a single value like an integer or float from a data structure like an array.

Use types in TypeScript

It’s really simple, just use a : suffix to any variable, class property declaration or interface property as below:

const myVar: string = 'hello';
type myObjectType = { a: string; b: number; }
class MyClass {
myProp: string;
/* ... */
}

ℹ️ Please notice the space after each : , it’s important to avoid confusion with object property declaration.

The Scalar types

TypeScript define the same scalar types as JavaScript, except it adds a enum , any and never ones:

  • never
The never type represents the type of values that never occur. 
Specifically, never is the return type for functions that never return and never is the type of variables under type guards that are never true.

🚨Beware, this is incorrect: let a: never = undefined; 
never shows the absence of type, you can see it as the opposite of any .

  • any

The variable can contains any type of data, like in JavaScript by default.

let a: any = “hello world”; // valid
let a: any = 1; // valid
let a: any = undefined; // valid
  • string 
    let a: string = “hello world”
  • array (not a scalar though.)
    let a: string[] = [“a”, “b”, “c”] 
    Or tuple : let a: [string, number] = [“1”, 2]
  • object (not a scalar though.)
let a: object = {} // correct
let a: object = { a: 1 } 
// not correct, we'll see this in next section with "interface"
  • boolean
let a: boolean = true; // correct
  • enum
enum Color {Red, Green, Blue} 
let c: Color = Color.Green; // (value in JS is a int)
  • null
let a: null = null; // correct
let a: null = undefined; // incorrect, it should have a null value.
  • undefined
let a: undefined;
let a?: string; // undefined | string

⚠️ Notice: By default, all types includes null and undefined , as says the documentation:

The type checker previously considered null and undefined assignable to anything. Effectively, null and undefined were valid values of every type and it wasn’t possible to specifically exclude them (and therefore not possible to detect erroneous use of them).
--strictNullChecks switches to a new strict null checking mode.
In strict null checking mode, the null and undefined values are not in the domain of every type and are only assignable to themselves and any

So, let a: string = undefined; 
is correct without the --strictNullChecks compiler option.

🚨 Beware : do not use String or Object types, those are primitive class types.


Rich types: interface and type

The “interface” keyword

The scalar types are essential but not sufficient for “real world” usage.
We need to describe complex data structure as types like objects, classes, etc

TypeScript helps us to describe theses types in a “structural way” — as seen in the first paragraph — with the interface keyword.

Interface is used to describe objects, functions or classes.

Describe a User object

interface User {
name: string;
birthday?: string; // optional property
}

As seen in the previous paragraph, the birthday property type is string | undefined .
Since in JavaScript a object property with undefined value is considered missing, having a nullable type property mean a optional object property.

Describe a Function

interface MyAddFunction {
(a: number, b: number): number
}
const add: MyAddFunction = (a, b) => a + b;

Here, the root () describe a callable object, in another way, a function.
We’ll see in the next paragraph how to type a function.

Describe an Cat type by re-using Animal type.

Since interfaces are named, you can extends and “re-open” them.
Example:

interface Animal {
name: string;
}
interface Cat extends Animal {
mustache_len: number;
}
// ---------------------
interface A { a: string; }
interface A { b: string; }
// is the same as 
interface A {
a: string;
b: string;
}

The “type” keyword

The type keyword is used to create “type aliases”, in other words :

  • renaming a existing type:
type MyString = string;

Useful for documentation purposes.

  • compose existing types : union types, augmenting, …
type PossibleValues = "open" | "close";
const a: PossibleValues = "open";
interface Car {
speed: string;
}
type CompetitionCar = Car & { competitor_id: string };

We will see more useful example is future articles about advanced types.

What the difference between “interface” and “type”?

Given the following, example, we could say that interface and type are the same:

interface A {
a: string;
}
type B = { a: string; }

But as explains the official doc and the stack-overflow answer in source, there is some fundamental differences:

  • interface can be edited, they are open; type are closed.
  • type can compose existing types with union, pick etc

To conclude, we can say that:

  • type is especially useful to build intermediate type composed of others
  • interface is useful for build fundamental and re-usable types

Function types, arguments, return type

Declared types, interfaces and variables are finally used 
to declare function signatures.

A function signature (or type signature, or method signature) defines input and output of functions or methods.
A signature can include:
- parameters and their types
- a return value and type
- exceptions that might be thrown or passed back
- information about the availability of the method in an object-oriented program (such as the keywords public, static, or prototype).

https://developer.mozilla.org/en-US/docs/Glossary/Signature/Function


Let’s type some functions

// inline typing
const capitalize = (str: string): string => {
return str.charAt(0).toUpperCase() + str.slice(1);
}
// types with interfaces
interface CapitalizeFunction {
(str: string) => string;
}
const capitalize: CapitalizeFunction = str => {
return str.charAt(0).toUpperCase() + str.slice(1);
}
// function as property in object
interface ReactComponentProps {
onClick: (e: React.MouseEvent<HTMLDivElement>) => void;
}
class MyComponent extends React.Component<ReactComponentProps> { }

As you can see, a function type is always using () , only return type change depending on the context:

  • : for “inline return type — to avoid conflict with arrow function definition
  • => for “interface function type”

ℹ️ Please note that “interface function type” is not usable for function declared function — only for “anonymous functions” aka “arrow functions”.


Object as arguments

Let’s consider this example

interface MyFunctionArgs {
name: string;
security_code: string;
id?: number;
}
function isCompleteUser({ name, id = 1 }: MyFunctionArgs): boolean {
return !!name;
}
isCompleteUser({ name: 'a', security_code: 'b' });

Here, arguments destructuring is clear, the function only pick name property.
This notation is particularly useful for compact function signature and optional and named arguments.
I personally find this syntax useful for “data validation” functions.


Conclusion

In this chapter we saw:

  • That TypeScript bring clearer variable typing with static typing.
  • How the TypeScript typing system behave in a structural and static way.
  • That TypeScript bring all fundamental types and even more with nifty ones like never or any .

The next chapter — “Generics, overload” or “Super-types” — will cover more complex cases with more real world examples 👷


Again, thanks to Sylvain PONTOREAU for the review and inspiration.


Feel free to follow me for Chapter five:

Generics and overload 👨‍🔬