# Types

TypeScript uses the so-called **duck-typing** method to provide type safety. This method compares one object with other objects by checking whether both objects have the same type of matching names or not. This checking mechanism is used in computer programming to verify whether an object may be utilized for a specific purpose. TypeScript type checking is used like "stencil". The term duck-typing comes from the English expression *duck test:*

> If something looks like a duck, swims like a duck, and quacks like a duck, it's most likely a duck.

## **Types**

## **I. Primitive types**

1. **Boolean**&#x20;

```typescript
let isTrue: boolean = true
```

2. **Number** - numbers of any numeral system except *BigInt.* Every possible number is of a **number** type, there is no equivalent to int or float

```typescript
let num: number = 10; 
let float: number = 10.5; 
let binary: number = 0b101;
```

3. **BigInt -** BigInt is a "special number", we cannot mix BIgInt with Number type by assignment or other operations.

**BigInt** type of value has to be used for the value that is greater than this constant variable:

```typescript
Number.MAX_SAFE_INTEGER
```

A value that is greater than **Number.MAX\_SAFE\_INTEGER** is not a **Number** anymore, it is a **BigInt**.

{% code lineNumbers="true" %}

```typescript
let bigIntVar: bigint = 100n;
num = bigint; // ERROR
```

{% endcode %}

*Line 2* throws a compile error, **Number** cannot be assigned to **BigInt**, or vice versa.

4. **String**&#x20;

```typescript
let str:string = "string";
```

&#x20;

5. **Null**&#x20;

Some refer to **Null** as the **Object**, others to a primitive. It is more convenient to consider **Null** as a primitive, but there is also a strong argument that **Null** is an **Object**, namely:  &#x20;

`typeof null === object` *is true*.&#x20;

Some say it's just a bug in the language and some say this is done to explicitly indicate that **Null** is exactly an empty **Object**, i.e. that there is no object there.&#x20;

{% code overflow="wrap" %}

```typescript
let nullVar: null = null;
```

{% endcode %}

6. **Undefined**&#x20;

**Undefined** is used when the property is not explicitly defined, so the value is not assigned, **Null** is used when the value has been assigned, so we need to specify either some meaningful value or null

```typescript
let undefinedVar: undefined = undefined;
```

The result of the following checking statement will be `"false"` if the *strict mode* in the compiler options is set to `true` (turned on), but **Null** will be equal to **Undefined** if strict *mode* is turned off.

```typescript
nullVar === undefinedVar
```

## **II. Complex types**

7. **Object**&#x20;

For the **Object** we have to declare the structure of the object after the colon, what structure of the object will it be.

```typescript
let objectVar: { 
    a: string, 
    b: number, 
    c: { d: boolean } 
} = { a: "", b: 1, c: { d: true } }; 
objectVar = { a: "", b: 1, c: { d: false } }; // it will work just fine
```

8. **Array**

```typescript
let arrayVar: number[][] = [[1], [2], [45], [1, 2, 4, 5, 99]];
```

`Array<number[]>` and A`rray<Array>`- similar ways to define an Array, but via Generics (Please see the corresponding chapter to understand Generics);&#x20;

`arrayVar = [];` is aso a valid input. There is nothing to check if there are no elements, and TS still considers this to be a valid Array input;

9. **Tuple**

An array of mixed types can be created with a **Tuple** - it's an array of a specific length, and the TS definition of a tuple captures the types of each element. There are no Tuples in JS, only in TS.

```typescript
let tuple: [number, string, boolean, {a: bigint} ] = [1, "2", true, {a: 10n}];
```

Problem with Tuple type: you can use `push()` and `pop()` methods with Tuple, and TS will not complain about it, i.e. you can break the defined type by doing `tuple.push("s")`. If you would use this method, a new element will be added to the tuple, and it will break the type's notation that we defined above.

Tuples are often used in React:

```typescript
const [state, setState] = useState();
```

10. **Any**

Basically, by using `any`, we "turn off" TS checking.

```typescript
let anyVar = {}; 
anyVar = "str"; 
anyVar = 2; 
anyVar.toUpperCase(); // ERROR!
```

There will be an error in the code above, we are trying to use `toUpperCase()` a method for a value of Number type, but TS will not help us to catch it because any is kind of a way of saying "TS, please turn off your checking algorithms for this variable". We should avoid `any` type by all means.

11. **Unknown**

This type has come to our help to "replace" `any` - it's much much safer than `any`.&#x20;

We can use **`unknown`** when we don't know what type we are going to work with. For example, the given data came from the backend - this is **`unknown`**. If the incoming data from the backend is well-known and the backend does not break any contracts, then it is much better to define a more specific type. But if the data comes from an incomprehensible API, then the **`unknown`** type is a good choice. When we use `any` - we can take any property and define it as `any`, then we can easily get an error as in the code above, but **`unknown`** will be able to determine the type of the passed value and help validate it automatically.

```typescript
let unknownVar: unknown = {a: ""};
unknownVar.upperCase(); // cannot be used
unknownVar.a.upperCase(); // cannot be used
```

