First-fiddle on TypeScript 
Tags: javascript, typescript, devjournal, womenintech
Published: 2021-08-01 23:52:17

I have spent the past few weeks diving in to **TypeScript**. It has been fun! 😆 TypeScript is a superset of JavaScript, that means it carries all of the JS syntax, including some new syntax as well. Variables in TypeScript have static types. Types come after a `:`.

TypeScript knows the types of built-in JavaScript functions and methods. While any code with valid syntax typically runs in JavaScript, code must have valid *syntax* and valid *type check* in order to run in TypeScript.

> **JavaScript**  `syntax check` → `execution`
**TypeScript**  `syntax check` → `type check` → `execution`

For example in TypeScript, `a + b` is a valid syntax. However, if `a` is a `number` and `b` is a `boolean`, then `a + b` will not fulfill a valid `type check`. This results in a *type error*.

## Basic types

To declare our own names for types, we can use the `type` keyword. It is similar to declaring a variable `let`. By convention, user-defined type names are *UpperCamelCased*.

```ts
type CarrotCake = string;
let c: CarrotCake = 'It is delicious';
c;
// 'It is delicious'
```

Types are only used during *type checking* prior to *execution*. The declaration of `type CarrotCake = string` is a TypeScript syntax. The technical jargon is *'type erasure'.* It is solely used for *type checking*, and later on discarded from the compiled output. Types are not exclusive to only *variables*, but also to *function types*.

```ts
type HowManyEggsForACake = (egg: number) => string;

function redVelvetCake(egg: number): string {
  return egg.toString() + ' eggs to start';
}

const f: HowManyEggsForACake = redVelvetCake;
f(2);
// '2 eggs to start'
```

I can implement similar de-structuring assignment on JavaScript to TypeScript.

```ts
function makeCake({cake}: {cake: string}): string {
  return cake;
}

makeCake({cake: 'Sponge Cake'});
// 'Sponge Cake'
```

## Arrays

I like the fact that TypeScript allows the use of an *'array of data'* type for *function arguments* and *function return values*. In the example below, function arguments contain an array of strings.

```ts
function arrayOfCakes(cakes: string[]) {
  return cakes;
}
```

`string[]` is syntactically identical to `Array<string>`. This syntax, `let otherCakes: Array<string> = ['Banana bread', 'Bebinca']`, is also perfectly valid. Furthermore, I can make an *'array of arrays of data'*. Example use of case as follows.

```ts
const arrayOfCakes: string[][] = [['Birthday Cake'], ['White Cake']];
// optionally, you can write (string[])[]


function cakes(namesAndCakes: [string, number][]): string[] {
  return namesAndCakes.map(cake => cake[0]);
}

cakes([['Angel Cake', 3], ['Apple Cake', 1]]);
// ['Angel Cake', 'Apple Cake'];
```

## Inference

We can certainly avoid writing types. This is called *type inference*, and TypeScript *infers* types. Inference means that the compiler determines the types for us. Types does not necessarily differ from place to place. Every type can be used anywhere that types are allowed. Such as, `string` can be a type of a variable, a function argument, or a function's return value.

```ts
function cake() {
  return 'cup' + 'cake';
}

cake();
// 'cupcake'
```

TypeScript has *generic function inference*, and this allows us to call function many times without specifying the type parameters. We can name our generic type parameter `T`, and you can use any name you like. Type safety will still be maintained throughout code execution.

```ts
function cakeSample<T>(cakes: T[]): T {
  return cakes[1];
}

let results: [boolean, string] = [
  cakeSample<boolean>([true, true, false]),
  cakeSample<string>(['Walnut Cake', 'Orange Cake', 'Fruit Cake']),
];
results;
// [true, 'Orange Cake']
```

## Type error

In JavaScript, there is a common symptom of `undefined` error from a function. TypeScript's object types inform back of any type errors during compilation. This helps to identify early rather than failing in production.

```ts
type Cake = {
  ingredient: string;
  delicious: boolean
}

let lemoncake: Cake = {
  ingredient: 'lemon',
  delicious: true,
}

lemoncake.delicious;
// true

let bundt: Cake = {
  ingredient: 'chocolate'
}
// type error: missing { delicious: boolean } in type but required in type 'Cake'
```

## Literal types

While we have seen basic types such as `boolean` and `string`, every concrete number is also a type. A variable of type `1` can only hold the number `1`. It can not hold number `2`, this is a compile-time type error. Type `1` here is a *literal number type*. We can combine *literal types* with unions to allow only certain values.

```ts
let uno: 1 = 1;
one;
// 1

let unoDos: 1 | 2 = 2;
unoDos;
// 2

type deliciousCake = 'Biscuit Cake' | 'Angel Food Cake' | 'Carrot Cake';
let aCake: deliciousCake = 'Hazelnut Mousse Cake';
aCake;
// type error: type "Hazelnut Mousse Cake" is not assignable to type 'deliciousCake'
```

## Tuples

This is a new syntax to my TypeScript learning, *tuples*. They are arrays of fixed length, in which each type is defined.

```ts
let trays: [string, number] = ['Pound Cake', 2];
trays[0];
// 'Pound Cake'

let platter: [string, number] = ['Vanilla Cake'];
// type error: target requires 2
```

## Type unions

As an extension of JavaScript, TypeScript is able to add static types to existing JavaScript code. The `a | b` syntax means either type `a` or type `b`.

```ts
type Cake = {name: string};

function isCake(c: Cake[] | Cake): string[] {
  return Array.isArray(c) ? c.map(cake => cake.name) : [cake.name]; 
}

isCake([{name: 'Butter Cake'}, {name: 'Chiffon Cake'}]);
// ['Butter Cake', 'Chiffon Cake']
```

