How to make it easier for yourself to develop using types-spring
Tags: typescript, javascript, webdev, frontend
Published: 2024-03-02 09:02:49

Typescript is great and we know that. However each of us sometimes faces cases when built-in types works slightly loose. May be even any of us listened about package **ts-reset**, which one fixes the most annoying | unsafe points in typescript. But now we will talk about another little-known awesome package called [types-spring](https://github.com/Sanshain/types-spring). Aim of it is also to eliminate some of the shortcomings of the built-in types in typescipt and also deliver additional utility types that will facilitate daily work.

It's includes type safe enhancements that were not provided by ts-reset package, but which we face in daily development. The package contains improvements in types, both the built-in javascript standard functions and DOM methods. Well, less words, more action. Let's see what concretely it proffers:

First of all, install this package and tune it:

```
npm i -D types-spring
```

Also we should have at least 4.7.4 typescript version. If we havn't - make it: 

```
npm i -D typescript@latest
```

Then we need to add the package to `include` section of our `tsconfig.json`: 

```
    "include": [
        "./sources/index.ts",
        "./node_modules/types-spring/**/*"
    ]   
```

That's all. So we are ready to get started:

## Built-in types features:

### Array.map

#### Before:

```ts
const a = [1, 2, 3] as const;         // [1, 2, 3]
let arr = a.map(r => r + '')          // string[]
```



#### After: 

```ts
const a = [1, 2, 3] as const;         // [1, 2, 3]
let arr = a.map(r => r + '')          // [string, string, string]
```

As we can see, with the types-string patch, a more exact array type was deduced.


### Array.isArray

#### Before: 

```ts
function checkArray(a: { a: 1 } | ReadonlyArray<number>) 
{
    if (Array.isArray(a)) {           
        // inside this block `a` variable will have type `any[]` - that's not quite exact. We know `any` is a free type without type checking. So it can lead to either runtime errors:
        a.forEach(item => item.f())   // => runtime error!
    }
    else { a.a }                      // type error: property `a` does not exists!
}
```

#### After: 

```ts
function checkArray(a: { a: 1 } | ReadonlyArray<number>) 
{
    if (Array.isArray(a)) {
        // now `a` has type `number[]`. It's more correct then before anyway
        a.forEach(item => item.f())                         // type error: f does not exist on type number
    }
    else { a.a }                                            // success 
}
```
As we can see in first example (before) typescript's type guard works wrong. And next example with types-spring against has correct type inference.

### Object.create

#### After: 

```ts
let o = Object.create({})                  // any
```

It's amazing, why typescript developers decided to return `any` type from `Object.create` function. Even we pass `null` as argument - will have an object in runtime output (just with null prototype). Why would not return the object type instead? Type-spring do it: 

```ts
let o = Object.create({})                 // object
```

### Object.assign

#### Before:

```ts
let t = Object.assign({ a: 7, b: 8 }, { b: '' }) 
// => {a: number, b: never}
```

As we could see the assign by default works wrong for matching keys with different types. But we know how it workds actually in runtime: the result will overwrite the old property with the new one. So correct behavior will be next: 

```ts
let t = Object.assign({ a: 7, b: 8 }, { b: '' }) 
// => {a: number, b: string}

```
Types-spring does it. Not bad?

But also, no less, and perhaps even more interesting are the type patches that types-spring supplies for DOM:

## DOM features:

### querySelector

First of all it improves detecting Element type from selector signature:

#### Before:

```ts
const input = document.querySelector('input');                   
// input is HTMLInputElement | null
const unknown = document.querySelector('.cls');                        
// unknown is Element | null
const inputWCls = document.querySelector('input.cls');                 
// inputWCls is Element | null

if (divCls) {
    inputWCls.value = ''   // error
}
```

#### After:

```ts
const input = document.querySelector('input');                      
// input is also HTMLInputElement | null
const unknown = document.querySelector('.cls');                     
// unknown is also Element | null
const inputWCls = document.querySelector('input.cls');              
// but inputWCls is HTMLInputElement | null
if (divCls) {
    inputWCls.value = '' // success
}
```
As we could see original behavior extends the type of a specific element to Element if any selector other than the tag name is passed in the argument. But most of the time we don't use single tag selectors. because we need to somehow uniquely identify an element from other elements in the same tag on the page. In this case, we have to explicitly specify the type of the expected element as a generic. With types-string, if the argument starts with a tag selector, then regardless of which selectors are specified next, exactly the type of element whose tag is specified at the beginning will be output. Because... such a selector cannot return some other type of element.


### HTMLElement.cloneNode

#### Before

```ts
const elem = document.getElementById('id')  // elem is HTMLElement
const clonedElem = elem?.cloneNode()        // clonedElem is Node
```

`cloneNode` method clones source object as is. If it clones Node - so it'll be return a Node. But if it clones HTMLElement... obviosly it'll be return HTMLElement. So

#### After

```ts
const elem = document.getElementById('id')  // elem is HTMLElement
const clonedElem = elem?.cloneNode()        // clonedElem is HTMLElement 
```


### currentTarget

Types-spring makes automatic type detection for the currentTarget in MouseEvent, KeyboardEvent and other user interface events (just for addEventListener callback):

#### Before: 

```ts
let elem: HTMLDivElement = document.querySelector('div');
elem?.addEventListener('click', e => {        
    let target = e.currentTarget;      // is EventTarget | null
})
```

`elem` is HTMLElement. So what kind of type may be `currentEvent` inside its ui events? Right. It's it:

#### After

```ts
let elem: HTMLDivElement = document.querySelector('div');
elem?.addEventListener('click', e => {        
    let target = e.currentTarget;     // is HTMLDivElement | null
})
```

Also besides the patches types-spring provides additional [utility-types](https://github.com/Sanshain/types-spring/tree/master/sources/utils), which ones make the development even more efficient. 

### Conclusion

Types-string makes development safer and more efficient than without it. Of course, it doesn't solve all type problems, but at least it makes life easier. It can be used in combination with ts-reset or with other similar packages without any conflicts. What do you think about it?