Whatever we are trying to do with the value of **`unknown`** type, TS will start complaining. In the code above, we are trying to access the fields and methods of **`unknown`** value, and TS will not allow us to do that. This makes it safe to work with **`unknown`** values. We cannot call `upperCase()` method or any other methods for the object we specified. And even the fields of the **`unknown`** object cannot be accessed, i.e. the value is "closed" from all sides for use and access.

```typescript
num = unknownVar // cannot be assigned
```

TS will not allow you to make this assignment, **`unknown`** cannot be assigned to anything, while you can assign anything for the element of `any` type.

It turns out that nothing can be done with the value of **`unknown`** type. But there is a mechanism to move from **`unknown`** to a specific type by using *Type Guards.*

* What to do if we see a value of **`unknown`** type:

1. Check for a specific type to identify the real type (for example, by using operator *typeof*);
2. If it is an object, then all properties must also be separately checked and their real type has to be revealed;
3. After we checked everything using *Type Guards* and identified the real type, we explicitly cast this type and work with it as with a normally defined type;
4. If the type check fails, then we throw an error or a warning;

The order of work if we see unknown: We put a type check; If it is an object, then all properties must also be checked; And after we checked everything using TypeGuard and identified the type, we explicitly cast this type and work with it as with a regular defined type; If the type check fails, then we throw an error or a warning;

If it is hard to understand do not worry, we will get to the *Type Guards* chapter soon enough. You can find more information about *Type Guards* in the corresponding chapter.

12. **Never**

This type usually is used in scenarios that should never happen, or for code that should never be executed (for example, infinite loops or infinite recursion). It is actively used in *Service Types*, in scenarios when we want to show that something is not working correctly. Also, it is used in service *Conditional Types* to explicitly indicate an impossible scenario.&#x20;

\
13\. **Function**

* **`Function Declaration`** and **`Function Expression`** are typed in the same way.
* **`Arguments`** and **`return type`** are both typed.&#x20;

{% code lineNumbers="true" %}

```typescript
function func(a: string): boolean {
    return true;
}

const funcArrow = (a:string): boolean => { return true };
const funcArrow = (a:string): undefined => { return };
const funcArrow = (a:string): null => { return null };
const funcArrow = (a:string): void => { }; // no return
const funcArrow = (a:string): void => { return };
```

{% endcode %}

Code in line #9 is fine for TS, but nevertheless, it is still better in this scenario to set explicitly the return type `undefined`, so that it is clear what we are working with.

The following code shows the typing of a callback as an argument. It is not a function implementation, but a specification of the type we define, a template of the callback that this function is able to take as an argument.

{% code lineNumbers="true" %}

```typescript
const hoc = (callback: (b:string) => boolean): void => { };
```

{% endcode %}

\
\&#xNAN;*14.* **Literal Types**

To use **Literal Types** we set specific values that we can put in a variable, limiting the variable not just by a specific type, but by specific values.

n of the type we define, a template of the callback that this function is able to take as an argument.

{% code lineNumbers="true" %}

```typescript
let fontWeight: 500 | 600 | 700 | "bold" = 500;
```

{% endcode %}

**Operations with Types:**\
\
Операции с типами:

1. **Intersection Type**

{% code lineNumbers="true" %}

```typescript
let intersectionVar: { a: boolean } & { b: string }; 
```

{% endcode %}

The result of this intersection type will be the following expression:

{% code lineNumbers="true" %}

```typescript
{ a: boolean, b: string  }
```

{% endcode %}

This is how a strict type would look like in this case:

{% code lineNumbers="true" %}

```typescript
let intersectionVar: { a: boolean, b: string };
```

{% endcode %}

because this is still exactly the intersection of 2 types, which means that many different scenarios will fit our defined *Intersection Type*. All we ask in our type is that both elements `(a:boolean & b:string)` must be present in the object, but there can be many more elements and this will not be a problem. We just care about having those 2 elements together all the time, but many more elements can coexist with them.

<figure><img src="https://912526596-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fqr0c15Pq0PDOfb3kGQh6%2Fuploads%2FEuqkEThYC7JzxMuoPBTF%2FScreen%20Shot%202023-05-05%20at%207.22.56%20AM.png?alt=media&#x26;token=9d2e30ad-34dd-4e6c-82be-845723d9b1e8" alt=""><figcaption></figcaption></figure>

t type would look like in this case:

{% code lineNumbers="true" %}

```typescript
let intesectionVar: number & string; // never
```

{% endcode %}

Intersection of 2 `Number` & `String` is type `Never`, because sets of strings and numbers cannot have intersections, one element cannot be a `Number` & a `String` at the same time.

2. **Union Type**

In the following example, the return type is a Union Type that receives a set of `Number` and a set of `Null`.

{% code lineNumbers="true" %}

```typescript
function sum(numbers: (number | string)[]): number | null {
    if (numbers.length) {
        return numbers.reduce<number>((acc, num) => acc + Number(num) || 0, 0)
    }

    return null;
}
```

{% endcode %}

When we define Intersection Type, we mean that both elements will be defined, we expect  A & B to be defined together and together only. When we define Union Type - we expect only one resulting element - either one or the other.