There is a *type unsoundness* that I found on TypeScript. We understand that we can assign our array to a new variable of type `(string | number)[]`. If an array contains only strings, this particular array of `string | number` just happens to have no numbers in it right now. Our array variables have different types, but the underlying array is the same. If I were to push a number onto the strings array, weirdly TypeScript allows this. This clearly violates the `deliciousCakes` variable's `string[]` type!

```ts
let deliciousCakes: string[] = ['Cheesecake', 'Strawberry Cake'];
let cakeLovers: (string | number)[] = deliciousCakes;

cakeLovers.push(8);
cakeLovers;
// ['Cheesecake', 'Strawberry Cake', 8]
```

`any` type would be another example of *type unsoundness* in TypeScript.

```ts
const cake: any = 'Coffee Cake';
const myCake: string = cake;
myCake;
// 'Coffee Cake'

const cake: any = 'Coffee Cake';
const yourCake: number = cake;
yourCake;
// 'Coffee Cake'
```

We defined `cake` a string type, 'Coffee Cake'. We can put a string in an `any`, then assign it to a variable of type `number`. This is wrong, but it will not cause a *type error*. Another way to approach this would be to utilize the `unknown` type. We use `unknown` to represent values of which type is not known yet.

```ts
const cake: unknown = 'Coffee Cake';
const myCake: string = cake;
myCake;
// type error:  Type 'cake' is not assignable to type 'string'

const cake: unknown = 'Coffee Cake';
const myCake: string = typeof cake === 'string' ? cake : 'No Cake';
myCake;
// 'Coffee Cake'
```

We can not use `unknown` where TypeScript expects a `string` or any other type. This will give you a type error. One way to make `unknown` useful is to use conditional narrowing the unknown back to a `string` type.

## Nullish coalescing

In TypeScript, following values are equivalent of `false` — `false`, `0`, `0n`, `''`, `undefined`, `null`, and `NaN`. It gets tricky when..

```ts
function numberOfCake(n: number | undefined): number {
  return n || 1;
}

numberOfCake(0);
// 1
```

This is not fully accurate as `0` is a number too, and `numberOfCake(0)` should be returning `0`. There is a new features called **nullish coalescing** in 2019 ECMAScript. The *nullish coalescing* operator is `??`, and it is similar to JavaScript logical OR operator, `||`.

```ts
1 ?? 'default' === 1
0 ?? 'default' === 0
'cake' ?? 'bananaBread' === 'cake'
'' ?? 'marbleCake' === ''
null ?? 'appleCrumble' === 'appleCrumble'
undefined ?? 'financier' === 'financier'
false ?? 'caramel' === false

function numberOfCake(n: number | undefined): number {
  return n ?? 1;
}

numberOfCake(0);
// 0
```

*Nullish coalescing* does not consider `0` and `''` as *falsey*. It is only used for checking `null` and `undefined`, which means if we are getting `false`, that is because `false` is not `null` or `undefined`.

## Optional chaining

Let's start with a `Cake` type, and each cake has ingredients, but only sometimes. The `Ingredients` type have nuts, but only sometimes. If we want to compile a list of cakes' nuts, `Lamingtons` will not cause a problem since the cake's nuts is `undefined`. However, `Lemon Yoghurt Cake`'s nuts will pose a problem. Since its ingredients is `undefined`, asking for `ingredients.nuts` will cause a *type error.*

```ts
type Cake = {
  name: string
  ingredients: Ingredients | undefined
};

type Ingredients = {
  egg: number
  nuts: string | undefined
};

const cakes: Cake[] = [
  {
    name: 'Walnut Cake',
    ingredients: {
      egg: 4,
      nuts: 'walnuts',
    }
  },
  {
    name: 'Lamingtons',
    ingredients: {
      egg: 2,
      nuts: undefined,
    }
  },
  {
    name: 'Lemon Yoghurt Cake',
    ingredients: undefined,
  },
];

cakes.map(cake => cake?.ingredients?.nuts);
// ['walnuts', undefined, undefined]
```

Optional chaining comes to the rescue, `?.`. It checks whether the object is `null` or `undefined`. If it is, the expression will return `undefined`. If it is not, it will return the value of the object's property. With `?.`, we can securely access properties and sub-properties of an object that may be `null` or `undefined`. *Important to note*, even if it's `null`, it will still return `undefined`.

## As

TypeScript does not allow an object type.

```ts
const cake = {};
cake.name = 'Battenberg Cake';
cake.diameter = 10;
cake;
// type error: property 'cake' does not exist on type '{}'
```

We can use `as` to build a cake object, starting with the empty object `{}`. We are able to surpass the normal type checking, and have the compiler to treat our cake as an object type `{name: string, diameter: number}`.

```ts
const cake = {} as {name: string, diameter: number};
cake.name = 'Battenberg Cake';
cake.diameter = 10;
cake;
// {name: 'Battenberg Cake', diameter: 10}
```

`as` is dangerous as it overrides the type system, and we lose this *type check* safety. For example, we can tell TypeScript that a number is a string. `as` overrides that, and now the types are just wrong.

```ts
const cake: unknown = 1;
const aCakeString = cake as string;
aCakeString;
// 1
```

While my knowledge exposure to TypeScript is minimal, I am super excited to implement this new skill to a real application. I feel TypeScript gives a bit more rigidity to the liberal JavaScript. Thanks TypeScript and many cake types discovery, it's nice getting to know ya! 🍰

***
<center>[**fentybit**](https://fentybit.me/) | [GitHub](https://github.com/fentybit) | [Twitter](https://twitter.com/fentybit) | [LinkedIn](https://www.linkedin.com/in/fentybit/)</center>